Commit 51b8b871 authored by Alfredo Sumaran's avatar Alfredo Sumaran

Merge branch 'master' into issue_14800

# Conflicts:
#	app/assets/stylesheets/framework/variables.scss
parents 824fecb7 67136007
...@@ -132,8 +132,9 @@ linters: ...@@ -132,8 +132,9 @@ linters:
SpaceAroundOperator: SpaceAroundOperator:
enabled: false enabled: false
# Opening braces should be preceded by a single space.
SpaceBeforeBrace: SpaceBeforeBrace:
enabled: false enabled: true
StringQuotes: StringQuotes:
enabled: false enabled: false
......
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.7.0 (unreleased) v 8.7.0 (unreleased)
- Improved Markdown rendering performance !3389 (Yorick Peterse)
- Don't attempt to look up an avatar in repo if repo directory does not exist (Stan hu) - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan hu)
- Preserve time notes/comments have been updated at when moving issue - Preserve time notes/comments have been updated at when moving issue
- Make HTTP(s) label consistent on clone bar (Stan Hu) - Make HTTP(s) label consistent on clone bar (Stan Hu)
- Expose label description in API (Mariusz Jachimowicz) - Expose label description in API (Mariusz Jachimowicz)
- Allow back dating on issues when created through the API - Allow back dating on issues when created through the API
- Fix avatar stretching by providing a cropping feature - Fix avatar stretching by providing a cropping feature
- Fix raw/rendered diff producing different results on merge requests !3450 - Add endpoints to archive or unarchive a project !3372
- Add links to CI setup documentation from project settings and builds pages - Add links to CI setup documentation from project settings and builds pages
- Handle nil descriptions in Slack issue messages (Stan Hu) - Handle nil descriptions in Slack issue messages (Stan Hu)
- Add default scope to projects to exclude projects pending deletion
- Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.) - Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.)
- Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.)
- Gracefully handle notes on deleted commits in merge requests (Stan Hu) - Gracefully handle notes on deleted commits in merge requests (Stan Hu)
- Fix creation of merge requests for orphaned branches (Stan Hu)
- Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla) - Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla)
- Remove "Congratulations!" tweet button on newly-created project. (Connor Shea)
v 8.6.4
- Don't attempt to fetch any tags from a forked repo (Stan Hu)
v 8.6.3
- Mentions on confidential issues doesn't create todos for non-members. !3374
- Destroy related todos when an Issue/MR is deleted. !3376
- Fix error 500 when target is nil on todo list. !3376
- Fix copying uploads when moving issue to another project. !3382
- Ensuring Merge Request API returns boolean values for work_in_progress (Abhi Rao). !3432
- Fix raw/rendered diff producing different results on merge requests. !3450
- Fix commit comment alignment (Stan Hu). !3466
- Fix Error 500 when searching for a comment in a project snippet. !3468
- Allow temporary email as notification email. !3477
- Fix issue with dropdowns not selecting values. !3478
- Update gitlab-shell version and doc to 2.6.12. gitlab-org/gitlab-ee!280
v 8.6.2 v 8.6.2
- Fix dropdown alignment. !3298 - Fix dropdown alignment. !3298
...@@ -108,6 +128,7 @@ v 8.6.0 ...@@ -108,6 +128,7 @@ v 8.6.0
- Add main language of a project in the list of projects (Tiago Botelho) - Add main language of a project in the list of projects (Tiago Botelho)
- Add #upcoming filter to Milestone filter (Tiago Botelho) - Add #upcoming filter to Milestone filter (Tiago Botelho)
- Add ability to show archived projects on dashboard, explore and group pages - Add ability to show archived projects on dashboard, explore and group pages
- Remove fork link closes all merge requests opened on source project (Florent Baldino)
- Move group activity to separate page - Move group activity to separate page
- Create external users which are excluded of internal and private projects unless access was explicitly granted - Create external users which are excluded of internal and private projects unless access was explicitly granted
- Continue parameters are checked to ensure redirection goes to the same instance - Continue parameters are checked to ensure redirection goes to the same instance
......
...@@ -214,7 +214,7 @@ gem 'jquery-rails', '~> 4.0.0' ...@@ -214,7 +214,7 @@ gem 'jquery-rails', '~> 4.0.0'
gem 'jquery-scrollto-rails', '~> 1.4.3' gem 'jquery-scrollto-rails', '~> 1.4.3'
gem 'jquery-ui-rails', '~> 5.0.0' gem 'jquery-ui-rails', '~> 5.0.0'
gem 'raphael-rails', '~> 2.1.2' gem 'raphael-rails', '~> 2.1.2'
gem 'request_store', '~> 1.2.0' gem 'request_store', '~> 1.3.0'
gem 'select2-rails', '~> 3.5.9' gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1' gem 'virtus', '~> 1.0.1'
gem 'net-ssh', '~> 3.0.1' gem 'net-ssh', '~> 3.0.1'
......
...@@ -652,7 +652,7 @@ GEM ...@@ -652,7 +652,7 @@ GEM
redis-store (~> 1.1.0) redis-store (~> 1.1.0)
redis-store (1.1.7) redis-store (1.1.7)
redis (>= 2.2) redis (>= 2.2)
request_store (1.2.1) request_store (1.3.0)
rerun (0.11.0) rerun (0.11.0)
listen (~> 3.0) listen (~> 3.0)
responders (2.1.1) responders (2.1.1)
...@@ -1011,7 +1011,7 @@ DEPENDENCIES ...@@ -1011,7 +1011,7 @@ DEPENDENCIES
redcarpet (~> 3.3.3) redcarpet (~> 3.3.3)
redis-namespace redis-namespace
redis-rails (~> 4.0.0) redis-rails (~> 4.0.0)
request_store (~> 1.2.0) request_store (~> 1.3.0)
rerun (~> 0.11.0) rerun (~> 0.11.0)
responders (~> 2.0) responders (~> 2.0)
rouge (~> 1.10.1) rouge (~> 1.10.1)
......
class @AwardsHandler class @AwardsHandler
constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) -> constructor: (@get_emojis_url, @post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
$(".js-add-award").on "click", (event) => $(".js-add-award").on "click", (event) =>
event.stopPropagation() event.stopPropagation()
event.preventDefault() event.preventDefault()
...@@ -34,7 +34,7 @@ class @AwardsHandler ...@@ -34,7 +34,7 @@ class @AwardsHandler
$("#emoji_search").focus() $("#emoji_search").focus()
else else
$('.js-add-award').addClass "is-loading" $('.js-add-award').addClass "is-loading"
$.get "/emojis", (response) => $.get @get_emojis_url, (response) =>
$('.js-add-award').removeClass "is-loading" $('.js-add-award').removeClass "is-loading"
$(".js-award-holder").append response $(".js-award-holder").append response
setTimeout => setTimeout =>
......
...@@ -344,7 +344,7 @@ class GitLabDropdown ...@@ -344,7 +344,7 @@ class GitLabDropdown
selectedObject = @renderedData[selectedIndex] selectedObject = @renderedData[selectedIndex]
value = if @options.id then @options.id(selectedObject, el) else selectedObject.id value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']") field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
if el.hasClass(ACTIVE_CLASS) if el.hasClass(ACTIVE_CLASS)
el.removeClass(ACTIVE_CLASS) el.removeClass(ACTIVE_CLASS)
field.remove() field.remove()
...@@ -376,6 +376,8 @@ class GitLabDropdown ...@@ -376,6 +376,8 @@ class GitLabDropdown
input = $(input) input = $(input)
.attr('id', @options.inputId) .attr('id', @options.inputId)
@dropdown.before input @dropdown.before input
else
field.val value
return selectedObject return selectedObject
......
((w) ->
notificationGranted = (message, opts, onclick) ->
notification = new Notification(message, opts)
if onclick
notification.onclick = onclick
notifyPermissions = ->
if 'Notification' of window
Notification.requestPermission()
notifyMe = (message, body, icon, onclick) ->
opts =
body: body
icon: icon
# Let's check if the browser supports notifications
if !('Notification' of window)
# do nothing
else if Notification.permission == 'granted'
# If it's okay let's create a notification
notificationGranted message, opts, onclick
else if Notification.permission != 'denied'
Notification.requestPermission (permission) ->
# If the user accepts, let's create a notification
if permission == 'granted'
notificationGranted message, opts, onclick
w.notify = notifyMe
w.notifyPermissions = notifyPermissions
) window
...@@ -2,13 +2,18 @@ class @MergeRequestWidget ...@@ -2,13 +2,18 @@ class @MergeRequestWidget
# Initialize MergeRequestWidget behavior # Initialize MergeRequestWidget behavior
# #
# check_enable - Boolean, whether to check automerge status # check_enable - Boolean, whether to check automerge status
# url_to_automerge_check - String, URL to use to check automerge status # merge_check_url - String, URL to use to check automerge status
# current_status - String, current automerge status # ci_status_url - String, URL to use to check CI status
# ci_enable - Boolean, whether a CI service is enabled
# url_to_ci_check - String, URL to use to check CI status
# #
constructor: (@opts) -> constructor: (@opts) ->
modal = $('#modal_merge_info').modal(show: false) $('#modal_merge_info').modal(show: false)
@firstCICheck = true
@readyForCICheck = true
clearInterval @fetchBuildStatusInterval
@pollCIStatus()
notifyPermissions()
mergeInProgress: (deleteSourceBranch = false)-> mergeInProgress: (deleteSourceBranch = false)->
$.ajax $.ajax
...@@ -27,18 +32,57 @@ class @MergeRequestWidget ...@@ -27,18 +32,57 @@ class @MergeRequestWidget
dataType: 'json' dataType: 'json'
getMergeStatus: -> getMergeStatus: ->
$.get @opts.url_to_automerge_check, (data) -> $.get @opts.merge_check_url, (data) ->
$('.mr-state-widget').replaceWith(data) $('.mr-state-widget').replaceWith(data)
getCiStatus: -> ciLabelForStatus: (status) ->
if @opts.ci_enable if status == 'success'
$.get @opts.url_to_ci_check, (data) => 'passed'
this.showCiState data.status else
status
pollCIStatus: ->
@fetchBuildStatusInterval = setInterval ( =>
return if not @readyForCICheck
@getCIStatus(true)
@readyForCICheck = false
), 5000
getCIStatus: (showNotification) ->
_this = @
$('.ci-widget-fetching').show()
$.getJSON @opts.ci_status_url, (data) =>
@readyForCICheck = true
if @firstCICheck
@firstCICheck = false
@opts.ci_status = data.status
if data.status isnt @opts.ci_status
@showCIStatus data.status
if data.coverage if data.coverage
this.showCiCoverage data.coverage @showCICoverage data.coverage
, 'json'
if showNotification
message = @opts.ci_message.replace('{{status}}', @ciLabelForStatus(data.status))
message = message.replace('{{sha}}', data.sha)
message = message.replace('{{title}}', data.title)
notify(
"Build #{@ciLabelForStatus(data.status)}",
message,
@opts.gitlab_icon,
->
@close()
Turbolinks.visit _this.opts.builds_path
)
@opts.ci_status = data.status
showCiState: (state) -> showCIStatus: (state) ->
$('.ci_widget').hide() $('.ci_widget').hide()
allowed_states = ["failed", "canceled", "running", "pending", "success", "skipped", "not_found"] allowed_states = ["failed", "canceled", "running", "pending", "success", "skipped", "not_found"]
if state in allowed_states if state in allowed_states
...@@ -52,7 +96,7 @@ class @MergeRequestWidget ...@@ -52,7 +96,7 @@ class @MergeRequestWidget
$('.ci_widget.ci-error').show() $('.ci_widget.ci-error').show()
@setMergeButtonClass('btn-danger') @setMergeButtonClass('btn-danger')
showCiCoverage: (coverage) -> showCICoverage: (coverage) ->
text = 'Coverage ' + coverage + '%' text = 'Coverage ' + coverage + '%'
$('.ci_widget:visible .ci-coverage').text(text) $('.ci_widget:visible .ci-coverage').text(text)
......
...@@ -84,12 +84,16 @@ class @MilestoneSelect ...@@ -84,12 +84,16 @@ class @MilestoneSelect
# display:block overrides the hide-collapse rule # display:block overrides the hide-collapse rule
$value.removeAttr('style') $value.removeAttr('style')
clicked: (e) -> clicked: (selected) ->
if $dropdown.hasClass 'js-filter-bulk-update' if $dropdown.hasClass 'js-filter-bulk-update'
return return
if $dropdown.hasClass 'js-filter-submit' if $dropdown.hasClass('js-filter-submit')
$dropdown.parents('form').submit() if selected.name?
selectedMilestone = selected.name
else
selectedMilestone = ''
Issues.filterResults $dropdown.closest('form')
else else
selected = $selectbox selected = $selectbox
.find('input[type="hidden"]') .find('input[type="hidden"]')
...@@ -117,4 +121,4 @@ class @MilestoneSelect ...@@ -117,4 +121,4 @@ class @MilestoneSelect
else else
$value.html(milestoneLinkNoneTemplate) $value.html(milestoneLinkNoneTemplate)
$sidebarCollapsedValue.find('span').text('No') $sidebarCollapsedValue.find('span').text('No')
) )
\ No newline at end of file
...@@ -4,7 +4,6 @@ expanded = 'page-sidebar-expanded' ...@@ -4,7 +4,6 @@ expanded = 'page-sidebar-expanded'
toggleSidebar = -> toggleSidebar = ->
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}") $('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
$('header').toggleClass("header-collapsed header-expanded") $('header').toggleClass("header-collapsed header-expanded")
$('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' }) $.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
setTimeout ( -> setTimeout ( ->
......
...@@ -13,10 +13,10 @@ ...@@ -13,10 +13,10 @@
// Toggle between two states. // Toggle between two states.
.js-toggler-container { .js-toggler-container {
.turn-on { display: block; } .turn-on { display: block; }
.turn-off { display: none; } .turn-off { display: none; }
&.on { &.on {
.turn-on { display: none; } .turn-on { display: none; }
.turn-off { display: block; } .turn-off { display: block; }
} }
} }
.calender-block {
@media (min-width: $screen-sm-min) and (max-width: $screen-lg-min) {
overflow-x: scroll;
}
}
.user-calendar-activities { .user-calendar-activities {
.calendar_onclick_hr { .calendar_onclick_hr {
padding: 0; padding: 0;
......
...@@ -121,7 +121,7 @@ p.time { ...@@ -121,7 +121,7 @@ p.time {
text-shadow: none; text-shadow: none;
} }
.thin_area{ .thin_area {
height: 150px; height: 150px;
} }
...@@ -148,7 +148,7 @@ li.note { ...@@ -148,7 +148,7 @@ li.note {
} }
} }
.wiki_content code, .readme code{ .wiki_content code, .readme code {
background-color: inherit; background-color: inherit;
} }
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
vertical-align: top; vertical-align: top;
} }
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
.issues-filters, .issues-filters,
.issues_bulk_update { .issues_bulk_update {
.dropdown-menu-toggle { .dropdown-menu-toggle {
......
...@@ -91,7 +91,7 @@ label { ...@@ -91,7 +91,7 @@ label {
} }
.form-control::-webkit-input-placeholder { .form-control::-webkit-input-placeholder {
color: #7f8fa4; color: $gl-placeholder-color;
} }
.input-group { .input-group {
......
...@@ -33,10 +33,15 @@ ...@@ -33,10 +33,15 @@
background: $color; background: $color;
} }
.complex-sidebar .nav-primary {
border-right: 1px solid lighten($color, 3%);
}
.sidebar-wrapper { .sidebar-wrapper {
background: $color-darker; background: $color-darker;
.sidebar-user { .sidebar-user {
border-top: 1px solid lighten($color, 3%);
background: $color-darker; background: $color-darker;
color: $color-light; color: $color-light;
...@@ -62,7 +67,6 @@ ...@@ -62,7 +67,6 @@
.count { .count {
color: $color-light; color: $color-light;
background: $color-dark;
} }
} }
......
...@@ -36,7 +36,7 @@ header { ...@@ -36,7 +36,7 @@ header {
padding: 0; padding: 0;
.nav > li > a { .nav > li > a {
color: #7f8fa4; color: $gl-icon-color;
font-size: 18px; font-size: 18px;
padding: 0; padding: 0;
margin: ($header-height - 28) / 2 0; margin: ($header-height - 28) / 2 0;
...@@ -62,7 +62,7 @@ header { ...@@ -62,7 +62,7 @@ header {
background-color: #eee; background-color: #eee;
} }
&.active { &.active {
color: #7f8fa4; color: $gl-icon-color;
} }
} }
} }
...@@ -81,14 +81,14 @@ header { ...@@ -81,14 +81,14 @@ header {
font-size: 19px; font-size: 19px;
line-height: $header-height; line-height: $header-height;
font-weight: normal; font-weight: normal;
color: #4c4e54; color: $gl-text-color;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
vertical-align: top; vertical-align: top;
white-space: nowrap; white-space: nowrap;
a { a {
color: #4c4e54; color: $gl-text-color;
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;
} }
...@@ -123,11 +123,11 @@ header { ...@@ -123,11 +123,11 @@ header {
} }
@mixin collapsed-header { @mixin collapsed-header {
margin-left: $sidebar_collapsed_width; margin-left: 40px;
} }
.header-collapsed { .header-collapsed {
margin-left: $sidebar_collapsed_width; margin-left: 40px;
@media (min-width: $screen-md-min) { @media (min-width: $screen-md-min) {
@include collapsed-header; @include collapsed-header;
......
...@@ -107,7 +107,7 @@ ...@@ -107,7 +107,7 @@
} }
.page-title { .page-title {
.note_created_ago, .new-issue-link { .note-created-ago, .new-issue-link {
display: none; display: none;
} }
} }
...@@ -116,7 +116,7 @@ ...@@ -116,7 +116,7 @@
display: none; display: none;
} }
aside:not(.right-sidebar){ aside:not(.right-sidebar) {
display: none; display: none;
} }
......
...@@ -56,6 +56,17 @@ ...@@ -56,6 +56,17 @@
} }
} }
.nav-search {
display: inline-block;
width: 50%;
padding: 11px 0;
/* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-sm-min) {
width: 100%;
}
}
.nav-links { .nav-links {
display: inline-block; display: inline-block;
width: 50%; width: 50%;
...@@ -100,6 +111,7 @@ ...@@ -100,6 +111,7 @@
> form { > form {
display: inline-block; display: inline-block;
margin-top: -1px;
} }
.icon-label { .icon-label {
...@@ -110,7 +122,7 @@ ...@@ -110,7 +122,7 @@
height: 34px; height: 34px;
display: inline-block; display: inline-block;
position: relative; position: relative;
top: 1px; top: 2px;
margin-right: $gl-padding-top; margin-right: $gl-padding-top;
/* Medium devices (desktops, 992px and up) */ /* Medium devices (desktops, 992px and up) */
......
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
padding: 10px 15px; padding: 10px 15px;
} }
.select2-drop{ .select2-drop {
color: #7f8fa4; color: #7f8fa4;
} }
......
...@@ -144,7 +144,7 @@ ...@@ -144,7 +144,7 @@
} }
a { a {
padding: 7px 15px; padding: 7px 12px;
font-size: $gl-font-size; font-size: $gl-font-size;
line-height: 24px; line-height: 24px;
color: $gray; color: $gray;
...@@ -169,10 +169,12 @@ ...@@ -169,10 +169,12 @@
} }
.count { .count {
float: right; &:before {
background: #eee; content: '(';
padding: 0 8px; }
@include border-radius(6px); &:after {
content: ')';
}
} }
&.back-link i { &.back-link i {
...@@ -191,6 +193,27 @@ ...@@ -191,6 +193,27 @@
} }
} }
.expand-nav a {
color: $gl-icon-color;
width: 60px;
position: fixed;
top: 0;
left: 0;
font-size: 20px;
background: #fff;
height: 59px;
text-align: center;
line-height: 59px;
border-bottom: 1px solid #eee;
transition-duration: .3s;
outline: none;
z-index: 100;
&:hover {
text-decoration: none;
}
}
.collapse-nav a { .collapse-nav a {
width: $sidebar_width; width: $sidebar_width;
position: fixed; position: fixed;
...@@ -210,55 +233,12 @@ ...@@ -210,55 +233,12 @@
} }
.page-sidebar-collapsed { .page-sidebar-collapsed {
padding-left: $sidebar_collapsed_width;
.sidebar-wrapper { .sidebar-wrapper {
width: $sidebar_collapsed_width; display: none;
.header-logo {
width: $sidebar_collapsed_width;
a {
padding-left: ($sidebar_collapsed_width - 36) / 2;
.gitlab-text-container {
display: none;
}
}
}
.nav-sidebar {
width: $sidebar_collapsed_width;
li {
width: auto;
a {
span {
display: none;
}
}
}
}
.collapse-nav a {
width: $sidebar_collapsed_width;
}
.sidebar-user {
padding-left: ($sidebar_collapsed_width - 36) / 2;
width: $sidebar_collapsed_width;
.username {
display: none;
}
}
} }
} }
.page-sidebar-expanded { .page-sidebar-expanded {
padding-left: $sidebar_collapsed_width;
@media (min-width: $screen-md-min) { @media (min-width: $screen-md-min) {
padding-left: $sidebar_width; padding-left: $sidebar_width;
} }
...@@ -309,3 +289,48 @@ ...@@ -309,3 +289,48 @@
padding-right: $sidebar_collapsed_width; padding-right: $sidebar_collapsed_width;
} }
} }
.complex-sidebar {
display: inline-block;
.nav-primary {
width: 61px;
float: left;
height: 100vh;
.nav-sidebar {
width: 60px;
li a {
width: 60px;
span {
display: none;
}
}
}
}
.nav-secondary {
$nav-secondary-width: 168px;
float: left;
width: $nav-secondary-width;
.nav-sidebar {
width: $nav-secondary-width;
li {
width: $nav-secondary-width;
a {
width: $nav-secondary-width;
i {
display: none;
}
}
}
}
}
}
...@@ -56,8 +56,8 @@ $component-active-bg: $brand-info; ...@@ -56,8 +56,8 @@ $component-active-bg: $brand-info;
//## //##
$input-color: $text-color; $input-color: $text-color;
$input-border: #e7e9ed; $input-border: $border-color;
$input-border-focus: #7f8fa4; $input-border-focus: $focus-border-color;
$legend-color: $text-color; $legend-color: $text-color;
......
...@@ -11,6 +11,7 @@ $gutter_inner_width: 258px; ...@@ -11,6 +11,7 @@ $gutter_inner_width: 258px;
* UI elements * UI elements
*/ */
$border-color: #efeff1; $border-color: #efeff1;
$focus-border-color: #3aabf0;
$table-border-color: #eef0f2; $table-border-color: #eef0f2;
$background-color: #faf9f9; $background-color: #faf9f9;
...@@ -26,6 +27,7 @@ $gl-text-orange: #d90; ...@@ -26,6 +27,7 @@ $gl-text-orange: #d90;
$gl-link-color: #3084bb; $gl-link-color: #3084bb;
$gl-dark-link-color: #333; $gl-dark-link-color: #333;
$gl-placeholder-color: #8f8f8f; $gl-placeholder-color: #8f8f8f;
$gl-icon-color: $gl-placeholder-color;
$gl-gray: $gl-text-color; $gl-gray: $gl-text-color;
$gl-header-color: $gl-title-color; $gl-header-color: $gl-title-color;
...@@ -178,7 +180,7 @@ $dropdown-divider-color: rgba(#000, .1); ...@@ -178,7 +180,7 @@ $dropdown-divider-color: rgba(#000, .1);
$dropdown-header-color: #959494; $dropdown-header-color: #959494;
$dropdown-title-btn-color: #bfbfbf; $dropdown-title-btn-color: #bfbfbf;
$dropdown-input-color: #555; $dropdown-input-color: #555;
$dropdown-input-focus-border: rgb(58, 171, 240); $dropdown-input-focus-border: $focus-border-color;
$dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, .4); $dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, .4);
$dropdown-loading-bg: rgba(#fff, .6); $dropdown-loading-bg: rgba(#fff, .6);
...@@ -207,3 +209,11 @@ $location-badge-bg: $gray-normal; ...@@ -207,3 +209,11 @@ $location-badge-bg: $gray-normal;
$location-badge-active-bg: #4f91f8; $location-badge-active-bg: #4f91f8;
$location-icon-color: #e7e9ed; $location-icon-color: #e7e9ed;
$location-icon-active-color: #807e7e; $location-icon-active-color: #807e7e;
/*
* Notes
*/
$notes-light-color: #8e8e8e;
$notes-action-color: #c3c3c3;
$notes-role-color: #8e8e8e;
$notes-role-border-color: #e4e4e4;
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
height: 300px; height: 300px;
overflow-y: scroll; overflow-y: scroll;
input.emoji-search{ input.emoji-search {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC"); background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC");
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: right 5px center; background-position: right 5px center;
......
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
} }
} }
.loading{ .loading {
font-size: 20px; font-size: 20px;
} }
......
.commit-title{ .commit-title {
display: block; display: block;
} }
.commit-author, .commit-committer{ .commit-author, .commit-committer {
display: block; display: block;
color: #999; color: #999;
font-weight: normal; font-weight: normal;
font-style: italic; font-style: italic;
} }
.commit-author strong, .commit-committer strong{ .commit-author strong, .commit-committer strong {
font-weight: bold; font-weight: bold;
font-style: normal; font-style: normal;
} }
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
color: $gl-text-red; color: $gl-text-red;
} }
} }
.edit-file{ .edit-file {
a { a {
color: $gl-text-color; color: $gl-text-color;
} }
......
.commits-compare-switch{ .commits-compare-switch {
@include btn-default; @include btn-default;
@include btn-white; @include btn-white;
background: image-url("switch_icon.png") no-repeat center center; background: image-url("switch_icon.png") no-repeat center center;
......
.file-editor { .file-editor {
#editor{ #editor {
border: none; border: none;
@include border-radius(0); @include border-radius(0);
height: 500px; height: 500px;
......
...@@ -43,10 +43,6 @@ ...@@ -43,10 +43,6 @@
.md { .md {
color: #7f8fa4; color: #7f8fa4;
font-size: $gl-font-size; font-size: $gl-font-size;
iframe.twitter-share-button {
vertical-align: bottom;
}
} }
pre { pre {
......
.ci-body { .ci-body {
.incorrect-syntax{ .incorrect-syntax {
font-size: 19px; font-size: 19px;
color: red; color: red;
} }
.correct-syntax{ .correct-syntax {
font-size: 19px; font-size: 19px;
color: #47a447; color: #47a447;
} }
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
} }
} }
.login-box{ .login-box {
background: #fafafa; background: #fafafa;
border-radius: 10px; border-radius: 10px;
box-shadow: 0 0 2px #ccc; box-shadow: 0 0 2px #ccc;
......
...@@ -22,7 +22,7 @@ ul.notes { ...@@ -22,7 +22,7 @@ ul.notes {
margin-left: 55px; margin-left: 55px;
} }
.note_created_ago, .note-updated-at { .note-created-ago, .note-updated-at {
white-space: nowrap; white-space: nowrap;
} }
...@@ -39,53 +39,6 @@ ul.notes { ...@@ -39,53 +39,6 @@ ul.notes {
} }
} }
.discussion-header,
.note-header {
@extend .cgray;
a:hover {
text-decoration: none;
}
.avatar {
float: left;
margin-right: 10px;
}
.discussion-last-update,
.note-last-update {
&:before {
content: "\00b7";
}
a {
color: $gl-gray;
&:hover {
text-decoration: underline;
}
}
}
.author {
color: #4c4e54;
margin-right: 3px;
&:hover {
color: $gl-link-color;
}
}
.author-username {
}
.note-role {
float: right;
margin-top: 1px;
border: 1px solid #bbb;
background-color: transparent;
color: $gl-gray;
}
}
.discussion-body { .discussion-body {
padding-top: 15px; padding-top: 15px;
} }
...@@ -198,40 +151,88 @@ ul.notes { ...@@ -198,40 +151,88 @@ ul.notes {
border-width: 1px 0; border-width: 1px 0;
padding-top: 0; padding-top: 0;
vertical-align: top; vertical-align: top;
&.parallel{ &.parallel {
border-width: 1px; border-width: 1px;
} }
} }
} }
} }
.discussion-header,
.note-header {
a {
color: inherit;
&:hover {
color: $gl-link-color;
text-decoration: none;
}
}
.author_link {
font-weight: 600;
}
}
.note-headline-light,
.discussion-headline-light {
color: $notes-light-color;
}
/** /**
* Actions for Discussions/Notes * Actions for Discussions/Notes
*/ */
.discussion, .discussion-actions,
.note { .note-actions {
.discussion-actions, float: right;
.note-actions { margin-left: 10px;
float: right; color: $notes-action-color;
margin-left: 10px; }
a { .note-action-button,
margin-left: 5px; .discussion-action-button {
color: $gl-gray; display: inline-block;
margin-left: 10px;
line-height: 24px;
i.fa { .fa {
font-size: 16px; position: relative;
line-height: 16px; top: 1px;
} font-size: 17px;
}
&:hover { .fa-trash-o {
@extend .cgray; top: 0;
&.danger { @extend .cred; } font-size: 16px;
}
}
} }
} }
.discussion-toggle-button {
line-height: 20px;
font-size: 13px;
.fa {
margin-right: 3px;
font-size: 10px;
line-height: 18px;
vertical-align: top;
}
}
.note-role {
position: relative;
top: -2px;
display: inline-block;
padding-left: 4px;
padding-right: 4px;
color: $notes-role-color;
font-size: 12px;
line-height: 20px;
border: 1px solid $notes-role-border-color;
border-radius: $border-radius-base;
}
.diff-file .note .note-actions { .diff-file .note .note-actions {
right: 0; right: 0;
top: 0; top: 0;
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
.navbar-nav { .navbar-nav {
li { li {
.badge.todos-pending-count { .badge.todos-pending-count {
background-color: #7f8fa4; background-color: $gl-icon-color;
margin-top: -5px; margin-top: -5px;
font-weight: normal; font-weight: normal;
} }
......
...@@ -52,7 +52,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -52,7 +52,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:require_two_factor_authentication, :require_two_factor_authentication,
:two_factor_grace_period, :two_factor_grace_period,
:gravatar_enabled, :gravatar_enabled,
:twitter_sharing_enabled,
:sign_in_text, :sign_in_text,
:help_page_text, :help_page_text,
:home_page_url, :home_page_url,
......
...@@ -224,14 +224,22 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -224,14 +224,22 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def ci_status def ci_status
ci_service = @merge_request.source_project.ci_service ci_commit = @merge_request.ci_commit
status = ci_service.commit_status(merge_request.last_commit.sha, merge_request.source_branch) if ci_commit
status = ci_commit.status
coverage = ci_commit.try(:coverage)
else
ci_service = @merge_request.source_project.ci_service
status = ci_service.commit_status(merge_request.last_commit.sha, merge_request.source_branch) if ci_service
if ci_service.respond_to?(:commit_coverage) if ci_service.respond_to?(:commit_coverage)
coverage = ci_service.commit_coverage(merge_request.last_commit.sha, merge_request.source_branch) coverage = ci_service.commit_coverage(merge_request.last_commit.sha, merge_request.source_branch)
end
end end
response = { response = {
title: merge_request.title,
sha: merge_request.last_commit_short_sha,
status: status, status: status,
coverage: coverage coverage: coverage
} }
......
...@@ -71,7 +71,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -71,7 +71,7 @@ class ProjectsController < Projects::ApplicationController
def remove_fork def remove_fork
return access_denied! unless can?(current_user, :remove_fork_project, @project) return access_denied! unless can?(current_user, :remove_fork_project, @project)
if @project.unlink_fork if ::Projects::UnlinkForkService.new(@project, current_user).execute
flash[:notice] = 'The fork relationship has been removed.' flash[:notice] = 'The fork relationship has been removed.'
end end
end end
......
...@@ -3,10 +3,6 @@ module ApplicationSettingsHelper ...@@ -3,10 +3,6 @@ module ApplicationSettingsHelper
current_application_settings.gravatar_enabled? current_application_settings.gravatar_enabled?
end end
def twitter_sharing_enabled?
current_application_settings.twitter_sharing_enabled?
end
def signup_enabled? def signup_enabled?
current_application_settings.signup_enabled? current_application_settings.signup_enabled?
end end
......
...@@ -216,7 +216,7 @@ module EventsHelper ...@@ -216,7 +216,7 @@ module EventsHelper
end end
def event_row_class(event) def event_row_class(event)
if event.body? || event.created_project? if event.body?
"event-block" "event-block"
else else
"event-inline" "event-inline"
......
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
# updated_at :datetime # updated_at :datetime
# home_page_url :string(255) # home_page_url :string(255)
# default_branch_protection :integer default(2) # default_branch_protection :integer default(2)
# twitter_sharing_enabled :boolean default(TRUE)
# restricted_visibility_levels :text # restricted_visibility_levels :text
# version_check_enabled :boolean default(TRUE) # version_check_enabled :boolean default(TRUE)
# max_attachment_size :integer default(10), not null # max_attachment_size :integer default(10), not null
...@@ -140,7 +139,6 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -140,7 +139,6 @@ class ApplicationSetting < ActiveRecord::Base
default_branch_protection: Settings.gitlab['default_branch_protection'], default_branch_protection: Settings.gitlab['default_branch_protection'],
signup_enabled: Settings.gitlab['signup_enabled'], signup_enabled: Settings.gitlab['signup_enabled'],
signin_enabled: Settings.gitlab['signin_enabled'], signin_enabled: Settings.gitlab['signin_enabled'],
twitter_sharing_enabled: Settings.gitlab['twitter_sharing_enabled'],
gravatar_enabled: Settings.gravatar['enabled'], gravatar_enabled: Settings.gravatar['enabled'],
sign_in_text: Settings.extra['sign_in_text'], sign_in_text: Settings.extra['sign_in_text'],
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'], restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
......
...@@ -74,14 +74,14 @@ class Commit ...@@ -74,14 +74,14 @@ class Commit
# #
# This pattern supports cross-project references. # This pattern supports cross-project references.
def self.reference_pattern def self.reference_pattern
%r{ @reference_pattern ||= %r{
(?:#{Project.reference_pattern}#{reference_prefix})? (?:#{Project.reference_pattern}#{reference_prefix})?
(?<commit>\h{7,40}) (?<commit>\h{7,40})
}x }x
end end
def self.link_reference_pattern def self.link_reference_pattern
super("commit", /(?<commit>\h{7,40})/) @link_reference_pattern ||= super("commit", /(?<commit>\h{7,40})/)
end end
def to_reference(from_project = nil) def to_reference(from_project = nil)
......
...@@ -43,14 +43,14 @@ class CommitRange ...@@ -43,14 +43,14 @@ class CommitRange
# #
# This pattern supports cross-project references. # This pattern supports cross-project references.
def self.reference_pattern def self.reference_pattern
%r{ @reference_pattern ||= %r{
(?:#{Project.reference_pattern}#{reference_prefix})? (?:#{Project.reference_pattern}#{reference_prefix})?
(?<commit_range>#{STRICT_PATTERN}) (?<commit_range>#{STRICT_PATTERN})
}x }x
end end
def self.link_reference_pattern def self.link_reference_pattern
super("compare", /(?<commit_range>#{PATTERN})/) @link_reference_pattern ||= super("compare", /(?<commit_range>#{PATTERN})/)
end end
# Initialize a CommitRange # Initialize a CommitRange
......
...@@ -19,6 +19,7 @@ module Issuable ...@@ -19,6 +19,7 @@ module Issuable
has_many :notes, as: :noteable, dependent: :destroy has_many :notes, as: :noteable, dependent: :destroy
has_many :label_links, as: :target, dependent: :destroy has_many :label_links, as: :target, dependent: :destroy
has_many :labels, through: :label_links has_many :labels, through: :label_links
has_many :todos, as: :target, dependent: :destroy
validates :author, presence: true validates :author, presence: true
validates :title, presence: true, length: { within: 0..255 } validates :title, presence: true, length: { within: 0..255 }
...@@ -41,7 +42,7 @@ module Issuable ...@@ -41,7 +42,7 @@ module Issuable
scope :join_project, -> { joins(:project) } scope :join_project, -> { joins(:project) }
scope :references_project, -> { references(:project) } scope :references_project, -> { references(:project) }
scope :non_archived, -> { join_project.merge(Project.non_archived.only(:where)) } scope :non_archived, -> { join_project.where(projects: { archived: false }) }
delegate :name, delegate :name,
:email, :email,
......
...@@ -31,7 +31,7 @@ class ExternalIssue ...@@ -31,7 +31,7 @@ class ExternalIssue
# Pattern used to extract `JIRA-123` issue references from text # Pattern used to extract `JIRA-123` issue references from text
def self.reference_pattern def self.reference_pattern
%r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)} @reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
end end
def to_reference(_from_project = nil) def to_reference(_from_project = nil)
......
...@@ -73,14 +73,14 @@ class Issue < ActiveRecord::Base ...@@ -73,14 +73,14 @@ class Issue < ActiveRecord::Base
# #
# This pattern supports cross-project references. # This pattern supports cross-project references.
def self.reference_pattern def self.reference_pattern
%r{ @reference_pattern ||= %r{
(#{Project.reference_pattern})? (#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}(?<issue>\d+) #{Regexp.escape(reference_prefix)}(?<issue>\d+)
}x }x
end end
def self.link_reference_pattern def self.link_reference_pattern
super("issues", /(?<issue>\d+)/) @link_reference_pattern ||= super("issues", /(?<issue>\d+)/)
end end
def to_reference(from_project = nil) def to_reference(from_project = nil)
......
...@@ -56,7 +56,7 @@ class Label < ActiveRecord::Base ...@@ -56,7 +56,7 @@ class Label < ActiveRecord::Base
# This pattern supports cross-project references. # This pattern supports cross-project references.
# #
def self.reference_pattern def self.reference_pattern
%r{ @reference_pattern ||= %r{
(#{Project.reference_pattern})? (#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)} #{Regexp.escape(reference_prefix)}
(?: (?:
......
...@@ -135,6 +135,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -135,6 +135,7 @@ class MergeRequest < ActiveRecord::Base
scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) } scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
scope :by_milestone, ->(milestone) { where(milestone_id: milestone) } scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
scope :of_projects, ->(ids) { where(target_project_id: ids) } scope :of_projects, ->(ids) { where(target_project_id: ids) }
scope :from_project, ->(project) { where(source_project_id: project.id) }
scope :merged, -> { with_state(:merged) } scope :merged, -> { with_state(:merged) }
scope :closed_and_merged, -> { with_states(:closed, :merged) } scope :closed_and_merged, -> { with_states(:closed, :merged) }
...@@ -149,14 +150,14 @@ class MergeRequest < ActiveRecord::Base ...@@ -149,14 +150,14 @@ class MergeRequest < ActiveRecord::Base
# #
# This pattern supports cross-project references. # This pattern supports cross-project references.
def self.reference_pattern def self.reference_pattern
%r{ @reference_pattern ||= %r{
(#{Project.reference_pattern})? (#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}(?<merge_request>\d+) #{Regexp.escape(reference_prefix)}(?<merge_request>\d+)
}x }x
end end
def self.link_reference_pattern def self.link_reference_pattern
super("merge_requests", /(?<merge_request>\d+)/) @link_reference_pattern ||= super("merge_requests", /(?<merge_request>\d+)/)
end end
# Returns all the merge requests from an ActiveRecord:Relation. # Returns all the merge requests from an ActiveRecord:Relation.
......
...@@ -79,7 +79,7 @@ class Milestone < ActiveRecord::Base ...@@ -79,7 +79,7 @@ class Milestone < ActiveRecord::Base
end end
def self.link_reference_pattern def self.link_reference_pattern
super("milestones", /(?<milestone>\d+)/) @link_reference_pattern ||= super("milestones", /(?<milestone>\d+)/)
end end
def self.upcoming def self.upcoming
...@@ -89,7 +89,7 @@ class Milestone < ActiveRecord::Base ...@@ -89,7 +89,7 @@ class Milestone < ActiveRecord::Base
def to_reference(from_project = nil) def to_reference(from_project = nil)
escaped_title = self.title.gsub("]", "\\]") escaped_title = self.title.gsub("]", "\\]")
h = Gitlab::Application.routes.url_helpers h = Gitlab::Routing.url_helpers
url = h.namespace_project_milestone_url(self.project.namespace, self.project, self) url = h.namespace_project_milestone_url(self.project.namespace, self.project, self)
"[#{escaped_title}](#{url})" "[#{escaped_title}](#{url})"
......
...@@ -311,7 +311,7 @@ class Note < ActiveRecord::Base ...@@ -311,7 +311,7 @@ class Note < ActiveRecord::Base
for_merge_request? && for_diff_line? for_merge_request? && for_diff_line?
end end
def for_project_snippet? def for_snippet?
noteable_type == "Snippet" noteable_type == "Snippet"
end end
......
...@@ -206,6 +206,8 @@ class Project < ActiveRecord::Base ...@@ -206,6 +206,8 @@ class Project < ActiveRecord::Base
mount_uploader :avatar, AvatarUploader mount_uploader :avatar, AvatarUploader
# Scopes # Scopes
default_scope { where(pending_delete: false) }
scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) } scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) }
scope :sorted_by_stars, -> { reorder('projects.star_count DESC') } scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }
scope :sorted_by_names, -> { joins(:namespace).reorder('namespaces.name ASC, projects.name ASC') } scope :sorted_by_names, -> { joins(:namespace).reorder('namespaces.name ASC, projects.name ASC') }
...@@ -469,7 +471,7 @@ class Project < ActiveRecord::Base ...@@ -469,7 +471,7 @@ class Project < ActiveRecord::Base
end end
def web_url def web_url
Gitlab::Application.routes.url_helpers.namespace_project_url(self.namespace, self) Gitlab::Routing.url_helpers.namespace_project_url(self.namespace, self)
end end
def web_url_without_protocol def web_url_without_protocol
...@@ -590,7 +592,7 @@ class Project < ActiveRecord::Base ...@@ -590,7 +592,7 @@ class Project < ActiveRecord::Base
if avatar.present? if avatar.present?
[gitlab_config.url, avatar.url].join [gitlab_config.url, avatar.url].join
elsif avatar_in_git elsif avatar_in_git
Gitlab::Application.routes.url_helpers.namespace_project_avatar_url(namespace, self) Gitlab::Routing.url_helpers.namespace_project_avatar_url(namespace, self)
end end
end end
...@@ -929,16 +931,6 @@ class Project < ActiveRecord::Base ...@@ -929,16 +931,6 @@ class Project < ActiveRecord::Base
self.builds_enabled = true self.builds_enabled = true
end end
def unlink_fork
if forked?
forked_from_project.lfs_objects.find_each do |lfs_object|
lfs_object.projects << self
end
forked_project_link.destroy
end
end
def any_runners?(&block) def any_runners?(&block)
if runners.active.any?(&block) if runners.active.any?(&block)
return true return true
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
# #
class GitlabIssueTrackerService < IssueTrackerService class GitlabIssueTrackerService < IssueTrackerService
include Gitlab::Application.routes.url_helpers include Gitlab::Routing.url_helpers
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
class JiraService < IssueTrackerService class JiraService < IssueTrackerService
include HTTParty include HTTParty
include Gitlab::Application.routes.url_helpers include Gitlab::Routing.url_helpers
DEFAULT_API_VERSION = 2 DEFAULT_API_VERSION = 2
......
...@@ -72,7 +72,7 @@ class Repository ...@@ -72,7 +72,7 @@ class Repository
return @has_visible_content unless @has_visible_content.nil? return @has_visible_content unless @has_visible_content.nil?
@has_visible_content = cache.fetch(:has_visible_content?) do @has_visible_content = cache.fetch(:has_visible_content?) do
raw_repository.branch_count > 0 branch_count > 0
end end
end end
...@@ -173,7 +173,7 @@ class Repository ...@@ -173,7 +173,7 @@ class Repository
end end
def branch_names def branch_names
cache.fetch(:branch_names) { raw_repository.branch_names } cache.fetch(:branch_names) { branches.map(&:name) }
end end
def tag_names def tag_names
...@@ -191,7 +191,7 @@ class Repository ...@@ -191,7 +191,7 @@ class Repository
end end
def branch_count def branch_count
@branch_count ||= cache.fetch(:branch_count) { raw_repository.branch_count } @branch_count ||= cache.fetch(:branch_count) { branches.size }
end end
def tag_count def tag_count
...@@ -239,7 +239,7 @@ class Repository ...@@ -239,7 +239,7 @@ class Repository
def expire_branches_cache def expire_branches_cache
cache.expire(:branch_names) cache.expire(:branch_names)
@branches = nil @local_branches = nil
end end
def expire_cache(branch_name = nil, revision = nil) def expire_cache(branch_name = nil, revision = nil)
...@@ -614,10 +614,14 @@ class Repository ...@@ -614,10 +614,14 @@ class Repository
refs_contains_sha('tag', sha) refs_contains_sha('tag', sha)
end end
def branches def local_branches
@branches ||= raw_repository.branches @local_branches ||= rugged.branches.each(:local).map do |branch|
Gitlab::Git::Branch.new(branch.name, branch.target)
end
end end
alias_method :branches, :local_branches
def tags def tags
@tags ||= raw_repository.tags @tags ||= raw_repository.tags
end end
...@@ -820,7 +824,7 @@ class Repository ...@@ -820,7 +824,7 @@ class Repository
end end
def fetch_ref(source_path, source_ref, target_ref) def fetch_ref(source_path, source_ref, target_ref)
args = %W(#{Gitlab.config.git.bin_path} fetch -f #{source_path} #{source_ref}:#{target_ref}) args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
Gitlab::Popen.popen(args, path_to_repo) Gitlab::Popen.popen(args, path_to_repo)
end end
......
...@@ -56,14 +56,14 @@ class Snippet < ActiveRecord::Base ...@@ -56,14 +56,14 @@ class Snippet < ActiveRecord::Base
# #
# This pattern supports cross-project references. # This pattern supports cross-project references.
def self.reference_pattern def self.reference_pattern
%r{ @reference_pattern ||= %r{
(#{Project.reference_pattern})? (#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}(?<snippet>\d+) #{Regexp.escape(reference_prefix)}(?<snippet>\d+)
}x }x
end end
def self.link_reference_pattern def self.link_reference_pattern
super("snippets", /(?<snippet>\d+)/) @link_reference_pattern ||= super("snippets", /(?<snippet>\d+)/)
end end
def to_reference(from_project = nil) def to_reference(from_project = nil)
......
...@@ -408,6 +408,8 @@ class User < ActiveRecord::Base ...@@ -408,6 +408,8 @@ class User < ActiveRecord::Base
end end
def owns_notification_email def owns_notification_email
return if self.temp_oauth_email?
self.errors.add(:notification_email, "is not an email you own") unless self.all_emails.include?(self.notification_email) self.errors.add(:notification_email, "is not an email you own") unless self.all_emails.include?(self.notification_email)
end end
......
...@@ -43,7 +43,7 @@ module Issues ...@@ -43,7 +43,7 @@ module Issues
def create_new_issue def create_new_issue
new_params = { id: nil, iid: nil, label_ids: [], milestone: nil, new_params = { id: nil, iid: nil, label_ids: [], milestone: nil,
project: @new_project, author: @old_issue.author, project: @new_project, author: @old_issue.author,
description: unfold_references(@old_issue.description) } description: rewrite_content(@old_issue.description) }
new_params = @old_issue.serializable_hash.merge(new_params) new_params = @old_issue.serializable_hash.merge(new_params)
CreateService.new(@new_project, @current_user, new_params).execute CreateService.new(@new_project, @current_user, new_params).execute
...@@ -53,7 +53,7 @@ module Issues ...@@ -53,7 +53,7 @@ module Issues
@old_issue.notes.find_each do |note| @old_issue.notes.find_each do |note|
new_note = note.dup new_note = note.dup
new_params = { project: @new_project, noteable: @new_issue, new_params = { project: @new_project, noteable: @new_issue,
note: unfold_references(new_note.note), note: rewrite_content(new_note.note),
created_at: note.created_at, created_at: note.created_at,
updated_at: note.updated_at } updated_at: note.updated_at }
...@@ -61,6 +61,18 @@ module Issues ...@@ -61,6 +61,18 @@ module Issues
end end
end end
def rewrite_content(content)
return unless content
rewriters = [Gitlab::Gfm::ReferenceRewriter,
Gitlab::Gfm::UploadsRewriter]
rewriters.inject(content) do |text, klass|
rewriter = klass.new(text, @old_project, @current_user)
rewriter.rewrite(@new_project)
end
end
def close_issue def close_issue
close_service = CloseService.new(@old_project, @current_user) close_service = CloseService.new(@old_project, @current_user)
close_service.execute(@old_issue, notifications: false, system_note: false) close_service.execute(@old_issue, notifications: false, system_note: false)
...@@ -78,20 +90,12 @@ module Issues ...@@ -78,20 +90,12 @@ module Issues
direction: :to) direction: :to)
end end
def unfold_references(content) def mark_as_moved
return unless content @old_issue.update(moved_to: @new_issue)
rewriter = Gitlab::Gfm::ReferenceRewriter.new(content, @old_project,
@current_user)
rewriter.rewrite(@new_project)
end end
def notify_participants def notify_participants
notification_service.issue_moved(@old_issue, @new_issue, @current_user) notification_service.issue_moved(@old_issue, @new_issue, @current_user)
end end
def mark_as_moved
@old_issue.update(moved_to: @new_issue)
end
end end
end end
module Projects
class UnlinkForkService < BaseService
def execute
return unless @project.forked?
@project.forked_from_project.lfs_objects.find_each do |lfs_object|
lfs_object.projects << @project
end
merge_requests = @project.forked_from_project.merge_requests.opened.from_project(@project)
merge_requests.each do |mr|
MergeRequests::CloseService.new(@project, @current_user).execute(mr)
end
@project.forked_project_link.destroy
end
end
end
...@@ -95,17 +95,19 @@ class SystemHooksService ...@@ -95,17 +95,19 @@ class SystemHooksService
end end
def project_member_data(model) def project_member_data(model)
project = model.project || Project.unscoped.find(model.source_id)
{ {
project_name: model.project.name, project_name: project.name,
project_path: model.project.path, project_path: project.path,
project_path_with_namespace: model.project.path_with_namespace, project_path_with_namespace: project.path_with_namespace,
project_id: model.project.id, project_id: project.id,
user_username: model.user.username, user_username: model.user.username,
user_name: model.user.name, user_name: model.user.name,
user_email: model.user.email, user_email: model.user.email,
user_id: model.user.id, user_id: model.user.id,
access_level: model.human_access, access_level: model.human_access,
project_visibility: Project.visibility_levels.key(model.project.visibility_level_field).downcase project_visibility: Project.visibility_levels.key(project.visibility_level_field).downcase
} }
end end
......
...@@ -224,7 +224,7 @@ class SystemNoteService ...@@ -224,7 +224,7 @@ class SystemNoteService
# #
# "Started branch `issue-branch-button-201`" # "Started branch `issue-branch-button-201`"
def self.new_issue_branch(issue, project, author, branch) def self.new_issue_branch(issue, project, author, branch)
h = Gitlab::Application.routes.url_helpers h = Gitlab::Routing.url_helpers
link = h.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch) link = h.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch)
body = "Started branch [`#{branch}`](#{link})" body = "Started branch [`#{branch}`](#{link})"
......
...@@ -123,7 +123,7 @@ class TodoService ...@@ -123,7 +123,7 @@ class TodoService
def handle_note(note, author) def handle_note(note, author)
# Skip system notes, and notes on project snippet # Skip system notes, and notes on project snippet
return if note.system? || note.for_project_snippet? return if note.system? || note.for_snippet?
project = note.project project = note.project
target = note.noteable target = note.noteable
...@@ -170,14 +170,30 @@ class TodoService ...@@ -170,14 +170,30 @@ class TodoService
end end
def filter_mentioned_users(project, target, author) def filter_mentioned_users(project, target, author)
mentioned_users = target.mentioned_users.select do |user| mentioned_users = target.mentioned_users
user.can?(:read_project, project) mentioned_users = reject_users_without_access(mentioned_users, project, target)
end
mentioned_users.delete(author) mentioned_users.delete(author)
mentioned_users.uniq mentioned_users.uniq
end end
def reject_users_without_access(users, project, target)
if target.is_a?(Note) && target.for_issue?
target = target.noteable
end
if target.is_a?(Issue)
select_users(users, :read_issue, target)
else
select_users(users, :read_project, project)
end
end
def select_users(users, ability, subject)
users.select do |user|
user.can?(ability.to_sym, subject)
end
end
def pending_todos(user, criteria = {}) def pending_todos(user, criteria = {})
valid_keys = [:project_id, :target_id, :target_type, :commit_id] valid_keys = [:project_id, :target_id, :target_type, :commit_id]
user.todos.pending.where(criteria.slice(*valid_keys)) user.todos.pending.where(criteria.slice(*valid_keys))
......
# encoding: utf-8 # encoding: utf-8
class FileUploader < CarrierWave::Uploader::Base class FileUploader < CarrierWave::Uploader::Base
include UploaderHelper include UploaderHelper
MARKDOWN_PATTERN = %r{\!?\[.*?\]\(/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?)\)}
storage :file storage :file
attr_accessor :project, :secret attr_accessor :project, :secret
def initialize(project, secret = self.class.generate_secret) def initialize(project, secret = nil)
@project = project @project = project
@secret = secret @secret = secret || self.class.generate_secret
end end
def base_dir def base_dir
...@@ -23,14 +24,14 @@ class FileUploader < CarrierWave::Uploader::Base ...@@ -23,14 +24,14 @@ class FileUploader < CarrierWave::Uploader::Base
File.join(base_dir, 'tmp', @project.path_with_namespace, @secret) File.join(base_dir, 'tmp', @project.path_with_namespace, @secret)
end end
def self.generate_secret
SecureRandom.hex
end
def secure_url def secure_url
File.join("/uploads", @secret, file.filename) File.join("/uploads", @secret, file.filename)
end end
def to_markdown
to_h[:markdown]
end
def to_h def to_h
filename = image? ? self.file.basename : self.file.filename filename = image? ? self.file.basename : self.file.filename
escaped_filename = filename.gsub("]", "\\]") escaped_filename = filename.gsub("]", "\\]")
...@@ -45,4 +46,8 @@ class FileUploader < CarrierWave::Uploader::Base ...@@ -45,4 +46,8 @@ class FileUploader < CarrierWave::Uploader::Base
markdown: markdown markdown: markdown
} }
end end
def self.generate_secret
SecureRandom.hex
end
end end
...@@ -76,13 +76,6 @@ ...@@ -76,13 +76,6 @@
= f.label :gravatar_enabled do = f.label :gravatar_enabled do
= f.check_box :gravatar_enabled = f.check_box :gravatar_enabled
Gravatar enabled Gravatar enabled
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :twitter_sharing_enabled do
= f.check_box :twitter_sharing_enabled, :'aria-describedby' => 'twitter_help_block'
Twitter enabled
%span.help-block#twitter_help_block Show users a button to share their newly created public or internal projects on twitter
.form-group .form-group
= f.label :default_projects_limit, class: 'control-label col-sm-2' = f.label :default_projects_limit, class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
%td %td
- if project - if project
= link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project), class: "monospace" = link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project)
%td %td
= link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace" = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace"
......
.admin-dashboard .admin-dashboard.prepend-top-default
.row .row
.col-md-4 .col-md-4
%h4 Statistics %h4 Statistics
......
- page_title "Deploy Keys" - page_title "Deploy Keys"
.panel.panel-default .panel.panel-default.prepend-top-default
.panel-heading .panel-heading
Public deploy keys (#{@deploy_keys.count}) Public deploy keys (#{@deploy_keys.count})
.controls .controls
......
- css_class = '' unless local_assigns[:css_class]
- css_class += ' no-description' if group.description.blank?
%li.group-row{ class: css_class }
.controls.hidden-xs
= link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: 'btn btn-grouped btn-sm'
= link_to 'Destroy', [:admin, group], data: {confirm: "REMOVE #{group.name}? Are you sure?"}, method: :delete, class: 'btn btn-grouped btn-sm btn-remove'
.stats
%span
= icon('bookmark')
= number_with_delimiter(group.projects.count)
%span
= icon('users')
= number_with_delimiter(group.users.count)
%span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)}
= visibility_level_icon(group.visibility_level, fw: false)
= image_tag group_icon(group), class: 'avatar s40 hidden-xs'
.title
= link_to [:admin, group], class: 'group-name' do
= group.name
- if group.description.present?
.description
= markdown(group.description, pipeline: :description)
- page_title "Groups" - page_title "Groups"
%h3.page-title %h3.page-title
Groups (#{number_with_delimiter(@groups.total_count)}) Groups (#{number_with_delimiter(@groups.total_count)})
= link_to 'New Group', new_admin_group_path, class: "btn btn-new pull-right"
%p.light %p.light
Group allows you to keep projects organized. Group allows you to keep projects organized.
Use groups for uniting related projects. Use groups for uniting related projects.
%hr .top-area
= form_tag admin_groups_path, method: :get, class: 'form-inline' do .nav-search
= hidden_field_tag :sort, @sort = form_tag admin_groups_path, method: :get, class: 'form-inline' do
.form-group = hidden_field_tag :sort, @sort
= text_field_tag :name, params[:name], class: "form-control" = text_field_tag :name, params[:name], class: "form-control"
= button_tag "Search", class: "btn submit btn-primary" = button_tag "Search", class: "btn submit btn-primary"
.pull-right .nav-controls
.dropdown.inline .dropdown.inline
%a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
%span.light %span.light
...@@ -33,34 +32,10 @@ ...@@ -33,34 +32,10 @@
= sort_title_recently_updated = sort_title_recently_updated
= link_to admin_groups_path(sort: sort_value_oldest_updated) do = link_to admin_groups_path(sort: sort_value_oldest_updated) do
= sort_title_oldest_updated = sort_title_oldest_updated
= link_to 'New Group', new_admin_group_path, class: "btn btn-new"
%hr %ul.content-list
%ul.bordered-list
- @groups.each do |group| - @groups.each do |group|
%li = render 'group', group: group
.clearfix
.pull-right.prepend-top-10
= link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-sm"
= link_to 'Destroy', [:admin, group], data: {confirm: "REMOVE #{group.name}? Are you sure?"}, method: :delete, class: "btn btn-sm btn-remove"
%h4
= link_to [:admin, group] do
%span{ class: visibility_level_color(group.visibility_level) }
= visibility_level_icon(group.visibility_level)
%i.fa.fa-folder
= group.name
&rarr;
%span.monospace
%strong #{group.path}/
.clearfix
%p
= truncate group.description, length: 150
.clearfix
%p.light
#{pluralize(group.members.size, 'member')}, #{pluralize(group.projects.count, 'project')}
= paginate @groups, theme: "gitlab" = paginate @groups, theme: "gitlab"
- page_title "Labels" - page_title "Labels"
= link_to new_admin_label_path, class: "pull-right btn btn-nr btn-new" do
New label %div
%h3.page-title = link_to new_admin_label_path, class: "pull-right btn btn-nr btn-new" do
Labels New label
%h3.page-title
Labels
%hr %hr
.labels .labels
...@@ -13,4 +15,4 @@ ...@@ -13,4 +15,4 @@
- else - else
.light-well .light-well
.nothing-here-block There are no labels yet .nothing-here-block There are no labels yet
%p.lead %p.lead.prepend-top-default
%span %span
To register a new runner you should enter the following registration token. To register a new runner you should enter the following registration token.
With this token the runner will request a unique runner token and use that for future communication. With this token the runner will request a unique runner token and use that for future communication.
......
...@@ -10,7 +10,10 @@ ...@@ -10,7 +10,10 @@
(removed) (removed)
%span.todo-label %span.todo-label
= todo_action_name(todo) = todo_action_name(todo)
= todo_target_link(todo) - if todo.target
= todo_target_link(todo)
- else
(removed)
&middot; #{time_ago_with_tooltip(todo.created_at)} &middot; #{time_ago_with_tooltip(todo.created_at)}
......
...@@ -7,21 +7,3 @@ ...@@ -7,21 +7,3 @@
= link_to_project event.project = link_to_project event.project
- else - else
= event.project_name = event.project_name
- if !event.project.private? && twitter_sharing_enabled?
.event-body{"data-user-is" => event.author_id}
.event-note
.md
%p
Congratulations! Why not share your accomplishment with the world?
%a.twitter-share-button{ |
href: "https://twitter.com/share", |
"data-url" => event.project.web_url, |
"data-text" => "I just #{event.action_name} a new project on GitLab! GitLab is version control on your server.", |
"data-size" => "medium", |
"data-related" => "gitlab", |
"data-hashtags" => "gitlab", |
"data-count" => "none"}
Tweet
%script{src: "//platform.twitter.com/widgets.js"}
- if nav_menu_collapsed?
= link_to icon('angle-right'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
- else
= link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" } .page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
= render "layouts/broadcast" = render "layouts/broadcast"
.expand-nav
= link_to icon('bars'), '#', class: 'toggle-nav-collapse', title: "Open sidebar"
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class } .sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo .header-logo
%a#logo %a#logo
...@@ -8,15 +10,19 @@ ...@@ -8,15 +10,19 @@
.gitlab-text-container .gitlab-text-container
%h3 GitLab %h3 GitLab
- if defined?(sidebar) && sidebar - primary_sidebar = current_user ? 'dashboard' : 'explore'
= render "layouts/nav/#{sidebar}"
- elsif current_user - if defined?(sidebar) && sidebar && sidebar != primary_sidebar
= render 'layouts/nav/dashboard' .complex-sidebar
.nav-primary
= render "layouts/nav/#{primary_sidebar}"
.nav-secondary
= render "layouts/nav/#{sidebar}"
- else - else
= render 'layouts/nav/explore' = render "layouts/nav/#{primary_sidebar}"
.collapse-nav .collapse-nav
= render partial: 'layouts/collapse_button' = link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Hide sidebar"
- if current_user - if current_user
= link_to current_user, class: 'sidebar-user', title: "Profile" do = link_to current_user, class: 'sidebar-user', title: "Profile" do
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36' = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
......
...@@ -95,7 +95,7 @@ ...@@ -95,7 +95,7 @@
Spam Logs Spam Logs
%span.count= number_with_delimiter(SpamLog.count(:all)) %span.count= number_with_delimiter(SpamLog.count(:all))
= nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do = nav_link(controller: :application_settings) do
= link_to admin_application_settings_path, title: 'Settings' do = link_to admin_application_settings_path, title: 'Settings' do
= icon('cogs fw') = icon('cogs fw')
%span %span
......
...@@ -15,12 +15,12 @@ ...@@ -15,12 +15,12 @@
= icon('dashboard fw') = icon('dashboard fw')
%span %span
Activity Activity
= nav_link(controller: :groups) do = nav_link(path: ['dashboard/groups#index', 'explore/groups#index']) do
= link_to dashboard_groups_path, title: 'Groups' do = link_to dashboard_groups_path, title: 'Groups' do
= icon('group fw') = icon('group fw')
%span %span
Groups Groups
= nav_link(controller: :milestones) do = nav_link(path: 'dashboard#milestones') do
= link_to dashboard_milestones_path, title: 'Milestones' do = link_to dashboard_milestones_path, title: 'Milestones' do
= icon('clock-o fw') = icon('clock-o fw')
%span %span
...@@ -48,7 +48,6 @@ ...@@ -48,7 +48,6 @@
%span %span
Help Help
%li.separate-item
= nav_link(controller: :profile) do = nav_link(controller: :profile) do
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do = link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
= icon('user fw') = icon('user fw')
......
%ul.nav.nav-sidebar %ul.nav.nav-sidebar
= nav_link do
= link_to root_path, title: 'Go to dashboard', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to dashboard
%li.separate-item
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do = nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: 'Home' do = link_to group_path(@group), title: 'Home' do
= icon('group fw') = icon('group fw')
...@@ -42,7 +34,7 @@ ...@@ -42,7 +34,7 @@
%span %span
Members Members
- if can?(current_user, :admin_group, @group) - if can?(current_user, :admin_group, @group)
= nav_link(html_options: { class: "separate-item" }) do = nav_link do
= link_to edit_group_path(@group), title: 'Settings' do = link_to edit_group_path(@group), title: 'Settings' do
= icon ('cogs fw') = icon ('cogs fw')
%span %span
......
%ul.nav.nav-sidebar %ul.nav.nav-sidebar
= nav_link do
= link_to root_path, title: 'Go to dashboard', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to dashboard
%li.separate-item
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do = link_to profile_path, title: 'Profile Settings' do
= icon('user fw') = icon('user fw')
......
%ul.nav.nav-sidebar %ul.nav.nav-sidebar
- if @project.group
= nav_link do
= link_to group_path(@project.group), title: 'Go to group', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to group
- else
= nav_link do
= link_to root_path, title: 'Go to dashboard', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to dashboard
%li.separate-item
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do = nav_link(path: 'projects#show', html_options: {class: 'home'}) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
= icon('bookmark fw') = icon('bookmark fw')
...@@ -113,7 +98,7 @@ ...@@ -113,7 +98,7 @@
Snippets Snippets
- if project_nav_tab? :settings - if project_nav_tab? :settings
= nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do = nav_link(html_options: {class: "#{project_tab_class}"}) do
= link_to edit_project_path(@project), title: 'Settings' do = link_to edit_project_path(@project), title: 'Settings' do
= icon('cogs fw') = icon('cogs fw')
%span %span
......
...@@ -7,8 +7,6 @@ ...@@ -7,8 +7,6 @@
%p %p
Increase your account's security by enabling two-factor authentication (2FA). Increase your account's security by enabling two-factor authentication (2FA).
.col-lg-9 .col-lg-9
%p
Status: #{current_user.two_factor_enabled? ? 'enabled' : 'disabled'}
%p %p
Download the Google Authenticator application from App Store for iOS or Google Play for Android and scan this code. Download the Google Authenticator application from App Store for iOS or Google Play for Android and scan this code.
More information is available in the #{link_to('documentation', help_page_path('profile', 'two_factor_authentication'))}. More information is available in the #{link_to('documentation', help_page_path('profile', 'two_factor_authentication'))}.
......
...@@ -3,25 +3,32 @@ ...@@ -3,25 +3,32 @@
%a.btn.dropdown-toggle{href: '#', "data-toggle" => "dropdown"} %a.btn.dropdown-toggle{href: '#', "data-toggle" => "dropdown"}
= icon('plus') = icon('plus')
%ul.dropdown-menu.dropdown-menu-right.project-home-dropdown %ul.dropdown-menu.dropdown-menu-right.project-home-dropdown
- if can?(current_user, :create_issue, @project) - can_create_issue = can?(current_user, :create_issue, @project)
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- can_create_snippet = can?(current_user, :create_snippet, @project)
- if can_create_issue
%li %li
= link_to url_for_new_issue(@project, only_path: true) do = link_to url_for_new_issue(@project, only_path: true) do
= icon('exclamation-circle fw') = icon('exclamation-circle fw')
New issue New issue
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- if merge_project - if merge_project
%li %li
= link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project) do = link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project) do
= icon('tasks fw') = icon('tasks fw')
New merge request New merge request
- if can?(current_user, :create_snippet, @project)
- if can_create_snippet
%li %li
= link_to new_namespace_project_snippet_path(@project.namespace, @project) do = link_to new_namespace_project_snippet_path(@project.namespace, @project) do
= icon('file-text-o fw') = icon('file-text-o fw')
New snippet New snippet
- if can?(current_user, :push_code, @project) - if can_create_issue || merge_project || can_create_snippet
%li.divider %li.divider
- if can?(current_user, :push_code, @project)
%li %li
= link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do = link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do
= icon('file fw') = icon('file fw')
...@@ -35,13 +42,11 @@ ...@@ -35,13 +42,11 @@
= icon('tags fw') = icon('tags fw')
New tag New tag
- elsif current_user && current_user.already_forked?(@project) - elsif current_user && current_user.already_forked?(@project)
%li.divider
%li %li
= link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do = link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do
= icon('file fw') = icon('file fw')
New file New file
- elsif can?(current_user, :fork_project, @project) - elsif can?(current_user, :fork_project, @project)
%li.divider
%li %li
- continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master'), - continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master'),
notice: edit_in_new_fork_notice, notice: edit_in_new_fork_notice,
......
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
%td %td
= build.name = build.name
.pull-right .label-container
- if build.tags.any? - if build.tags.any?
- build.tags.each do |tag| - build.tags.each do |tag|
%span.label.label-primary %span.label.label-primary
......
- diff = diff_file.diff - diff = diff_file.diff
- file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path)) - file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path))
- old_commit_id = diff_refs.first.id // diff_refs will be nil for orphaned commits (e.g. first commit in repo)
- old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(old_commit_id, diff.old_path)) - if diff_refs
- old_commit_id = diff_refs.first.id
- old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(old_commit_id, diff.old_path))
- if diff.renamed_file || diff.new_file || diff.deleted_file - if diff.renamed_file || diff.new_file || diff.deleted_file
.image .image
%span.wrap %span.wrap
......
- if @ci_commit - if @ci_commit
.mr-widget-heading .mr-widget-heading
.ci_widget{class: "ci-#{@ci_commit.status}"} - %w[success skipped canceled failed running pending].each do |status|
= ci_status_icon(@ci_commit) .ci_widget{ class: "ci-#{status}", style: ("display:none" unless @ci_commit.status == status) }
%span = ci_icon_for_status(status)
Build %span
= ci_status_label(@ci_commit) CI build
for = ci_label_for_status(status)
= succeed "." do for
= link_to @ci_commit.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @ci_commit.sha), class: "monospace" - commit = @merge_request.last_commit
%span.ci-coverage = succeed "." do
= link_to "View details", builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "js-show-tab", data: {action: 'builds'} = link_to @ci_commit.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @ci_commit.sha), class: "monospace"
%span.ci-coverage
= link_to "View details", builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "js-show-tab", data: {action: 'builds'}
- elsif @merge_request.has_ci? - elsif @merge_request.has_ci?
- # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX - # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX
...@@ -43,5 +45,5 @@ ...@@ -43,5 +45,5 @@
:javascript :javascript
$(function() { $(function() {
merge_request_widget.getCiStatus(); merge_request_widget.getCIStatus(false);
}); });
...@@ -9,12 +9,17 @@ ...@@ -9,12 +9,17 @@
:javascript :javascript
var merge_request_widget; var merge_request_widget;
var opts = {
merge_request_widget = new MergeRequestWidget({ merge_check_url: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
url_to_automerge_check: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
check_enable: #{@merge_request.unchecked? ? "true" : "false"}, check_enable: #{@merge_request.unchecked? ? "true" : "false"},
url_to_ci_check: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
gitlab_icon: "#{asset_path 'gitlab_logo.png'}",
ci_status: "",
ci_message: "Build {{status}} for \"{{title}}\"",
ci_enable: #{@project.ci_service ? "true" : "false"}, ci_enable: #{@project.ci_service ? "true" : "false"},
current_status: "#{@merge_request.gitlab_merge_status}", builds_path: "#{builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}"
}); };
if(typeof merge_request_widget === 'undefined') {
merge_request_widget = new MergeRequestWidget(opts);
}
...@@ -5,28 +5,21 @@ ...@@ -5,28 +5,21 @@
= image_tag avatar_icon(note.author), alt: '', class: 'avatar s40' = image_tag avatar_icon(note.author), alt: '', class: 'avatar s40'
.timeline-content .timeline-content
.note-header .note-header
= link_to_member(note.project, note.author, avatar: false)
.inline.note-headline-light
= "#{note.author.to_reference} commented"
%a{ href: "##{dom_id(note)}" }
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
- if note_editable?(note) - if note_editable?(note)
.note-actions .note-actions
= link_to '#', title: 'Edit comment', class: 'js-note-edit' do - access = note.project.team.human_max_access(note.author.id)
- if access
%span.note-role
= access
= link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
= icon('pencil-square-o') = icon('pencil-square-o')
= link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do
= link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'js-note-delete danger' do
= icon('trash-o') = icon('trash-o')
- unless note.system
- access = note.project.team.human_max_access(note.author.id)
- if access
%span.note-role.label
= access
= link_to_member(note.project, note.author, avatar: false)
%span.author-username
= '@' + note.author.username
%span.note-last-update
%a{name: dom_id(note), href: "##{dom_id(note)}", title: 'Link here'}
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note_created_ago')
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''} .note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
.note-text .note-text
= preserve do = preserve do
......
- note = discussion_notes.first - note = discussion_notes.first
.discussion.js-toggle-container{ class: note.discussion_id } .discussion.js-toggle-container{ class: note.discussion_id }
.discussion-header .discussion-header
= link_to_member(@project, note.author, avatar: false)
.inline.discussion-headline-light
= "#{note.author.to_reference} started a discussion"
= link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do
on the diff
.discussion-actions .discussion-actions
= link_to "#", class: "js-toggle-button" do = link_to "#", class: "discussion-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-up %i.fa.fa-chevron-up
Show/hide discussion Show/hide discussion
%div
= link_to_member(@project, note.author, avatar: false)
started a discussion
= link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do
%strong on the diff
.last-update.hide.js-toggle-content .last-update.hide.js-toggle-content
- last_note = discussion_notes.last - last_note = discussion_notes.last
last updated by last updated by
= link_to_member(@project, last_note.author, avatar: false) = link_to_member(@project, last_note.author, avatar: false)
#{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
%span.discussion-last-update
#{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content .discussion-body.js-toggle-content
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
...@@ -3,21 +3,20 @@ ...@@ -3,21 +3,20 @@
- commit_description = commit ? 'commit' : 'a deleted commit' - commit_description = commit ? 'commit' : 'a deleted commit'
.discussion.js-toggle-container{ class: note.discussion_id } .discussion.js-toggle-container{ class: note.discussion_id }
.discussion-header .discussion-header
= link_to_member(@project, note.author, avatar: false)
.inline.discussion-headline-light
= "#{note.author.to_reference} started a discussion on #{commit_description}"
- if commit
= link_to(commit.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable), class: 'monospace')
.discussion-actions .discussion-actions
= link_to "#", class: "js-toggle-button" do = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-up %i.fa.fa-chevron-up
Show/hide discussion Show/hide discussion
%div
= link_to_member(@project, note.author, avatar: false)
started a discussion on #{commit_description}
- if commit
= link_to(commit.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable), class: 'monospace')
.last-update.hide.js-toggle-content .last-update.hide.js-toggle-content
- last_note = discussion_notes.last - last_note = discussion_notes.last
last updated by last updated by
= link_to_member(@project, last_note.author, avatar: false) = link_to_member(@project, last_note.author, avatar: false)
%span.discussion-last-update #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
#{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content .discussion-body.js-toggle-content
- if note.for_diff_line? - if note.for_diff_line?
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
......
- note = discussion_notes.first - note = discussion_notes.first
.discussion.js-toggle-container{ class: note.discussion_id } .discussion.js-toggle-container{ class: note.discussion_id }
.discussion-header .discussion-header
= link_to_member(@project, note.author, avatar: false)
.inline.discussion-headline-light
= "#{note.author.to_reference} started a discussion"
on the outdated diff
.discussion-actions .discussion-actions
= link_to "#", class: "js-toggle-button" do = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-down %i.fa.fa-chevron-down
Show/hide discussion Show/hide discussion
%div .last-update.hide.js-toggle-content
= link_to_member(@project, note.author, avatar: false)
started a discussion on the
%strong outdated diff
%div
- last_note = discussion_notes.last - last_note = discussion_notes.last
last updated by last updated by
= link_to_member(@project, last_note.author, avatar: false) = link_to_member(@project, last_note.author, avatar: false)
%span.discussion-last-update #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
#{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content.hide .discussion-body.js-toggle-content.hide
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
- project = note.project - project = note.project
- note_url = Gitlab::UrlBuilder.new(:note).build(note.id)
- noteable_identifier = note.noteable.try(:iid) || note.noteable.id
.search-result-row .search-result-row
%h5.note-search-caption.str-truncated %h5.note-search-caption.str-truncated
%i.fa.fa-comment %i.fa.fa-comment
= link_to_member(project, note.author, avatar: false) = link_to_member(project, note.author, avatar: false)
commented on commented on
= link_to project.name_with_namespace, project
&middot;
- if note.for_commit? - if note.for_commit?
= link_to project do = link_to "Commit #{truncate_sha(note.commit_id)}", note_url
= project.name_with_namespace
&middot;
= link_to namespace_project_commit_path(project.namespace, project, note.commit_id, anchor: dom_id(note)) do
Commit #{truncate_sha(note.commit_id)}
- else - else
= link_to project do %span #{note.noteable_type.titleize} ##{noteable_identifier}
= project.name_with_namespace
&middot;
%span #{note.noteable_type.titleize} ##{note.noteable.iid}
&middot; &middot;
= link_to [project.namespace.becomes(Namespace), project, note.noteable, anchor: dom_id(note)] do = link_to note.noteable.title, note_url
= note.noteable.title
.note-search-result .note-search-result
.term .term
......
...@@ -7,13 +7,13 @@ ...@@ -7,13 +7,13 @@
class: "check_all_issues left" class: "check_all_issues left"
.issues-other-filters .issues-other-filters
.filter-item.inline .filter-item.inline
- if params[:author_id] - if params[:author_id].present?
= hidden_field_tag(:author_id, params[:author_id]) = hidden_field_tag(:author_id, params[:author_id])
= dropdown_tag(user_dropdown_label(params[:author_id], "Author"), options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit", = dropdown_tag(user_dropdown_label(params[:author_id], "Author"), options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit",
placeholder: "Search authors", data: { any_user: "Any Author", first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: "author_id", default_label: "Author" } }) placeholder: "Search authors", data: { any_user: "Any Author", first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: "author_id", default_label: "Author" } })
.filter-item.inline .filter-item.inline
- if params[:assignee_id] - if params[:assignee_id].present?
= hidden_field_tag(:assignee_id, params[:assignee_id]) = hidden_field_tag(:assignee_id, params[:assignee_id])
= dropdown_tag(user_dropdown_label(params[:assignee_id], "Assignee"), options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", = dropdown_tag(user_dropdown_label(params[:assignee_id], "Assignee"), options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } }) placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } })
......
- if params[:label_name] - if params[:label_name].present?
= hidden_field_tag(:label_name, params[:label_name]) = hidden_field_tag(:label_name, params[:label_name])
.dropdown .dropdown
%button.dropdown-menu-toggle.js-label-select.js-filter-submit{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}} %button.dropdown-menu-toggle.js-label-select.js-filter-submit{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}}
......
- if params[:milestone_title] - if params[:milestone_title].present?
= hidden_field_tag(:milestone_title, params[:milestone_title]) = hidden_field_tag(:milestone_title, params[:milestone_title])
= dropdown_tag(milestone_dropdown_label(params[:milestone_title]), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable", = dropdown_tag(milestone_dropdown_label(params[:milestone_title]), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable",
placeholder: "Search milestones", footer_content: @project.present?, data: { show_no: true, show_any: true, show_upcoming: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: @project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do placeholder: "Search milestones", footer_content: @project.present?, data: { show_no: true, show_any: true, show_upcoming: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: @project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do
......
...@@ -87,7 +87,7 @@ ...@@ -87,7 +87,7 @@
%div{ class: container_class } %div{ class: container_class }
.tab-content .tab-content
#activity.tab-pane #activity.tab-pane
.gray-content-block.white.second-block .gray-content-block.calender-block.white.second-block.hidden-xs
%div{ class: container_class } %div{ class: container_class }
.user-calendar{data: {href: user_calendar_path}} .user-calendar{data: {href: user_calendar_path}}
%h4.center.light %h4.center.light
......
...@@ -15,12 +15,14 @@ ...@@ -15,12 +15,14 @@
- if current_user - if current_user
:javascript :javascript
var get_emojis_url = "#{emojis_path}";
var post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}"; var post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}";
var noteable_type = "#{votable.class.name.underscore}"; var noteable_type = "#{votable.class.name.underscore}";
var noteable_id = "#{votable.id}"; var noteable_id = "#{votable.id}";
var aliases = #{AwardEmoji.aliases.to_json}; var aliases = #{AwardEmoji.aliases.to_json};
window.awards_handler = new AwardsHandler( window.awards_handler = new AwardsHandler(
get_emojis_url,
post_emoji_url, post_emoji_url,
noteable_type, noteable_type,
noteable_id, noteable_id,
......
...@@ -5,7 +5,7 @@ class ProjectDestroyWorker ...@@ -5,7 +5,7 @@ class ProjectDestroyWorker
def perform(project_id, user_id, params) def perform(project_id, user_id, params)
begin begin
project = Project.find(project_id) project = Project.unscoped.find(project_id)
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
return return
end end
......
...@@ -174,7 +174,6 @@ end ...@@ -174,7 +174,6 @@ end
Settings.gitlab['time_zone'] ||= nil Settings.gitlab['time_zone'] ||= nil
Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled'].nil? Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled'].nil?
Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil? Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil?
Settings.gitlab['twitter_sharing_enabled'] ||= true if Settings.gitlab['twitter_sharing_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], []) Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil? Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil? Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil?
......
class RemoveTodosForDeletedIssues < ActiveRecord::Migration
def up
execute <<-SQL
DELETE FROM todos
WHERE todos.target_type = 'Issue'
AND NOT EXISTS (
SELECT *
FROM issues
WHERE issues.id = todos.target_id
AND issues.deleted_at IS NULL
)
SQL
end
def down
end
end
class AddIndexOnPendingDeleteProjects < ActiveRecord::Migration
def change
add_index :projects, :pending_delete
end
end
class RemoveTodosForDeletedMergeRequests < ActiveRecord::Migration
def up
execute <<-SQL
DELETE FROM todos
WHERE todos.target_type = 'MergeRequest'
AND NOT EXISTS (
SELECT *
FROM merge_requests
WHERE merge_requests.id = todos.target_id
AND merge_requests.deleted_at IS NULL
)
SQL
end
def down
end
end
class RemoveTwitterSharingEnabledFromApplicationSettings < ActiveRecord::Migration
def change
remove_column :application_settings, :twitter_sharing_enabled, :boolean
end
end
...@@ -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: 20160320204112) do ActiveRecord::Schema.define(version: 20160331133914) 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"
...@@ -418,7 +418,7 @@ ActiveRecord::Schema.define(version: 20160320204112) do ...@@ -418,7 +418,7 @@ ActiveRecord::Schema.define(version: 20160320204112) do
t.integer "iid" t.integer "iid"
t.integer "updated_by_id" t.integer "updated_by_id"
t.integer "moved_to_id" t.integer "moved_to_id"
t.boolean "confidential", default: false t.boolean "confidential", default: false
t.datetime "deleted_at" t.datetime "deleted_at"
end end
...@@ -745,6 +745,7 @@ ActiveRecord::Schema.define(version: 20160320204112) do ...@@ -745,6 +745,7 @@ ActiveRecord::Schema.define(version: 20160320204112) do
add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree
add_index "projects", ["path"], name: "index_projects_on_path", using: :btree add_index "projects", ["path"], name: "index_projects_on_path", using: :btree
add_index "projects", ["path"], name: "index_projects_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"} add_index "projects", ["path"], name: "index_projects_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"}
add_index "projects", ["pending_delete"], name: "index_projects_on_pending_delete", using: :btree
add_index "projects", ["runners_token"], name: "index_projects_on_runners_token", using: :btree add_index "projects", ["runners_token"], name: "index_projects_on_runners_token", using: :btree
add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree
add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree
......
...@@ -491,6 +491,172 @@ Parameters: ...@@ -491,6 +491,172 @@ Parameters:
- `id` (required) - The ID of the project to be forked - `id` (required) - The ID of the project to be forked
### Archive a project
Archives the project if the user is either admin or the project owner of this project. This action is
idempotent, thus archiving an already archived project will not change the project.
Status code 201 with the project as body is given when successful, in case the user doesn't
have the proper access rights, code 403 is returned. Status 404 is returned if the project
doesn't exist, or is hidden to the user.
```
POST /projects/:id/archive
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project |
```bash
curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/archive"
```
Example response:
```json
{
"id": 3,
"description": null,
"default_branch": "master",
"public": false,
"visibility_level": 0,
"ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git",
"http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git",
"web_url": "http://example.com/diaspora/diaspora-project-site",
"tag_list": [
"example",
"disapora project"
],
"owner": {
"id": 3,
"name": "Diaspora",
"created_at": "2013-09-30T13: 46: 02Z"
},
"name": "Diaspora Project Site",
"name_with_namespace": "Diaspora / Diaspora Project Site",
"path": "diaspora-project-site",
"path_with_namespace": "diaspora/diaspora-project-site",
"issues_enabled": true,
"open_issues_count": 1,
"merge_requests_enabled": true,
"builds_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
"created_at": "2013-09-30T13: 46: 02Z",
"last_activity_at": "2013-09-30T13: 46: 02Z",
"creator_id": 3,
"namespace": {
"created_at": "2013-09-30T13: 46: 02Z",
"description": "",
"id": 3,
"name": "Diaspora",
"owner_id": 1,
"path": "diaspora",
"updated_at": "2013-09-30T13: 46: 02Z"
},
"permissions": {
"project_access": {
"access_level": 10,
"notification_level": 3
},
"group_access": {
"access_level": 50,
"notification_level": 3
}
},
"archived": true,
"avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png",
"shared_runners_enabled": true,
"forks_count": 0,
"star_count": 0,
"runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b"
}
```
### Unarchive a project
Unarchives the project if the user is either admin or the project owner of this project. This action is
idempotent, thus unarchiving an non-archived project will not change the project.
Status code 201 with the project as body is given when successful, in case the user doesn't
have the proper access rights, code 403 is returned. Status 404 is returned if the project
doesn't exist, or is hidden to the user.
```
POST /projects/:id/archive
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project |
```bash
curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/unarchive"
```
Example response:
```json
{
"id": 3,
"description": null,
"default_branch": "master",
"public": false,
"visibility_level": 0,
"ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git",
"http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git",
"web_url": "http://example.com/diaspora/diaspora-project-site",
"tag_list": [
"example",
"disapora project"
],
"owner": {
"id": 3,
"name": "Diaspora",
"created_at": "2013-09-30T13: 46: 02Z"
},
"name": "Diaspora Project Site",
"name_with_namespace": "Diaspora / Diaspora Project Site",
"path": "diaspora-project-site",
"path_with_namespace": "diaspora/diaspora-project-site",
"issues_enabled": true,
"open_issues_count": 1,
"merge_requests_enabled": true,
"builds_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
"created_at": "2013-09-30T13: 46: 02Z",
"last_activity_at": "2013-09-30T13: 46: 02Z",
"creator_id": 3,
"namespace": {
"created_at": "2013-09-30T13: 46: 02Z",
"description": "",
"id": 3,
"name": "Diaspora",
"owner_id": 1,
"path": "diaspora",
"updated_at": "2013-09-30T13: 46: 02Z"
},
"permissions": {
"project_access": {
"access_level": 10,
"notification_level": 3
},
"group_access": {
"access_level": 50,
"notification_level": 3
}
},
"archived": false,
"avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png",
"shared_runners_enabled": true,
"forks_count": 0,
"star_count": 0,
"runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b"
}
```
### Remove project ### Remove project
Removes a project including all associated resources (issues, merge requests etc.) Removes a project including all associated resources (issues, merge requests etc.)
......
...@@ -26,7 +26,6 @@ Example response: ...@@ -26,7 +26,6 @@ Example response:
"default_branch_protection" : 2, "default_branch_protection" : 2,
"restricted_visibility_levels" : [], "restricted_visibility_levels" : [],
"signin_enabled" : true, "signin_enabled" : true,
"twitter_sharing_enabled" : true,
"after_sign_out_path" : null, "after_sign_out_path" : null,
"max_attachment_size" : 10, "max_attachment_size" : 10,
"user_oauth_applications" : true, "user_oauth_applications" : true,
...@@ -57,7 +56,6 @@ PUT /application/settings ...@@ -57,7 +56,6 @@ PUT /application/settings
| `sign_in_text` | string | no | Text on login page | | `sign_in_text` | string | no | Text on login page |
| `home_page_url` | string | no | Redirect to this URL when not logged in | | `home_page_url` | string | no | Redirect to this URL when not logged in |
| `default_branch_protection` | integer | no | Determine if developers can push to master. Can take `0` _(not protected, both developers and masters can push new commits, force push or delete the branch)_, `1` _(partially protected, developers can push new commits, but cannot force push or delete the branch, masters can do anything)_ or `2` _(fully protected, developers cannot push new commits, force push or delete the branch, masters can do anything)_ as a parameter. Default is `1`. | | `default_branch_protection` | integer | no | Determine if developers can push to master. Can take `0` _(not protected, both developers and masters can push new commits, force push or delete the branch)_, `1` _(partially protected, developers can push new commits, but cannot force push or delete the branch, masters can do anything)_ or `2` _(fully protected, developers cannot push new commits, force push or delete the branch, masters can do anything)_ as a parameter. Default is `1`. |
| `twitter_sharing_enabled` | boolean | no | Allow users to share project creation on Twitter |
| `restricted_visibility_levels` | array of integers | no | Selected levels cannot be used by non-admin users for projects or snippets. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is null which means there is no restriction. | | `restricted_visibility_levels` | array of integers | no | Selected levels cannot be used by non-admin users for projects or snippets. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is null which means there is no restriction. |
| `max_attachment_size` | integer | no | Limit attachment size in MB | | `max_attachment_size` | integer | no | Limit attachment size in MB |
| `session_expire_delay` | integer | no | Session duration in minutes. GitLab restart is required to apply changes | | `session_expire_delay` | integer | no | Session duration in minutes. GitLab restart is required to apply changes |
...@@ -85,7 +83,6 @@ Example response: ...@@ -85,7 +83,6 @@ Example response:
"updated_at": "2015-06-30T13:22:42.210Z", "updated_at": "2015-06-30T13:22:42.210Z",
"home_page_url": "", "home_page_url": "",
"default_branch_protection": 2, "default_branch_protection": 2,
"twitter_sharing_enabled": true,
"restricted_visibility_levels": [], "restricted_visibility_levels": [],
"max_attachment_size": 10, "max_attachment_size": 10,
"session_expire_delay": 10080, "session_expire_delay": 10080,
......
...@@ -227,9 +227,9 @@ sudo usermod -aG redis git ...@@ -227,9 +227,9 @@ sudo usermod -aG redis git
### Clone the Source ### Clone the Source
# Clone GitLab repository # Clone GitLab repository
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-6-stable gitlab sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-7-stable gitlab
**Note:** You can change `8-6-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! **Note:** You can change `8-7-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It ### Configure It
......
# From 8.6 to 8.7
Make sure you view this update guide from the tag (version) of GitLab you would
like to install. In most cases this should be the highest numbered production
tag (without rc in it). You can select the tag in the version dropdown at the
top left corner of GitLab (below the menu bar).
If the highest number stable branch is unclear please check the
[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
guide links by version.
### 1. Stop server
sudo service gitlab stop
### 2. Backup
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
### 3. Get latest code
```bash
sudo -u git -H git fetch --all
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
```
For GitLab Community Edition:
```bash
sudo -u git -H git checkout 8-7-stable
```
OR
For GitLab Enterprise Edition:
```bash
sudo -u git -H git checkout 8-7-stable-ee
```
### 4. Update gitlab-shell
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch --all
sudo -u git -H git checkout v2.7.0
```
### 5. Update gitlab-workhorse
Install and compile gitlab-workhorse. This requires
[Go 1.5](https://golang.org/dl) which should already be on your system from
GitLab 8.1.
```bash
cd /home/git/gitlab-workhorse
sudo -u git -H git fetch --all
sudo -u git -H git checkout v0.7.1
sudo -u git -H make
```
### 6. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
# MySQL installations (note: the line below states '--without postgres')
sudo -u git -H bundle install --without postgres development test --deployment
# PostgreSQL installations (note: the line below states '--without mysql')
sudo -u git -H bundle install --without mysql development test --deployment
# Optional: clean up old gems
sudo -u git -H bundle clean
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
# Clean up assets and cache
sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
```
### 7. Update configuration files
#### Nginx configuration
Ensure you're still up-to-date with the latest NGINX configuration changes:
```sh
# For HTTPS configurations
git diff origin/8-6-stable:lib/support/nginx/gitlab-ssl origin/8-7-stable:lib/support/nginx/gitlab-ssl
# For HTTP configurations
git diff origin/8-6-stable:lib/support/nginx/gitlab origin/8-7-stable:lib/support/nginx/gitlab
```
If you are using Apache instead of NGINX please see the updated [Apache templates].
Also note that because Apache does not support upstreams behind Unix sockets you
will need to let gitlab-workhorse listen on a TCP port. You can do this
via [/etc/default/gitlab].
[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-7-stable/lib/support/init.d/gitlab.default.example#L37
#### Init script
Ensure you're still up-to-date with the latest init script changes:
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
### 8. Start application
sudo service gitlab start
sudo service nginx restart
### 9. Check application status
Check if GitLab and its environment are configured correctly:
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
To make sure you didn't miss anything run a more thorough check:
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
If all items are green, then congratulations, the upgrade is complete!
## Things went south? Revert to previous version (8.6)
### 1. Revert the code to the previous version
Follow the [upgrade guide from 8.5 to 8.6](8.5-to-8.6.md), except for the
database migration (the backup is already migrated to the previous version).
### 2. Restore from the backup
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
...@@ -6,6 +6,7 @@ Feature: Dashboard ...@@ -6,6 +6,7 @@ Feature: Dashboard
And project "Shop" has push event And project "Shop" has push event
And project "Shop" has CI enabled And project "Shop" has CI enabled
And project "Shop" has CI build And project "Shop" has CI build
And project "Shop" has labels: "bug", "feature", "enhancement"
And I visit dashboard page And I visit dashboard page
Scenario: I should see projects list Scenario: I should see projects list
...@@ -50,6 +51,13 @@ Feature: Dashboard ...@@ -50,6 +51,13 @@ Feature: Dashboard
And I visit dashboard issues page And I visit dashboard issues page
Then The list should be sorted by "Oldest updated" Then The list should be sorted by "Oldest updated"
@javascript
Scenario: Filtering Issues by label
Given project "Shop" has issue "Bugfix1" with label "feature"
When I visit dashboard issues page
And I filter the list by label "feature"
Then I should see "Bugfix1" in issues list
@javascript @javascript
Scenario: Visiting Project's issues after sorting Scenario: Visiting Project's issues after sorting
Given I visit dashboard issues page Given I visit dashboard issues page
......
...@@ -7,10 +7,6 @@ Feature: Groups ...@@ -7,10 +7,6 @@ Feature: Groups
When I visit group "NonExistentGroup" page When I visit group "NonExistentGroup" page
Then page status code should be 404 Then page status code should be 404
Scenario: I should have back to group button
When I visit group "Owned" page
Then I should see back to dashboard button
@javascript @javascript
Scenario: I should see group "Owned" dashboard list Scenario: I should see group "Owned" dashboard list
When I visit group "Owned" page When I visit group "Owned" page
......
...@@ -18,15 +18,6 @@ Feature: Project ...@@ -18,15 +18,6 @@ Feature: Project
Then I should see the default project avatar Then I should see the default project avatar
And I should not see the "Remove avatar" button And I should not see the "Remove avatar" button
Scenario: I should have back to group button
And project "Shop" belongs to group
And I visit project "Shop" page
Then I should see back to group button
Scenario: I should have back to group button
And I visit project "Shop" page
Then I should see back to dashboard button
Scenario: I should have readme on page Scenario: I should have readme on page
And I visit project "Shop" page And I visit project "Shop" page
Then I should see project "Shop" README Then I should see project "Shop" README
......
...@@ -87,4 +87,23 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps ...@@ -87,4 +87,23 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
step 'I should see 1 project at group list' do step 'I should see 1 project at group list' do
expect(find('span.last_activity/span')).to have_content('1') expect(find('span.last_activity/span')).to have_content('1')
end end
step 'I filter the list by label "feature"' do
page.within ".labels-filter" do
find('.dropdown').click
click_link "feature"
end
end
step 'I should see "Bugfix1" in issues list' do
page.within "ul.content-list" do
expect(page).to have_content "Bugfix1"
end
end
step 'project "Shop" has issue "Bugfix1" with label "feature"' do
project = Project.find_by(name: "Shop")
issue = create(:issue, title: "Bugfix1", project: project, assignee: current_user)
issue.labels << project.labels.find_by(title: 'feature')
end
end end
...@@ -5,7 +5,9 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps ...@@ -5,7 +5,9 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
include SharedUser include SharedUser
step 'I click on group milestones' do step 'I click on group milestones' do
click_link 'Milestones' page.within '.nav-secondary' do
click_link("Milestones")
end
end end
step 'I should see group milestones index page has no milestones' do step 'I should see group milestones index page has no milestones' do
......
...@@ -4,10 +4,6 @@ class Spinach::Features::Groups < Spinach::FeatureSteps ...@@ -4,10 +4,6 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
include SharedGroup include SharedGroup
include SharedUser include SharedUser
step 'I should see back to dashboard button' do
expect(page).to have_content 'Go to dashboard'
end
step 'I should see group "Owned"' do step 'I should see group "Owned"' do
expect(page).to have_content '@owned' expect(page).to have_content '@owned'
end end
......
...@@ -82,7 +82,9 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps ...@@ -82,7 +82,9 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
# Sub Tabs: Issues # Sub Tabs: Issues
step 'I click the "Milestones" tab' do step 'I click the "Milestones" tab' do
click_link('Milestones') page.within '.nav-secondary' do
click_link('Milestones')
end
end end
step 'I click the "Labels" tab' do step 'I click the "Labels" tab' do
......
...@@ -36,7 +36,7 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps ...@@ -36,7 +36,7 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
end end
step 'I goto the Merge Requests page' do step 'I goto the Merge Requests page' do
page.within '.page-sidebar-expanded' do page.within '.nav-secondary' do
click_link "Merge Requests" click_link "Merge Requests"
end end
end end
......
...@@ -326,7 +326,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -326,7 +326,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I should see a discussion has started on diff' do step 'I should see a discussion has started on diff' do
page.within(".notes .discussion") do page.within(".notes .discussion") do
page.should have_content "#{current_user.name} started a discussion" page.should have_content "#{current_user.name} #{current_user.to_reference} started a discussion"
page.should have_content sample_commit.line_code_path page.should have_content sample_commit.line_code_path
page.should have_content "Line is wrong" page.should have_content "Line is wrong"
end end
...@@ -334,7 +334,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -334,7 +334,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I should see a discussion by user "John Doe" has started on diff' do step 'I should see a discussion by user "John Doe" has started on diff' do
page.within(".notes .discussion") do page.within(".notes .discussion") do
page.should have_content "#{user_exists("John Doe").name} started a discussion" page.should have_content "#{user_exists("John Doe").name} #{user_exists("John Doe").to_reference} started a discussion"
page.should have_content sample_commit.line_code_path page.should have_content sample_commit.line_code_path
page.should have_content "Line is wrong" page.should have_content "Line is wrong"
end end
...@@ -350,7 +350,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -350,7 +350,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I should see a discussion has started on commit diff' do step 'I should see a discussion has started on commit diff' do
page.within(".notes .discussion") do page.within(".notes .discussion") do
page.should have_content "#{current_user.name} started a discussion on commit" page.should have_content "#{current_user.name} #{current_user.to_reference} started a discussion on commit"
page.should have_content sample_commit.line_code_path page.should have_content sample_commit.line_code_path
page.should have_content "Line is wrong" page.should have_content "Line is wrong"
end end
...@@ -358,7 +358,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -358,7 +358,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I should see a discussion has started on commit' do step 'I should see a discussion has started on commit' do
page.within(".notes .discussion") do page.within(".notes .discussion") do
page.should have_content "#{current_user.name} started a discussion on commit" page.should have_content "#{current_user.name} #{current_user.to_reference} started a discussion on commit"
page.should have_content "One comment to rule them all" page.should have_content "One comment to rule them all"
end end
end end
......
...@@ -114,7 +114,9 @@ class Spinach::Features::Project < Spinach::FeatureSteps ...@@ -114,7 +114,9 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end end
step 'I should not see "Snippets" button' do step 'I should not see "Snippets" button' do
expect(page).not_to have_link 'Snippets' page.within '.nav-secondary' do
expect(page).not_to have_link 'Snippets'
end
end end
step 'project "Shop" belongs to group' do step 'project "Shop" belongs to group' do
...@@ -123,14 +125,6 @@ class Spinach::Features::Project < Spinach::FeatureSteps ...@@ -123,14 +125,6 @@ class Spinach::Features::Project < Spinach::FeatureSteps
@project.save! @project.save!
end end
step 'I should see back to dashboard button' do
expect(page).to have_content 'Go to dashboard'
end
step 'I should see back to group button' do
expect(page).to have_content 'Go to group'
end
step 'I click notifications drop down button' do step 'I click notifications drop down button' do
click_link 'notifications-button' click_link 'notifications-button'
end end
......
...@@ -41,7 +41,7 @@ module SharedProjectTab ...@@ -41,7 +41,7 @@ module SharedProjectTab
end end
step 'the active main tab should be Settings' do step 'the active main tab should be Settings' do
page.within '.nav-sidebar' do page.within '.nav-secondary' do
expect(page).to have_content('Go to project') expect(page).to have_content('Go to project')
end end
end end
......
...@@ -8,7 +8,7 @@ module API ...@@ -8,7 +8,7 @@ module API
expose :id, :state, :avatar_url expose :id, :state, :avatar_url
expose :web_url do |user, options| expose :web_url do |user, options|
Gitlab::Application.routes.url_helpers.user_url(user) Gitlab::Routing.url_helpers.user_url(user)
end end
end end
...@@ -89,7 +89,7 @@ module API ...@@ -89,7 +89,7 @@ module API
expose :avatar_url expose :avatar_url
expose :web_url do |group, options| expose :web_url do |group, options|
Gitlab::Application.routes.url_helpers.group_url(group) Gitlab::Routing.url_helpers.group_url(group)
end end
end end
...@@ -334,7 +334,6 @@ module API ...@@ -334,7 +334,6 @@ module API
expose :updated_at expose :updated_at
expose :home_page_url expose :home_page_url
expose :default_branch_protection expose :default_branch_protection
expose :twitter_sharing_enabled
expose :restricted_visibility_levels expose :restricted_visibility_levels
expose :max_attachment_size expose :max_attachment_size
expose :session_expire_delay expose :session_expire_delay
......
...@@ -244,6 +244,34 @@ module API ...@@ -244,6 +244,34 @@ module API
end end
end end
# Archive project
#
# Parameters:
# id (required) - The ID of a project
# Example Request:
# PUT /projects/:id/archive
post ':id/archive' do
authorize!(:archive_project, user_project)
user_project.archive!
present user_project, with: Entities::Project
end
# Unarchive project
#
# Parameters:
# id (required) - The ID of a project
# Example Request:
# PUT /projects/:id/unarchive
post ':id/unarchive' do
authorize!(:archive_project, user_project)
user_project.unarchive!
present user_project, with: Entities::Project
end
# Remove project # Remove project
# #
# Parameters: # Parameters:
......
...@@ -11,15 +11,19 @@ module Banzai ...@@ -11,15 +11,19 @@ module Banzai
end end
def self.object_name def self.object_name
object_class.name.underscore @object_name ||= object_class.name.underscore
end end
def self.object_sym def self.object_sym
object_name.to_sym @object_sym ||= object_name.to_sym
end end
def self.data_reference def self.data_reference
"data-#{object_name.dasherize}" @data_reference ||= "data-#{object_name.dasherize}"
end
def self.object_class_title
@object_title ||= object_class.name.titleize
end end
# Public: Find references in text (like `!123` for merge requests) # Public: Find references in text (like `!123` for merge requests)
...@@ -53,6 +57,10 @@ module Banzai ...@@ -53,6 +57,10 @@ module Banzai
self.class.object_sym self.class.object_sym
end end
def object_class_title
self.class.object_class_title
end
def references_in(*args, &block) def references_in(*args, &block)
self.class.references_in(*args, &block) self.class.references_in(*args, &block)
end end
...@@ -62,36 +70,81 @@ module Banzai ...@@ -62,36 +70,81 @@ module Banzai
# Example: project.merge_requests.find # Example: project.merge_requests.find
end end
def find_object_cached(project, id)
if RequestStore.active?
cache = find_objects_cache[object_class][project.id]
get_or_set_cache(cache, id) { find_object(project, id) }
else
find_object(project, id)
end
end
def project_from_ref_cache(ref)
if RequestStore.active?
cache = project_refs_cache
get_or_set_cache(cache, ref) { project_from_ref(ref) }
else
project_from_ref(ref)
end
end
def url_for_object(object, project) def url_for_object(object, project)
# Implement in child class # Implement in child class
# Example: project_merge_request_url # Example: project_merge_request_url
end end
def call def url_for_object_cached(object, project)
if object_class.reference_pattern if RequestStore.active?
# `#123` cache = url_for_object_cache[object_class][project.id]
replace_text_nodes_matching(object_class.reference_pattern) do |content|
object_link_filter(content, object_class.reference_pattern)
end
# `[Issue](#123)`, which is turned into get_or_set_cache(cache, object) { url_for_object(object, project) }
# `<a href="#123">Issue</a>` else
replace_link_nodes_with_href(object_class.reference_pattern) do |link, text| url_for_object(object, project)
object_link_filter(link, object_class.reference_pattern, link_text: text)
end
end end
end
if object_class.link_reference_pattern def call
# `http://gitlab.example.com/namespace/project/issues/123`, which is turned into return doc if project.nil?
# `<a href="http://gitlab.example.com/namespace/project/issues/123">http://gitlab.example.com/namespace/project/issues/123</a>`
replace_link_nodes_with_text(object_class.link_reference_pattern) do |text| ref_pattern = object_class.reference_pattern
object_link_filter(text, object_class.link_reference_pattern) link_pattern = object_class.link_reference_pattern
end
# `[Issue](http://gitlab.example.com/namespace/project/issues/123)`, which is turned into each_node do |node|
# `<a href="http://gitlab.example.com/namespace/project/issues/123">Issue</a>` if text_node?(node) && ref_pattern
replace_link_nodes_with_href(object_class.link_reference_pattern) do |link, text| replace_text_when_pattern_matches(node, ref_pattern) do |content|
object_link_filter(link, object_class.link_reference_pattern, link_text: text) object_link_filter(content, ref_pattern)
end
elsif element_node?(node)
yield_valid_link(node) do |link, text|
if ref_pattern && link =~ /\A#{ref_pattern}/
replace_link_node_with_href(node, link) do
object_link_filter(link, ref_pattern, link_text: text)
end
next
end
next unless link_pattern
if link == text && text =~ /\A#{link_pattern}/
replace_link_node_with_text(node, link) do
object_link_filter(text, link_pattern)
end
next
end
if link =~ /\A#{link_pattern}\z/
replace_link_node_with_href(node, link) do
object_link_filter(link, link_pattern, link_text: text)
end
next
end
end
end end
end end
...@@ -109,9 +162,9 @@ module Banzai ...@@ -109,9 +162,9 @@ module Banzai
# have `gfm` and `gfm-OBJECT_NAME` class names attached for styling. # have `gfm` and `gfm-OBJECT_NAME` class names attached for styling.
def object_link_filter(text, pattern, link_text: nil) def object_link_filter(text, pattern, link_text: nil)
references_in(text, pattern) do |match, id, project_ref, matches| references_in(text, pattern) do |match, id, project_ref, matches|
project = project_from_ref(project_ref) project = project_from_ref_cache(project_ref)
if project && object = find_object(project, id) if project && object = find_object_cached(project, id)
title = object_link_title(object) title = object_link_title(object)
klass = reference_class(object_sym) klass = reference_class(object_sym)
...@@ -121,8 +174,11 @@ module Banzai ...@@ -121,8 +174,11 @@ module Banzai
object_sym => object.id object_sym => object.id
) )
url = matches[:url] if matches.names.include?("url") if matches.names.include?("url") && matches[:url]
url ||= url_for_object(object, project) url = matches[:url]
else
url = url_for_object_cached(object, project)
end
text = link_text || object_link_text(object, matches) text = link_text || object_link_text(object, matches)
...@@ -146,7 +202,7 @@ module Banzai ...@@ -146,7 +202,7 @@ module Banzai
end end
def object_link_title(object) def object_link_title(object)
"#{object_class.name.titleize}: #{object.title}" "#{object_class_title}: #{object.title}"
end end
def object_link_text(object, matches) def object_link_text(object, matches)
...@@ -157,6 +213,32 @@ module Banzai ...@@ -157,6 +213,32 @@ module Banzai
text text
end end
private
def project_refs_cache
RequestStore[:banzai_project_refs] ||= {}
end
def find_objects_cache
RequestStore[:banzai_find_objects_cache] ||= Hash.new do |hash, key|
hash[key] = Hash.new { |h, k| h[k] = {} }
end
end
def url_for_object_cache
RequestStore[:banzai_url_for_object] ||= Hash.new do |hash, key|
hash[key] = Hash.new { |h, k| h[k] = {} }
end
end
def get_or_set_cache(cache, key)
if cache.key?(key)
cache[key]
else
cache[key] = yield
end
end
end end
end end
end end
...@@ -43,7 +43,7 @@ module Banzai ...@@ -43,7 +43,7 @@ module Banzai
end end
def url_for_object(range, project) def url_for_object(range, project)
h = Gitlab::Application.routes.url_helpers h = Gitlab::Routing.url_helpers
h.namespace_project_compare_url(project.namespace, project, h.namespace_project_compare_url(project.namespace, project,
range.to_param.merge(only_path: context[:only_path])) range.to_param.merge(only_path: context[:only_path]))
end end
......
...@@ -37,7 +37,7 @@ module Banzai ...@@ -37,7 +37,7 @@ module Banzai
end end
def url_for_object(commit, project) def url_for_object(commit, project)
h = Gitlab::Application.routes.url_helpers h = Gitlab::Routing.url_helpers
h.namespace_project_commit_url(project.namespace, project, commit, h.namespace_project_commit_url(project.namespace, project, commit,
only_path: context[:only_path]) only_path: context[:only_path])
end end
......
...@@ -35,15 +35,29 @@ module Banzai ...@@ -35,15 +35,29 @@ module Banzai
def call def call
# Early return if the project isn't using an external tracker # Early return if the project isn't using an external tracker
return doc if project.nil? || project.default_issues_tracker? return doc if project.nil? || default_issues_tracker?
replace_text_nodes_matching(ExternalIssue.reference_pattern) do |content| ref_pattern = ExternalIssue.reference_pattern
issue_link_filter(content) ref_start_pattern = /\A#{ref_pattern}\z/
end
each_node do |node|
if text_node?(node)
replace_text_when_pattern_matches(node, ref_pattern) do |content|
issue_link_filter(content)
end
replace_link_nodes_with_href(ExternalIssue.reference_pattern) do |link, text| elsif element_node?(node)
issue_link_filter(link, link_text: text) yield_valid_link(node) do |link, text|
if link =~ ref_start_pattern
replace_link_node_with_href(node, link) do
issue_link_filter(link, link_text: text)
end
end
end
end
end end
doc
end end
# Replace `JIRA-123` issue references in text with links to the referenced # Replace `JIRA-123` issue references in text with links to the referenced
...@@ -76,6 +90,21 @@ module Banzai ...@@ -76,6 +90,21 @@ module Banzai
def url_for_issue(*args) def url_for_issue(*args)
IssuesHelper.url_for_issue(*args) IssuesHelper.url_for_issue(*args)
end end
def default_issues_tracker?
if RequestStore.active?
default_issues_tracker_cache[project.id] ||=
project.default_issues_tracker?
else
project.default_issues_tracker?
end
end
private
def default_issues_tracker_cache
RequestStore[:banzai_default_issues_tracker_cache] ||= {}
end
end end
end end
end end
...@@ -31,7 +31,7 @@ module Banzai ...@@ -31,7 +31,7 @@ module Banzai
end end
def url_for_object(label, project) def url_for_object(label, project)
h = Gitlab::Application.routes.url_helpers h = Gitlab::Routing.url_helpers
h.namespace_project_issues_url(project.namespace, project, label_name: label.name, h.namespace_project_issues_url(project.namespace, project, label_name: label.name,
only_path: context[:only_path]) only_path: context[:only_path])
end end
......
...@@ -14,7 +14,7 @@ module Banzai ...@@ -14,7 +14,7 @@ module Banzai
end end
def url_for_object(mr, project) def url_for_object(mr, project)
h = Gitlab::Application.routes.url_helpers h = Gitlab::Routing.url_helpers
h.namespace_project_merge_request_url(project.namespace, project, mr, h.namespace_project_merge_request_url(project.namespace, project, mr,
only_path: context[:only_path]) only_path: context[:only_path])
end end
......
...@@ -11,7 +11,7 @@ module Banzai ...@@ -11,7 +11,7 @@ module Banzai
end end
def url_for_object(issue, project) def url_for_object(issue, project)
h = Gitlab::Application.routes.url_helpers h = Gitlab::Routing.url_helpers
h.namespace_project_milestone_url(project.namespace, project, milestone, h.namespace_project_milestone_url(project.namespace, project, milestone,
only_path: context[:only_path]) only_path: context[:only_path])
end end
......
...@@ -52,18 +52,13 @@ module Banzai ...@@ -52,18 +52,13 @@ module Banzai
html.html_safe? ? html : ERB::Util.html_escape_once(html) html.html_safe? ? html : ERB::Util.html_escape_once(html)
end end
def ignore_parents def ignore_ancestor_query
@ignore_parents ||= begin @ignore_ancestor_query ||= begin
# Don't look for references in text nodes that are children of these
# elements.
parents = %w(pre code a style) parents = %w(pre code a style)
parents << 'blockquote' if context[:ignore_blockquotes] parents << 'blockquote' if context[:ignore_blockquotes]
parents.to_set
end
end
def ignored_ancestry?(node) parents.map { |n| "ancestor::#{n}" }.join(' or ')
has_ancestor?(node, ignore_parents) end
end end
def project def project
...@@ -74,119 +69,66 @@ module Banzai ...@@ -74,119 +69,66 @@ module Banzai
"gfm gfm-#{type}" "gfm gfm-#{type}"
end end
# Iterate through the document's text nodes, yielding the current node's # Ensure that a :project key exists in context
# content if:
#
# * The `project` context value is present AND
# * The node's content matches `pattern` AND
# * The node is not an ancestor of an ignored node type
#
# pattern - Regex pattern against which to match the node's content
#
# Yields the current node's String contents. The result of the block will
# replace the node's existing content and update the current document.
# #
# Returns the updated Nokogiri::HTML::DocumentFragment object. # Note that while the key might exist, its value could be nil!
def replace_text_nodes_matching(pattern) def validate
return doc if project.nil? needs :project
search_text_nodes(doc).each do |node|
next if ignored_ancestry?(node)
next unless node.text =~ pattern
content = node.to_html
html = yield content
next if html == content
node.replace(html)
end
doc
end end
# Iterate through the document's link nodes, yielding the current node's # Iterates over all <a> and text() nodes in a document.
# content if:
#
# * The `project` context value is present AND
# * The node's content matches `pattern`
#
# pattern - Regex pattern against which to match the node's content
#
# Yields the current node's String contents. The result of the block will
# replace the node and update the current document.
# #
# Returns the updated Nokogiri::HTML::DocumentFragment object. # Nodes are skipped whenever their ancestor is one of the nodes returned
def replace_link_nodes_with_text(pattern) # by `ignore_ancestor_query`. Link tags are not processed if they have a
return doc if project.nil? # "gfm" class or the "href" attribute is empty.
def each_node
query = %Q{descendant-or-self::text()[not(#{ignore_ancestor_query})]
| descendant-or-self::a[
not(contains(concat(" ", @class, " "), " gfm ")) and not(@href = "")
]}
doc.xpath('descendant-or-self::a').each do |node| doc.xpath(query).each do |node|
klass = node.attr('class') yield node
next if klass && klass.include?('gfm') end
end
link = node.attr('href')
text = node.text
next unless link && text
link = CGI.unescape(link)
next unless link.force_encoding('UTF-8').valid_encoding?
# Ignore ending punctionation like periods or commas
next unless link == text && text =~ /\A#{pattern}/
html = yield text
next if html == text # Yields the link's URL and text whenever the node is a valid <a> tag.
def yield_valid_link(node)
link = CGI.unescape(node.attr('href').to_s)
text = node.text
node.replace(html) return unless link.force_encoding('UTF-8').valid_encoding?
end
doc yield link, text
end end
# Iterate through the document's link nodes, yielding the current node's def replace_text_when_pattern_matches(node, pattern)
# content if: return unless node.text =~ pattern
#
# * The `project` context value is present AND
# * The node's HREF matches `pattern`
#
# pattern - Regex pattern against which to match the node's HREF
#
# Yields the current node's String HREF and String content.
# The result of the block will replace the node and update the current document.
#
# Returns the updated Nokogiri::HTML::DocumentFragment object.
def replace_link_nodes_with_href(pattern)
return doc if project.nil?
doc.xpath('descendant-or-self::a').each do |node| content = node.to_html
klass = node.attr('class') html = yield content
next if klass && klass.include?('gfm')
link = node.attr('href') node.replace(html) unless content == html
text = node.text end
next unless link && text def replace_link_node_with_text(node, link)
link = CGI.unescape(link) html = yield
next unless link.force_encoding('UTF-8').valid_encoding?
next unless link && link =~ /\A#{pattern}\z/
html = yield link, text node.replace(html) unless html == node.text
end
next if html == link def replace_link_node_with_href(node, link)
html = yield
node.replace(html) node.replace(html) unless html == link
end end
doc def text_node?(node)
node.is_a?(Nokogiri::XML::Text)
end end
# Ensure that a :project key exists in context def element_node?(node)
# node.is_a?(Nokogiri::XML::Element)
# Note that while the key might exist, its value could be nil!
def validate
needs :project
end end
end end
end end
......
...@@ -14,7 +14,7 @@ module Banzai ...@@ -14,7 +14,7 @@ module Banzai
end end
def url_for_object(snippet, project) def url_for_object(snippet, project)
h = Gitlab::Application.routes.url_helpers h = Gitlab::Routing.url_helpers
h.namespace_project_snippet_url(project.namespace, project, snippet, h.namespace_project_snippet_url(project.namespace, project, snippet,
only_path: context[:only_path]) only_path: context[:only_path])
end end
......
...@@ -59,13 +59,28 @@ module Banzai ...@@ -59,13 +59,28 @@ module Banzai
end end
def call def call
replace_text_nodes_matching(User.reference_pattern) do |content| return doc if project.nil?
user_link_filter(content)
ref_pattern = User.reference_pattern
ref_pattern_start = /\A#{ref_pattern}\z/
each_node do |node|
if text_node?(node)
replace_text_when_pattern_matches(node, ref_pattern) do |content|
user_link_filter(content)
end
elsif element_node?(node)
yield_valid_link(node) do |link, text|
if link =~ ref_pattern_start
replace_link_node_with_href(node, link) do
user_link_filter(link, link_text: text)
end
end
end
end
end end
replace_link_nodes_with_href(User.reference_pattern) do |link, text| doc
user_link_filter(link, link_text: text)
end
end end
# Replace `@user` user references in text with links to the referenced # Replace `@user` user references in text with links to the referenced
...@@ -90,7 +105,7 @@ module Banzai ...@@ -90,7 +105,7 @@ module Banzai
private private
def urls def urls
Gitlab::Application.routes.url_helpers Gitlab::Routing.url_helpers
end end
def link_class def link_class
......
...@@ -21,7 +21,6 @@ module Gitlab ...@@ -21,7 +21,6 @@ module Gitlab
default_branch_protection: Settings.gitlab['default_branch_protection'], default_branch_protection: Settings.gitlab['default_branch_protection'],
signup_enabled: Settings.gitlab['signup_enabled'], signup_enabled: Settings.gitlab['signup_enabled'],
signin_enabled: Settings.gitlab['signin_enabled'], signin_enabled: Settings.gitlab['signin_enabled'],
twitter_sharing_enabled: Settings.gitlab['twitter_sharing_enabled'],
gravatar_enabled: Settings.gravatar['enabled'], gravatar_enabled: Settings.gravatar['enabled'],
sign_in_text: Settings.extra['sign_in_text'], sign_in_text: Settings.extra['sign_in_text'],
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'], restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
......
...@@ -5,7 +5,7 @@ module Gitlab ...@@ -5,7 +5,7 @@ module Gitlab
attr_accessor :recipient attr_accessor :recipient
attr_reader :author_id, :ref, :action attr_reader :author_id, :ref, :action
include Gitlab::Application.routes.url_helpers include Gitlab::Routing.url_helpers
delegate :namespace, :name_with_namespace, to: :project, prefix: :project delegate :namespace, :name_with_namespace, to: :project, prefix: :project
delegate :name, to: :author, prefix: :author delegate :name, to: :author, prefix: :author
......
...@@ -26,7 +26,7 @@ module Gitlab ...@@ -26,7 +26,7 @@ module Gitlab
def user_map def user_map
users = {} users = {}
res = @api.command(:listPeople) res = @api.command(:listPeople)
res['people']['person'].each do |user| [res['people']['person']].flatten.each do |user|
users[user['ixPerson']] = { name: user['sFullName'], email: user['sEmail'] } users[user['ixPerson']] = { name: user['sFullName'], email: user['sEmail'] }
end end
users users
......
...@@ -34,16 +34,21 @@ module Gitlab ...@@ -34,16 +34,21 @@ module Gitlab
@source_project = source_project @source_project = source_project
@current_user = current_user @current_user = current_user
@original_html = markdown(text) @original_html = markdown(text)
@pattern = Gitlab::ReferenceExtractor.references_pattern
end end
def rewrite(target_project) def rewrite(target_project)
pattern = Gitlab::ReferenceExtractor.references_pattern return @text unless needs_rewrite?
@text.gsub(pattern) do |reference| @text.gsub(@pattern) do |reference|
unfold_reference(reference, Regexp.last_match, target_project) unfold_reference(reference, Regexp.last_match, target_project)
end end
end end
def needs_rewrite?
@text =~ @pattern
end
private private
def unfold_reference(reference, match, target_project) def unfold_reference(reference, match, target_project)
......
module Gitlab
module Gfm
##
# Class that rewrites markdown links for uploads
#
# Using a pattern defined in `FileUploader` it copies files to a new
# project and rewrites all links to uploads in in a given text.
#
#
class UploadsRewriter
def initialize(text, source_project, _current_user)
@text = text
@source_project = source_project
@pattern = FileUploader::MARKDOWN_PATTERN
end
def rewrite(target_project)
return @text unless needs_rewrite?
@text.gsub(@pattern) do |markdown|
file = find_file(@source_project, $~[:secret], $~[:file])
return markdown unless file.try(:exists?)
new_uploader = FileUploader.new(target_project)
new_uploader.store!(file)
new_uploader.to_markdown
end
end
def needs_rewrite?
files.any?
end
def files
referenced_files = @text.scan(@pattern).map do
find_file(@source_project, $~[:secret], $~[:file])
end
referenced_files.compact.select(&:exists?)
end
private
def find_file(project, secret, file)
uploader = FileUploader.new(project, secret)
uploader.retrieve_from_store!(file)
uploader.file
end
end
end
end
...@@ -41,7 +41,7 @@ module Gitlab ...@@ -41,7 +41,7 @@ module Gitlab
data[:issue] = note.noteable.hook_attrs data[:issue] = note.noteable.hook_attrs
elsif note.for_merge_request? elsif note.for_merge_request?
data[:merge_request] = note.noteable.hook_attrs data[:merge_request] = note.noteable.hook_attrs
elsif note.for_project_snippet? elsif note.for_snippet?
data[:snippet] = note.noteable.hook_attrs data[:snippet] = note.noteable.hook_attrs
end end
......
module Gitlab
module Routing
# Returns the URL helpers Module.
#
# This method caches the output as Rails' "url_helpers" method creates an
# anonymous module every time it's called.
#
# Returns a Module.
def self.url_helpers
@url_helpers ||= Gitlab::Application.routes.url_helpers
end
end
end
module Gitlab module Gitlab
class UrlBuilder class UrlBuilder
include Gitlab::Application.routes.url_helpers include Gitlab::Routing.url_helpers
include GitlabRoutingHelper include GitlabRoutingHelper
include ActionView::RecordIdentifier
def initialize(type) def initialize(type)
@type = type @type = type
...@@ -37,19 +38,16 @@ module Gitlab ...@@ -37,19 +38,16 @@ module Gitlab
namespace_project_commit_url(namespace_id: note.project.namespace, namespace_project_commit_url(namespace_id: note.project.namespace,
id: note.commit_id, id: note.commit_id,
project_id: note.project, project_id: note.project,
anchor: "note_#{note.id}") anchor: dom_id(note))
elsif note.for_issue? elsif note.for_issue?
issue = Issue.find(note.noteable_id) issue = Issue.find(note.noteable_id)
issue_url(issue, issue_url(issue, anchor: dom_id(note))
anchor: "note_#{note.id}")
elsif note.for_merge_request? elsif note.for_merge_request?
merge_request = MergeRequest.find(note.noteable_id) merge_request = MergeRequest.find(note.noteable_id)
merge_request_url(merge_request, merge_request_url(merge_request, anchor: dom_id(note))
anchor: "note_#{note.id}") elsif note.for_snippet?
elsif note.for_project_snippet?
snippet = Snippet.find(note.noteable_id) snippet = Snippet.find(note.noteable_id)
project_snippet_url(snippet, project_snippet_url(snippet, anchor: dom_id(note))
anchor: "note_#{note.id}")
end end
end end
end end
......
require 'spec_helper' require 'spec_helper'
describe Admin::UsersController do describe Admin::UsersController do
let(:admin) { create(:admin) } let(:user) { create(:user) }
before do before do
sign_in(admin) sign_in(create(:admin))
end end
describe 'DELETE #user with projects' do describe 'DELETE #user with projects' do
let(:user) { create(:user) } let(:project) { create(:empty_project, namespace: user.namespace) }
let(:project) { create(:project, namespace: user.namespace) }
before do before do
project.team << [user, :developer] project.team << [user, :developer]
...@@ -23,8 +22,6 @@ describe Admin::UsersController do ...@@ -23,8 +22,6 @@ describe Admin::UsersController do
end end
describe 'PUT block/:id' do describe 'PUT block/:id' do
let(:user) { create(:user) }
it 'blocks user' do it 'blocks user' do
put :block, id: user.username put :block, id: user.username
user.reload user.reload
...@@ -50,8 +47,6 @@ describe Admin::UsersController do ...@@ -50,8 +47,6 @@ describe Admin::UsersController do
end end
context 'manually blocked users' do context 'manually blocked users' do
let(:user) { create(:user) }
before do before do
user.block user.block
end end
...@@ -66,8 +61,6 @@ describe Admin::UsersController do ...@@ -66,8 +61,6 @@ describe Admin::UsersController do
end end
describe 'PUT unlock/:id' do describe 'PUT unlock/:id' do
let(:user) { create(:user) }
before do before do
request.env["HTTP_REFERER"] = "/" request.env["HTTP_REFERER"] = "/"
user.lock_access! user.lock_access!
...@@ -95,8 +88,6 @@ describe Admin::UsersController do ...@@ -95,8 +88,6 @@ describe Admin::UsersController do
end end
describe 'PATCH disable_two_factor' do describe 'PATCH disable_two_factor' do
let(:user) { create(:user) }
it 'disables 2FA for the user' do it 'disables 2FA for the user' do
expect(user).to receive(:disable_two_factor!) expect(user).to receive(:disable_two_factor!)
allow(subject).to receive(:user).and_return(user) allow(subject).to receive(:user).and_return(user)
......
FactoryGirl.define do
factory :file_uploader do
project
secret nil
transient do
fixture { 'rails_sample.jpg' }
path { File.join(Rails.root, 'spec/fixtures', fixture) }
file { Rack::Test::UploadedFile.new(path) }
end
after(:build) do |uploader, evaluator|
uploader.store!(evaluator.file)
end
initialize_with do
new(project, secret)
end
end
end
...@@ -13,5 +13,10 @@ FactoryGirl.define do ...@@ -13,5 +13,10 @@ FactoryGirl.define do
factory :forked_project_link do factory :forked_project_link do
association :forked_to_project, factory: :project association :forked_to_project, factory: :project
association :forked_from_project, factory: :project association :forked_from_project, factory: :project
after(:create) do |link|
link.forked_from_project.reload
link.forked_to_project.reload
end
end end
end end
require 'spec_helper' require 'spec_helper'
FactoryGirl.factories.map(&:name).each do |factory_name| describe 'factories' do
describe "#{factory_name} factory" do FactoryGirl.factories.each do |factory|
it 'should be valid' do describe "#{factory.name} factory" do
expect(build(factory_name)).to be_valid let(:entity) { build(factory.name) }
it 'does not raise error when created 'do
expect { entity }.to_not raise_error
end
it 'should be valid', if: factory.build_class < ActiveRecord::Base do
expect(entity).to be_valid
end
end end
end end
end end
require 'spec_helper'
feature 'Create New Merge Request', feature: true, js: false do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
before do
project.team << [user, :master]
login_as user
visit namespace_project_merge_requests_path(project.namespace, project)
end
it 'generates a diff for an orphaned branch' do
click_link 'New Merge Request'
select "orphaned-branch", from: "merge_request_source_branch"
select "master", from: "merge_request_target_branch"
click_button "Compare branches"
expect(page).to have_content "README.md"
expect(page).to have_content "wm.png"
fill_in "merge_request_title", with: "Orphaned MR test"
click_button "Submit merge request"
expect(page).to have_content 'git checkout -b orphaned-branch origin/orphaned-branch'
end
end
require 'spec_helper' require 'spec_helper'
describe "Search", feature: true do describe "Search", feature: true do
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
before do before do
login_as :user login_with(user)
@project = create(:project, namespace: @user.namespace) project.team << [user, :reporter]
@project.team << [@user, :reporter]
visit search_path visit search_path
end
page.within '.search-holder' do describe 'searching for Projects' do
fill_in "search", with: @project.name[0..3] it 'finds a project' do
click_button "Search" page.within '.search-holder' do
fill_in "search", with: project.name[0..3]
click_button "Search"
end
expect(page).to have_content project.name
end end
end end
it "should show project in search results" do context 'search for comments' do
expect(page).to have_content @project.name it 'finds a snippet' do
snippet = create(:project_snippet, :private, project: project, author: user, title: 'Some title')
note = create(:note,
noteable: snippet,
author: user,
note: 'Supercalifragilisticexpialidocious',
project: project)
# Must visit project dashboard since global search won't search
# everything (e.g. comments, snippets, etc.)
visit namespace_project_path(project.namespace, project)
page.within '.search' do
fill_in 'search', with: note.note
click_button 'Go'
end
click_link 'Comments'
expect(page).to have_link(snippet.title)
end
end end
end end
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
describe ExtractsPath, lib: true do describe ExtractsPath, lib: true do
include ExtractsPath include ExtractsPath
include RepoHelpers include RepoHelpers
include Gitlab::Application.routes.url_helpers include Gitlab::Routing.url_helpers
let(:project) { double('project') } let(:project) { double('project') }
......
...@@ -236,6 +236,6 @@ describe Gitlab::ClosingIssueExtractor, lib: true do ...@@ -236,6 +236,6 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
end end
def urls def urls
Gitlab::Application.routes.url_helpers Gitlab::Routing.url_helpers
end end
end end
require 'spec_helper'
describe Gitlab::FogbugzImport::Client, lib: true do
let(:client) { described_class.new(uri: '', token: '') }
let(:one_user) { { 'people' => { 'person' => { "ixPerson" => "2", "sFullName" => "James" } } } }
let(:two_users) { { 'people' => { 'person' => [one_user, { "ixPerson" => "3" }] } } }
it 'retrieves user_map with one user' do
stub_api(one_user)
expect(client.user_map.count).to eq(1)
end
it 'retrieves user_map with two users' do
stub_api(two_users)
expect(client.user_map.count).to eq(2)
end
def stub_api(users)
allow_any_instance_of(::Fogbugz::Interface).to receive(:command).with(:listPeople).and_return(users)
end
end
require 'spec_helper'
describe Gitlab::Gfm::UploadsRewriter do
let(:user) { create(:user) }
let(:old_project) { create(:project) }
let(:new_project) { create(:project) }
let(:rewriter) { described_class.new(text, old_project, user) }
context 'text contains links to uploads' do
let(:image_uploader) do
build(:file_uploader, project: old_project)
end
let(:zip_uploader) do
build(:file_uploader, project: old_project,
fixture: 'ci_build_artifacts.zip')
end
let(:text) do
"Text and #{image_uploader.to_markdown} and #{zip_uploader.to_markdown}"
end
describe '#rewrite' do
let!(:new_text) { rewriter.rewrite(new_project) }
let(:old_files) { [image_uploader, zip_uploader].map(&:file) }
let(:new_files) do
described_class.new(new_text, new_project, user).files
end
let(:old_paths) { old_files.map(&:path) }
let(:new_paths) { new_files.map(&:path) }
it 'rewrites content' do
expect(new_text).to_not eq text
expect(new_text.length).to eq text.length
end
it 'copies files' do
expect(new_files).to all(exist)
expect(old_paths).to_not match_array new_paths
expect(old_paths).to all(include(old_project.path_with_namespace))
expect(new_paths).to all(include(new_project.path_with_namespace))
end
it 'does not remove old files' do
expect(old_files).to all(exist)
end
it 'generates a new secret for each file' do
expect(new_paths).to_not include image_uploader.secret
expect(new_paths).to_not include zip_uploader.secret
end
end
describe '#needs_rewrite?' do
subject { rewriter.needs_rewrite? }
it { is_expected.to eq true }
end
describe '#files' do
subject { rewriter.files }
it { is_expected.to be_an(Array) }
end
end
end
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
# updated_at :datetime # updated_at :datetime
# home_page_url :string(255) # home_page_url :string(255)
# default_branch_protection :integer default(2) # default_branch_protection :integer default(2)
# twitter_sharing_enabled :boolean default(TRUE)
# restricted_visibility_levels :text # restricted_visibility_levels :text
# version_check_enabled :boolean default(TRUE) # version_check_enabled :boolean default(TRUE)
# max_attachment_size :integer default(10), not null # max_attachment_size :integer default(10), not null
......
...@@ -9,6 +9,7 @@ describe Issue, "Issuable" do ...@@ -9,6 +9,7 @@ describe Issue, "Issuable" do
it { is_expected.to belong_to(:author) } it { is_expected.to belong_to(:author) }
it { is_expected.to belong_to(:assignee) } it { is_expected.to belong_to(:assignee) }
it { is_expected.to have_many(:notes).dependent(:destroy) } it { is_expected.to have_many(:notes).dependent(:destroy) }
it { is_expected.to have_many(:todos).dependent(:destroy) }
end end
describe "Validation" do describe "Validation" do
......
...@@ -104,6 +104,15 @@ describe Project, models: true do ...@@ -104,6 +104,15 @@ describe Project, models: true do
end end
end end
describe 'default_scope' do
it 'excludes projects pending deletion from the results' do
project = create(:empty_project)
create(:empty_project, pending_delete: true)
expect(Project.all).to eq [project]
end
end
describe 'project token' do describe 'project token' do
it 'should set an random token if none provided' do it 'should set an random token if none provided' do
project = FactoryGirl.create :empty_project, runners_token: '' project = FactoryGirl.create :empty_project, runners_token: ''
......
...@@ -303,7 +303,7 @@ describe Repository, models: true do ...@@ -303,7 +303,7 @@ describe Repository, models: true do
describe 'when there are no branches' do describe 'when there are no branches' do
before do before do
allow(repository.raw_repository).to receive(:branch_count).and_return(0) allow(repository).to receive(:branch_count).and_return(0)
end end
it { is_expected.to eq(false) } it { is_expected.to eq(false) }
...@@ -311,13 +311,13 @@ describe Repository, models: true do ...@@ -311,13 +311,13 @@ describe Repository, models: true do
describe 'when there are branches' do describe 'when there are branches' do
it 'returns true' do it 'returns true' do
expect(repository.raw_repository).to receive(:branch_count).and_return(3) expect(repository).to receive(:branch_count).and_return(3)
expect(subject).to eq(true) expect(subject).to eq(true)
end end
it 'caches the output' do it 'caches the output' do
expect(repository.raw_repository).to receive(:branch_count). expect(repository).to receive(:branch_count).
once. once.
and_return(3) and_return(3)
...@@ -436,7 +436,7 @@ describe Repository, models: true do ...@@ -436,7 +436,7 @@ describe Repository, models: true do
it 'expires the visible content cache' do it 'expires the visible content cache' do
repository.has_visible_content? repository.has_visible_content?
expect(repository.raw_repository).to receive(:branch_count). expect(repository).to receive(:branch_count).
once. once.
and_return(0) and_return(0)
...@@ -865,4 +865,21 @@ describe Repository, models: true do ...@@ -865,4 +865,21 @@ describe Repository, models: true do
repository.build_cache repository.build_cache
end end
end end
describe '#local_branches' do
it 'returns the local branches' do
masterrev = repository.find_branch('master').target
create_remote_branch('joe', 'remote_branch', masterrev)
repository.add_branch(user, 'local_branch', masterrev)
expect(repository.local_branches.any? { |branch| branch.name == 'remote_branch' }).to eq(false)
expect(repository.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true)
end
end
def create_remote_branch(remote_name, branch_name, target)
rugged = repository.rugged
rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target)
end
end end
...@@ -173,6 +173,13 @@ describe User, models: true do ...@@ -173,6 +173,13 @@ describe User, models: true do
expect(user).to be_invalid expect(user).to be_invalid
end end
end end
context 'owns_notification_email' do
it 'accepts temp_oauth_email emails' do
user = build(:user, email: "temp-email-for-oauth@example.com")
expect(user).to be_valid
end
end
end end
end end
......
...@@ -948,6 +948,78 @@ describe API::API, api: true do ...@@ -948,6 +948,78 @@ describe API::API, api: true do
end end
end end
describe 'POST /projects/:id/archive' do
context 'on an unarchived project' do
it 'archives the project' do
post api("/projects/#{project.id}/archive", user)
expect(response.status).to eq(201)
expect(json_response['archived']).to be_truthy
end
end
context 'on an archived project' do
before do
project.archive!
end
it 'remains archived' do
post api("/projects/#{project.id}/archive", user)
expect(response.status).to eq(201)
expect(json_response['archived']).to be_truthy
end
end
context 'user without archiving rights to the project' do
before do
project.team << [user3, :developer]
end
it 'rejects the action' do
post api("/projects/#{project.id}/archive", user3)
expect(response.status).to eq(403)
end
end
end
describe 'POST /projects/:id/unarchive' do
context 'on an unarchived project' do
it 'remains unarchived' do
post api("/projects/#{project.id}/unarchive", user)
expect(response.status).to eq(201)
expect(json_response['archived']).to be_falsey
end
end
context 'on an archived project' do
before do
project.archive!
end
it 'unarchives the project' do
post api("/projects/#{project.id}/unarchive", user)
expect(response.status).to eq(201)
expect(json_response['archived']).to be_falsey
end
end
context 'user without archiving rights to the project' do
before do
project.team << [user3, :developer]
end
it 'rejects the action' do
post api("/projects/#{project.id}/unarchive", user3)
expect(response.status).to eq(403)
end
end
end
describe 'DELETE /projects/:id' do describe 'DELETE /projects/:id' do
context 'when authenticated as user' do context 'when authenticated as user' do
it 'should remove project' do it 'should remove project' do
......
...@@ -160,6 +160,20 @@ describe Issues::MoveService, services: true do ...@@ -160,6 +160,20 @@ describe Issues::MoveService, services: true do
.to eq "Note with reference to merge request #{old_project.to_reference}!1" .to eq "Note with reference to merge request #{old_project.to_reference}!1"
end end
end end
context 'issue description with uploads' do
let(:uploader) { build(:file_uploader, project: old_project) }
let(:description) { "Text and #{uploader.to_markdown}" }
include_context 'issue move executed'
it 'rewrites uploads in description' do
expect(new_issue.description).to_not eq description
expect(new_issue.description)
.to match(/Text and #{FileUploader::MARKDOWN_PATTERN}/)
expect(new_issue.description).to_not include uploader.secret
end
end
end end
describe 'rewritting references' do describe 'rewritting references' do
......
require 'spec_helper'
describe Projects::UnlinkForkService, services: true do
subject { Projects::UnlinkForkService.new(fork_project, user) }
let(:fork_link) { create(:forked_project_link) }
let(:fork_project) { fork_link.forked_to_project }
let(:user) { create(:user) }
context 'with opened merge request on the source project' do
let(:merge_request) { create(:merge_request, source_project: fork_project, target_project: fork_link.forked_from_project) }
let(:mr_close_service) { MergeRequests::CloseService.new(fork_project, user) }
before do
allow(MergeRequests::CloseService).to receive(:new).
with(fork_project, user).
and_return(mr_close_service)
end
it 'close all pending merge requests' do
expect(mr_close_service).to receive(:execute).with(merge_request)
subject.execute
end
end
it 'remove fork relation' do
expect(fork_project.forked_project_link).to receive(:destroy)
subject.execute
end
end
...@@ -2,22 +2,25 @@ require 'spec_helper' ...@@ -2,22 +2,25 @@ require 'spec_helper'
describe TodoService, services: true do describe TodoService, services: true do
let(:author) { create(:user) } let(:author) { create(:user) }
let(:john_doe) { create(:user, username: 'john_doe') } let(:assignee) { create(:user) }
let(:michael) { create(:user, username: 'michael') } let(:non_member) { create(:user) }
let(:stranger) { create(:user, username: 'stranger') } let(:member) { create(:user) }
let(:admin) { create(:admin) }
let(:john_doe) { create(:user) }
let(:project) { create(:project) } let(:project) { create(:project) }
let(:mentions) { [author.to_reference, john_doe.to_reference, michael.to_reference, stranger.to_reference].join(' ') } let(:mentions) { [author, assignee, john_doe, member, non_member, admin].map(&:to_reference).join(' ') }
let(:service) { described_class.new } let(:service) { described_class.new }
before do before do
project.team << [author, :developer] project.team << [author, :developer]
project.team << [member, :developer]
project.team << [john_doe, :developer] project.team << [john_doe, :developer]
project.team << [michael, :developer]
end end
describe 'Issues' do describe 'Issues' do
let(:issue) { create(:issue, project: project, assignee: john_doe, author: author, description: mentions) } let(:issue) { create(:issue, project: project, assignee: john_doe, author: author, description: mentions) }
let(:unassigned_issue) { create(:issue, project: project, assignee: nil) } let(:unassigned_issue) { create(:issue, project: project, assignee: nil) }
let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee, description: mentions) }
describe '#new_issue' do describe '#new_issue' do
it 'creates a todo if assigned' do it 'creates a todo if assigned' do
...@@ -37,10 +40,20 @@ describe TodoService, services: true do ...@@ -37,10 +40,20 @@ describe TodoService, services: true do
it 'creates a todo for each valid mentioned user' do it 'creates a todo for each valid mentioned user' do
service.new_issue(issue, author) service.new_issue(issue, author)
should_create_todo(user: michael, target: issue, action: Todo::MENTIONED) should_create_todo(user: member, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: stranger, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
end
it 'does not create todo for non project members when issue is confidential' do
service.new_issue(confidential_issue, john_doe)
should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::ASSIGNED)
should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
end end
end end
...@@ -48,16 +61,26 @@ describe TodoService, services: true do ...@@ -48,16 +61,26 @@ describe TodoService, services: true do
it 'creates a todo for each valid mentioned user' do it 'creates a todo for each valid mentioned user' do
service.update_issue(issue, author) service.update_issue(issue, author)
should_create_todo(user: michael, target: issue, action: Todo::MENTIONED) should_create_todo(user: member, target: issue, action: Todo::MENTIONED)
should_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED) should_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: stranger, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
end end
it 'does not create a todo if user was already mentioned' do it 'does not create a todo if user was already mentioned' do
create(:todo, :mentioned, user: michael, project: project, target: issue, author: author) create(:todo, :mentioned, user: member, project: project, target: issue, author: author)
expect { service.update_issue(issue, author) }.not_to change(michael.todos, :count) expect { service.update_issue(issue, author) }.not_to change(member.todos, :count)
end
it 'does not create todo for non project members when issue is confidential' do
service.update_issue(confidential_issue, john_doe)
should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
end end
end end
...@@ -109,8 +132,10 @@ describe TodoService, services: true do ...@@ -109,8 +132,10 @@ describe TodoService, services: true do
describe '#new_note' do describe '#new_note' do
let!(:first_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) } let!(:first_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) }
let!(:second_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) } let!(:second_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) }
let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee) }
let(:note) { create(:note, project: project, noteable: issue, author: john_doe, note: mentions) } let(:note) { create(:note, project: project, noteable: issue, author: john_doe, note: mentions) }
let(:note_on_commit) { create(:note_on_commit, project: project, author: john_doe, note: mentions) } let(:note_on_commit) { create(:note_on_commit, project: project, author: john_doe, note: mentions) }
let(:note_on_confidential_issue) { create(:note_on_issue, noteable: confidential_issue, project: project, note: mentions) }
let(:note_on_project_snippet) { create(:note_on_project_snippet, project: project, author: john_doe, note: mentions) } let(:note_on_project_snippet) { create(:note_on_project_snippet, project: project, author: john_doe, note: mentions) }
let(:award_note) { create(:note, :award, project: project, noteable: issue, author: john_doe, note: 'thumbsup') } let(:award_note) { create(:note, :award, project: project, noteable: issue, author: john_doe, note: 'thumbsup') }
let(:system_note) { create(:system_note, project: project, noteable: issue) } let(:system_note) { create(:system_note, project: project, noteable: issue) }
...@@ -142,19 +167,29 @@ describe TodoService, services: true do ...@@ -142,19 +167,29 @@ describe TodoService, services: true do
it 'creates a todo for each valid mentioned user' do it 'creates a todo for each valid mentioned user' do
service.new_note(note, john_doe) service.new_note(note, john_doe)
should_create_todo(user: michael, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) should_create_todo(user: member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
should_create_todo(user: author, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) should_create_todo(user: author, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
should_not_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) should_not_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
should_not_create_todo(user: stranger, target: issue, author: john_doe, action: Todo::MENTIONED, note: note) should_not_create_todo(user: non_member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
end
it 'does not create todo for non project members when leaving a note on a confidential issue' do
service.new_note(note_on_confidential_issue, john_doe)
should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
end end
it 'creates a todo for each valid mentioned user when leaving a note on commit' do it 'creates a todo for each valid mentioned user when leaving a note on commit' do
service.new_note(note_on_commit, john_doe) service.new_note(note_on_commit, john_doe)
should_create_todo(user: michael, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) should_create_todo(user: member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
should_not_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) should_not_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
should_not_create_todo(user: stranger, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit) should_not_create_todo(user: non_member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
end end
it 'does not create todo when leaving a note on snippet' do it 'does not create todo when leaving a note on snippet' do
...@@ -185,10 +220,10 @@ describe TodoService, services: true do ...@@ -185,10 +220,10 @@ describe TodoService, services: true do
it 'creates a todo for each valid mentioned user' do it 'creates a todo for each valid mentioned user' do
service.new_merge_request(mr_assigned, author) service.new_merge_request(mr_assigned, author)
should_create_todo(user: michael, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: stranger, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED)
end end
end end
...@@ -196,16 +231,16 @@ describe TodoService, services: true do ...@@ -196,16 +231,16 @@ describe TodoService, services: true do
it 'creates a todo for each valid mentioned user' do it 'creates a todo for each valid mentioned user' do
service.update_merge_request(mr_assigned, author) service.update_merge_request(mr_assigned, author)
should_create_todo(user: michael, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED)
should_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: stranger, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED)
end end
it 'does not create a todo if user was already mentioned' do it 'does not create a todo if user was already mentioned' do
create(:todo, :mentioned, user: michael, project: project, target: mr_assigned, author: author) create(:todo, :mentioned, user: member, project: project, target: mr_assigned, author: author)
expect { service.update_merge_request(mr_assigned, author) }.not_to change(michael.todos, :count) expect { service.update_merge_request(mr_assigned, author) }.not_to change(member.todos, :count)
end end
end end
......
...@@ -78,6 +78,6 @@ module FilterSpecHelper ...@@ -78,6 +78,6 @@ module FilterSpecHelper
# Shortcut to Rails' auto-generated routes helpers, to avoid including the # Shortcut to Rails' auto-generated routes helpers, to avoid including the
# module # module
def urls def urls
Gitlab::Application.routes.url_helpers Gitlab::Routing.url_helpers
end end
end end
...@@ -106,7 +106,7 @@ class MarkdownFeature ...@@ -106,7 +106,7 @@ class MarkdownFeature
end end
def urls def urls
Gitlab::Application.routes.url_helpers Gitlab::Routing.url_helpers
end end
def raw_markdown def raw_markdown
......
...@@ -15,6 +15,7 @@ module TestEnv ...@@ -15,6 +15,7 @@ module TestEnv
'lfs' => 'be93687', 'lfs' => 'be93687',
'master' => '5937ac0', 'master' => '5937ac0',
"'test'" => 'e56497b', "'test'" => 'e56497b',
'orphaned-branch' => '45127a9',
} }
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
......
...@@ -22,6 +22,8 @@ describe MergeWorker do ...@@ -22,6 +22,8 @@ describe MergeWorker do
merge_request.reload merge_request.reload
expect(merge_request).to be_merged expect(merge_request).to be_merged
source_project.repository.expire_branches_cache
expect(source_project.repository.branch_names).not_to include('markdown') expect(source_project.repository.branch_names).not_to include('markdown')
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment