Commit 065e0c0f authored by Kamil Trzcinski's avatar Kamil Trzcinski

Merge remote-tracking branch 'origin/master' into ci-commit-as-pipeline

# Conflicts:
#	db/schema.rb
parents 5117412e 05920a79
Please view this file on the master branch, on stable branches it's out of date.
v 8.7.0 (unreleased)
- All service classes (those residing in app/services) are now instrumented (Yorick Peterse)
- Developers can now add custom tags to transactions (Yorick Peterse)
- The Projects::HousekeepingService class has extra instrumentation
- All service classes (those residing in app/services) are now instrumented
- Developers can now add custom tags to transactions
- Loading of an issue's referenced merge requests and related branches is now done asynchronously
- Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea)
- Project switcher uses new dropdown styling
- Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea)
- Do not include award_emojis in issue and merge_request comment_count !3610 (Lucas Charles)
- All images in discussions and wikis now link to their source files !3464 (Connor Shea).
- Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu)
- Add setting for customizing the list of trusted proxies !3524
- Allow projects to be transfered to a lower visibility level group
- Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524
- Improved Markdown rendering performance !3389 (Yorick Peterse)
- Improved Markdown rendering performance !3389
- Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu)
- API: Ability to subscribe and unsubscribe from issues and merge requests (Robert Schilling)
- Expose project badges in project settings
- Make /profile/keys/new redirect to /profile/keys for back-compat. !3717
- Preserve time notes/comments have been updated at when moving issue
- Make HTTP(s) label consistent on clone bar (Stan Hu)
- Expose label description in API (Mariusz Jachimowicz)
- Allow back dating on issues when created through the API
- API: Ability to update a group (Robert Schilling)
- API: Ability to move issues (Robert Schilling)
- Fix Error 500 after renaming a project path (Stan Hu)
- Fix a bug whith trailing slash in teamcity_url (Charles May)
- Allow back dating on issues when created or updated through the API
- Allow back dating on issue notes when created through the API
- Fix avatar stretching by providing a cropping feature
- API: Expose `subscribed` for issues and merge requests (Robert Schilling)
- Allow SAML to handle external users based on user's information !3530
- Allow Omniauth providers to be marked as `external` !3657
- Add endpoints to archive or unarchive a project !3372
- Fix a bug whith trailing slash in bamboo_url
- Add links to CI setup documentation from project settings and builds pages
- Handle nil descriptions in Slack issue messages (Stan Hu)
- Add automated repository integrity checks
- API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling)
- API: Ability to star and unstar a project (Robert Schilling)
- Add default scope to projects to exclude projects pending deletion
- Allow to close merge requests which source projects(forks) are deleted.
- Ensure empty recipients are rejected in BuildsEmailService
- Use rugged to change HEAD in Project#change_head (P.S.V.R)
- API: Ability to filter milestones by state `active` and `closed` (Robert Schilling)
- API: Fix milestone filtering by `iid` (Robert Schilling)
- API: Delete notes of issues, snippets, and merge requests (Robert Schilling)
- Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.)
- Better errors handling when creating milestones inside groups
- Fix high CPU usage when PostReceive receives refs/merge-requests/<id>
- Hide `Create a group` help block when creating a new project in a group
- Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.)
- Allow issues and merge requests to be assigned to the author !2765
- Gracefully handle notes on deleted commits in merge requests (Stan Hu)
- Decouple membership and notifications
- Fix creation of merge requests for orphaned branches (Stan Hu)
......@@ -52,10 +67,19 @@ v 8.7.0 (unreleased)
- API: Expose 'updated_at' for issue, snippet, and merge request notes (Robert Schilling)
- API: User can leave a project through the API when not master or owner. !3613
- Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu)
- Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld)
- Improved markdown forms
- Delete tags using Rugged for performance reasons (Robert Schilling)
- Diffs load at the correct point when linking from from number
- Selected diff rows highlight
- Fix emoji categories in the emoji picker
- Add encrypted credentials for imported projects and migrate old ones
- Author and participants are displayed first on users autocompletion
v 8.6.6
- Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk)
- Project switcher uses new dropdown styling
- Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413
- Fix error on language detection when repository has no HEAD (e.g., master branch) (Jeroen Bobbeldijk). !3654
- Fix revoking of authorized OAuth applications (Connor Shea). !3690
v 8.6.5
- Fix importing from GitHub Enterprise. !3529
......@@ -255,7 +279,7 @@ v 8.5.1
v 8.5.0
- Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu)
- Cache various Repository methods to improve performance (Yorick Peterse)
- Cache various Repository methods to improve performance
- Fix duplicated branch creation/deletion Webhooks/service notifications when using Web UI (Stan Hu)
- Ensure rake tasks that don't need a DB connection can be run without one
- Update New Relic gem to 3.14.1.311 (Stan Hu)
......
......@@ -22,7 +22,17 @@
#= require cal-heatmap
#= require turbolinks
#= require autosave
#= require bootstrap
#= require bootstrap/affix
#= require bootstrap/alert
#= require bootstrap/button
#= require bootstrap/collapse
#= require bootstrap/dropdown
#= require bootstrap/modal
#= require bootstrap/scrollspy
#= require bootstrap/tab
#= require bootstrap/transition
#= require bootstrap/tooltip
#= require bootstrap/popover
#= require select2
#= require raphael
#= require g.raphael
......
......@@ -29,7 +29,11 @@ $(document).on 'keydown.quick_submit', '.js-quick-submit', (e) ->
e.preventDefault()
$form = $(e.target).closest('form')
$form.find('input[type=submit], button[type=submit]').disable()
$submit_button = $form.find('input[type=submit], button[type=submit]')
return if $submit_button.attr('disabled')
$submit_button.disable()
$form.submit()
# If the user tabs to a submit button on a `js-quick-submit` form, display a
......
......@@ -28,26 +28,26 @@ class Dispatcher
new Todos()
when 'projects:milestones:new', 'projects:milestones:edit'
new ZenMode()
new DropzoneInput($('.milestone-form'))
new GLForm($('.milestone-form'))
when 'groups:milestones:new'
new ZenMode()
when 'projects:compare:show'
new Diff()
when 'projects:issues:new','projects:issues:edit'
shortcut_handler = new ShortcutsNavigation()
new DropzoneInput($('.issue-form'))
new GLForm($('.issue-form'))
new IssuableForm($('.issue-form'))
when 'projects:merge_requests:new', 'projects:merge_requests:edit'
new Diff()
shortcut_handler = new ShortcutsNavigation()
new DropzoneInput($('.merge-request-form'))
new GLForm($('.merge-request-form'))
new IssuableForm($('.merge-request-form'))
when 'projects:tags:new'
new ZenMode()
new DropzoneInput($('.tag-form'))
new GLForm($('.tag-form'))
when 'projects:releases:edit'
new ZenMode()
new DropzoneInput($('.release-form'))
new GLForm($('.release-form'))
when 'projects:merge_requests:show'
new Diff()
shortcut_handler = new ShortcutsIssuable(true)
......@@ -137,7 +137,7 @@ class Dispatcher
new Wikis()
shortcut_handler = new ShortcutsNavigation()
new ZenMode()
new DropzoneInput($('.wiki-form'))
new GLForm($('.wiki-form'))
when 'snippets'
shortcut_handler = new ShortcutsNavigation()
new ZenMode() if path[2] == 'show'
......
......@@ -15,11 +15,13 @@ class @DropzoneInput
project_uploads_path = window.project_uploads_path or null
max_file_size = gon.max_file_size or 10
form_textarea = $(form).find("textarea.markdown-area")
form_textarea = $(form).find(".js-gfm-input")
form_textarea.wrap "<div class=\"div-dropzone\"></div>"
form_textarea.on 'paste', (event) =>
handlePaste(event)
$mdArea = $(form_textarea).closest('.md-area')
$(form).setupMarkdownPreview()
form_dropzone = $(form).find('.div-dropzone')
......@@ -49,17 +51,16 @@ class @DropzoneInput
$(".div-dropzone-alert").alert "close"
dragover: ->
form_textarea.addClass "div-dropzone-focus"
$mdArea.addClass 'is-dropzone-hover'
form.find(".div-dropzone-hover").css "opacity", 0.7
return
dragleave: ->
form_textarea.removeClass "div-dropzone-focus"
$mdArea.removeClass 'is-dropzone-hover'
form.find(".div-dropzone-hover").css "opacity", 0
return
drop: ->
form_textarea.removeClass "div-dropzone-focus"
form.find(".div-dropzone-hover").css "opacity", 0
form_textarea.focus()
return
......
......@@ -2,6 +2,8 @@
window.GitLab ?= {}
GitLab.GfmAutoComplete =
dataLoading: false
dataSource: ''
# Emoji
......@@ -17,17 +19,41 @@ GitLab.GfmAutoComplete =
template: '<li><small>${id}</small> ${title}</li>'
# Add GFM auto-completion to all input fields, that accept GFM input.
setup: ->
input = $('.js-gfm-input')
setup: (wrap) ->
@input = $('.js-gfm-input')
# destroy previous instances
@destroyAtWho()
# set up instances
@setupAtWho()
if @dataSource
if !@dataLoading
@dataLoading = true
# We should wait until initializations are done
# and only trigger the last .setup since
# The previous .dataSource belongs to the previous issuable
# and the last one will have the **proper** .dataSource property
# TODO: Make this a singleton and turn off events when moving to another page
setTimeout( =>
fetch = @fetchData(@dataSource)
fetch.done (data) =>
@dataLoading = false
@loadData(data)
, 1000)
setupAtWho: ->
# Emoji
input.atwho
@input.atwho
at: ':'
displayTpl: @Emoji.template
insertTpl: ':${name}:'
# Team Members
input.atwho
@input.atwho
at: '@'
displayTpl: @Members.template
insertTpl: '${atwho-at}${username}'
......@@ -42,7 +68,7 @@ GitLab.GfmAutoComplete =
title: sanitize(title)
search: sanitize("#{m.username} #{m.name}")
input.atwho
@input.atwho
at: '#'
alias: 'issues'
searchKey: 'search'
......@@ -55,7 +81,7 @@ GitLab.GfmAutoComplete =
title: sanitize(i.title)
search: "#{i.iid} #{i.title}"
input.atwho
@input.atwho
at: '!'
alias: 'mergerequests'
searchKey: 'search'
......@@ -68,13 +94,18 @@ GitLab.GfmAutoComplete =
title: sanitize(m.title)
search: "#{m.iid} #{m.title}"
if @dataSource
$.getJSON(@dataSource).done (data) ->
# load members
input.atwho 'load', '@', data.members
# load issues
input.atwho 'load', 'issues', data.issues
# load merge requests
input.atwho 'load', 'mergerequests', data.mergerequests
# load emojis
input.atwho 'load', ':', data.emojis
destroyAtWho: ->
@input.atwho('destroy')
fetchData: (dataSource) ->
$.getJSON(dataSource)
loadData: (data) ->
# load members
@input.atwho 'load', '@', data.members
# load issues
@input.atwho 'load', 'issues', data.issues
# load merge requests
@input.atwho 'load', 'mergerequests', data.mergerequests
# load emojis
@input.atwho 'load', ':', data.emojis
class @GLForm
constructor: (@form) ->
@textarea = @form.find('textarea.js-gfm-input')
# Before we start, we should clean up any previous data for this form
@destroy()
# Setup the form
@setupForm()
@form.data 'gl-form', @
destroy: ->
# Clean form listeners
@clearEventListeners()
@form.data 'gl-form', null
setupForm: ->
isNewForm = @form.is(':not(.gfm-form)')
@form.removeClass 'js-new-note-form'
if isNewForm
@form.find('.div-dropzone').remove()
@form.addClass('gfm-form')
disableButtonIfEmptyField @form.find('.js-note-text'), @form.find('.js-comment-button')
# remove notify commit author checkbox for non-commit notes
GitLab.GfmAutoComplete.setup()
new DropzoneInput(@form)
autosize(@textarea)
# form and textarea event listeners
@addEventListeners()
# hide discard button
@form.find('.js-note-discard').hide()
@form.show()
clearEventListeners: ->
@textarea.off 'focus'
@textarea.off 'blur'
addEventListeners: ->
@textarea.on 'focus', ->
$(@).closest('.md-area').addClass 'is-focused'
@textarea.on 'blur', ->
$(@).closest('.md-area').removeClass 'is-focused'
......@@ -10,6 +10,9 @@ class @Issue
@initTaskList()
@initIssueBtnEventListeners()
@initMergeRequests()
@initRelatedBranches()
initTaskList: ->
$('.detail-page-description .js-task-list-container').taskList('enable')
$(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
......@@ -69,3 +72,23 @@ class @Issue
type: 'PATCH'
url: $('form.js-issuable-update').attr('action')
data: patchData
initMergeRequests: ->
$container = $('#merge-requests')
$.getJSON($container.data('url'))
.error ->
new Flash('Failed to load referenced merge requests', 'alert')
.success (data) ->
if 'html' of data
$container.html(data.html)
initRelatedBranches: ->
$container = $('#related-branches')
$.getJSON($container.data('url'))
.error ->
new Flash('Failed to load related branches', 'alert')
.success (data) ->
if 'html' of data
$container.html(data.html)
......@@ -34,7 +34,7 @@ class @LabelsSelect
labelHTMLTemplate = _.template(
'<% _.each(labels, function(label){ %>
<a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name=<%= label.title %>">
<span class="label color-label" style="background-color: <%= label.color %>;">
<span class="label has-tooltip color-label" title="<%= label.description %>" style="background-color: <%= label.color %>;">
<%= label.title %>
</span>
</a>
......@@ -165,6 +165,8 @@ class @LabelsSelect
.html(template)
$sidebarCollapsedValue.text(labelCount)
$('.has-tooltip', $value).tooltip(container: 'body')
$value
.find('a')
.each((i) ->
......
......@@ -85,8 +85,10 @@ class @MergeRequestTabs
scrollToElement: (container) ->
if window.location.hash
$el = $("div#{container} #{window.location.hash}")
$('body').scrollTo($el.offset().top) if $el.length
navBarHeight = $('.navbar-gitlab').outerHeight()
$el = $("#{container} #{window.location.hash}")
$.scrollTo("#{container} #{window.location.hash}", offset: -navBarHeight) if $el.length
# Activate a tab based on the current action
activateTab: (action) ->
......@@ -152,12 +154,38 @@ class @MergeRequestTabs
@_get
url: "#{source}.json" + @_location.search
success: (data) =>
document.querySelector("div#diffs").innerHTML = data.html
$('#diffs').html data.html
gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'))
$('div#diffs .js-syntax-highlight').syntaxHighlight()
$('#diffs .js-syntax-highlight').syntaxHighlight()
@expandViewContainer() if @diffViewType() is 'parallel'
@diffsLoaded = true
@scrollToElement("#diffs")
@highlighSelectedLine()
$(document)
.off 'click', '.diff-line-num a'
.on 'click', '.diff-line-num a', (e) =>
e.preventDefault()
window.location.hash = $(e.currentTarget).attr 'href'
@highlighSelectedLine()
@scrollToElement("#diffs")
highlighSelectedLine: ->
$('.hll').removeClass 'hll'
locationHash = window.location.hash
if locationHash isnt ''
hashClassString = ".#{locationHash.replace('#', '')}"
$diffLine = $(locationHash)
if $diffLine.is ':not(tr)'
$diffLine = $("td#{locationHash}, td#{hashClassString}")
else
$diffLine = $('td', $diffLine)
$diffLine.addClass 'hll'
diffLineTop = $diffLine.offset().top
navBarHeight = $('.navbar-gitlab').outerHeight()
loadBuilds: (source) ->
return if @buildsLoaded
......
......@@ -283,32 +283,10 @@ class @Notes
show the form
###
setupNoteForm: (form) ->
disableButtonIfEmptyField form.find(".js-note-text"), form.find(".js-comment-button")
form.removeClass "js-new-note-form"
form.find('.div-dropzone').remove()
# hide discard button
form.find('.js-note-discard').hide()
# setup preview buttons
previewButton = form.find(".js-md-preview-button")
new GLForm form
textarea = form.find(".js-note-text")
textarea.on "input", ->
if $(this).val().trim() isnt ""
previewButton.removeClass("turn-off").addClass "turn-on"
else
previewButton.removeClass("turn-on").addClass "turn-off"
textarea.on 'focus', ->
$(this).closest('.md-area').addClass 'is-focused'
textarea.on 'blur', ->
$(this).closest('.md-area').removeClass 'is-focused'
autosize(textarea)
new Autosave textarea, [
"Note"
form.find("#note_commit_id").val()
......@@ -317,11 +295,6 @@ class @Notes
form.find("#note_noteable_id").val()
]
# remove notify commit author checkbox for non-commit notes
GitLab.GfmAutoComplete.setup()
new DropzoneInput(form)
form.show()
###
Called in response to the new note form being submitted
......@@ -375,34 +348,15 @@ class @Notes
note = $(this).closest(".note")
note.addClass "is-editting"
form = note.find(".note-edit-form")
isNewForm = form.is(':not(.gfm-form)')
if isNewForm
form.addClass('gfm-form')
form.addClass('current-note-edit-form')
# Show the attachment delete link
note.find(".js-note-attachment-delete").show()
# Setup markdown form
if isNewForm
GitLab.GfmAutoComplete.setup()
new DropzoneInput(form)
textarea = form.find("textarea")
textarea.focus()
if isNewForm
autosize(textarea)
# HACK (rspeicher/DouweM): Work around a Chrome 43 bug(?).
# The textarea has the correct value, Chrome just won't show it unless we
# modify it, so let's clear it and re-set it!
value = textarea.val()
textarea.val ""
textarea.val value
new GLForm form
if isNewForm
disableButtonIfEmptyField textarea, form.find(".js-comment-button")
form.find(".js-note-text").focus()
###
Called in response to clicking the edit note link
......@@ -559,6 +513,9 @@ class @Notes
removeDiscussionNoteForm: (form)->
row = form.closest("tr")
glForm = form.data 'gl-form'
glForm.destroy()
form.find(".js-note-text").data("autosave").reset()
# show the reply button (will only work for replies)
......@@ -570,7 +527,6 @@ class @Notes
# only remove the form
form.remove()
cancelDiscussionForm: (e) =>
e.preventDefault()
form = $(e.target).closest(".js-discussion-note-form")
......
......@@ -2,7 +2,7 @@ class @Subscription
constructor: (container) ->
$container = $(container)
@url = $container.attr('data-url')
@subscribe_button = $container.find('.subscribe-button')
@subscribe_button = $container.find('.js-subscribe-button')
@subscription_status = $container.find('.subscription-status')
@subscribe_button.unbind('click').click(@toggleSubscription)
......
......@@ -12,6 +12,7 @@ class @UsersSelect
showNullUser = $dropdown.data('null-user')
showAnyUser = $dropdown.data('any-user')
firstUser = $dropdown.data('first-user')
@authorId = $dropdown.data('author-id')
selectedId = $dropdown.data('selected')
defaultLabel = $dropdown.data('default-label')
issueURL = $dropdown.data('issueUpdate')
......@@ -207,6 +208,7 @@ class @UsersSelect
@projectId = $(select).data('project-id')
@groupId = $(select).data('group-id')
@showCurrentUser = $(select).data('current-user')
@authorId = $(select).data('author-id')
showNullUser = $(select).data('null-user')
showAnyUser = $(select).data('any-user')
showEmailUser = $(select).data('email-user')
......@@ -312,6 +314,7 @@ class @UsersSelect
project_id: @projectId
group_id: @groupId
current_user: @showCurrentUser
author_id: @authorId
dataType: "json"
).done (users) ->
callback(users)
......
/**
* Styles that apply to all GFM related forms.
*/
.issue-form, .merge-request-form, .wiki-form {
.description {
height: 16em;
border-top-left-radius: 0;
}
}
.wiki-form {
.description {
height: 26em;
}
}
.milestone-form {
.description {
height: 14em;
}
}
.gfm-commit, .gfm-commit_range {
font-family: $monospace_font;
......
.div-dropzone-wrapper {
.div-dropzone {
position: relative;
margin-bottom: -5px;
.div-dropzone-focus {
border-color: #66afe9 !important;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6) !important;
outline: 0 !important;
}
.div-dropzone-hover {
position: absolute;
top: 50%;
left: 50%;
margin-top: -0.5em;
margin-left: -0.6em;
margin-top: -11.5px;
margin-left: -15px;
opacity: 0;
font-size: 50px;
font-size: 30px;
transition: opacity 200ms ease-in-out;
pointer-events: none;
}
......
......@@ -43,7 +43,6 @@
@import "bootstrap/modals";
@import "bootstrap/tooltip";
@import "bootstrap/popovers";
@import "bootstrap/carousel";
// Utility classes
.clearfix {
......
......@@ -250,14 +250,6 @@ a > code {
* Textareas intended for GFM
*
*/
.js-gfm-input {
font-family: $monospace_font;
color: $gl-text-color;
}
.md-preview {
}
.strikethrough {
text-decoration: line-through;
}
......
......@@ -150,6 +150,7 @@ $light-grey-header: #faf9f9;
*/
$gl-primary: $blue-normal;
$gl-success: $green-normal;
$gl-success-focus: rgba($gl-success, .4);
$gl-info: $blue-normal;
$gl-warning: $orange-normal;
$gl-danger: $red-normal;
......
......@@ -21,6 +21,12 @@
// Diff line
.line_holder {
td.diff-line-num.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) {
background-color: #557;
border-color: darken(#557, 15%);
}
.diff-line-num.new, .line_content.new {
@include diff_background(rgba(51, 255, 51, 0.1), rgba(51, 255, 51, 0.2), #808080);
}
......
......@@ -21,6 +21,12 @@
// Diff line
.line_holder {
td.diff-line-num.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) {
background-color: #49483e;
border-color: darken(#49483e, 15%);
}
.diff-line-num.new, .line_content.new {
@include diff_background(rgba(166, 226, 46, 0.1), rgba(166, 226, 46, 0.15), #808080);
}
......
......@@ -21,6 +21,12 @@
// Diff line
.line_holder {
td.diff-line-num.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) {
background-color: #174652;
border-color: darken(#174652, 15%);
}
.diff-line-num.new, .line_content.new {
@include diff_background(rgba(133, 153, 0, 0.15), rgba(133, 153, 0, 0.25), #113b46);
}
......
......@@ -21,6 +21,12 @@
// Diff line
.line_holder {
td.diff-line-num.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) {
background-color: #ddd8c5;
border-color: darken(#ddd8c5, 15%);
}
.diff-line-num.new, .line_content.new {
@include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.25), #c5d0d4);
}
......
......@@ -21,6 +21,12 @@
// Diff line
.line_holder {
td.diff-line-num.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) {
background-color: #f8eec7;
border-color: darken(#f8eec7, 15%);
}
.diff-line-num {
&.old {
background-color: $line-number-old;
......
......@@ -75,6 +75,11 @@ li.commit {
}
}
.item-title {
display: inline-block;
max-width: 70%;
}
.commit-row-description {
font-size: 14px;
border-left: 1px solid #eee;
......
......@@ -67,6 +67,24 @@
line-height: $code_line_height;
font-size: $code_font_size;
&.noteable_line {
position: relative;
&.old {
&:before {
content: '-';
position: absolute;
}
}
&.new {
&:before {
content: '+';
position: absolute;
}
}
}
span {
white-space: pre;
}
......@@ -391,3 +409,23 @@
margin-bottom: 0;
}
}
.file-holder {
.diff-line-num:not(.js-unfold-bottom) {
a {
&:before {
content: attr(data-linenumber);
}
}
}
}
.discussion {
.diff-content {
.diff-line-num {
&:before {
content: attr(data-linenumber);
}
}
}
}
......@@ -173,12 +173,6 @@
}
}
.subscribe-button {
span {
margin-top: 0;
}
}
&.right-sidebar-collapsed {
/* Extra small devices (phones, less than 768px) */
display: none;
......@@ -322,3 +316,9 @@
color: #8c8c8c;
}
}
.issuable-form-padding-top {
@media (min-width: $screen-sm-min) {
padding-top: 7px;
}
}
......@@ -79,19 +79,30 @@
color: $white-light;
}
@mixin labels-mobile {
@media (max-width: $screen-xs-min) {
display: block;
width: 100%;
margin-left: 0;
padding: 10px 0;
}
}
.manage-labels-list {
.prepend-left-10 {
.prepend-left-10, .prepend-description-left {
display: inline-block;
width: 40%;
vertical-align: middle;
@media (max-width: $screen-xs-min) {
display: block;
width: 100%;
margin-left: 0;
padding: 10px 0;
}
@include labels-mobile;
}
.prepend-description-left {
width: 57%;
@include labels-mobile;
}
.pull-info-right {
......@@ -106,7 +117,7 @@
padding: 6px;
color: $gl-text-color;
&.subscribe-button {
&.label-subscribe-button {
padding-left: 0;
}
}
......
......@@ -40,6 +40,7 @@
}
.note-textarea {
display: block;
padding: 10px 0;
font-family: $regular_font;
border: 0;
......@@ -63,7 +64,7 @@
&.is-focused {
border-color: $focus-border-color;
box-shadow: 0 0 2px rgba(#000, .2),
box-shadow: 0 0 2px $black-transparent,
0 0 4px rgba($focus-border-color, .4);
.comment-toolbar,
......@@ -72,6 +73,17 @@
}
}
&.is-dropzone-hover {
border-color: $gl-success;
box-shadow: 0 0 2px $black-transparent,
0 0 4px $gl-success-focus;
.comment-toolbar,
.nav-links {
border-color: $gl-success;
}
}
p {
code {
white-space: normal;
......
......@@ -276,8 +276,7 @@ ul.notes {
.diff-file tr.line_holder {
@mixin show-add-diff-note {
filter: alpha(opacity=100);
opacity: 1.0;
display: inline-block;
}
.add-diff-note {
......@@ -291,13 +290,8 @@ ul.notes {
position: absolute;
z-index: 10;
width: 32px;
transition: all 0.2s ease;
// "hide" it by default
opacity: 0.0;
filter: alpha(opacity=0);
display: none;
&:hover {
background: $gl-info;
color: #fff;
......
......@@ -19,6 +19,15 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
redirect_to admin_runners_path
end
def clear_repository_check_states
RepositoryCheck::ClearWorker.perform_async
redirect_to(
admin_application_settings_path,
notice: 'Started asynchronous removal of all repository check states.'
)
end
private
def set_application_setting
......@@ -82,6 +91,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:akismet_enabled,
:akismet_api_key,
:email_author_in_body,
:repository_checks_enabled,
restricted_visibility_levels: [],
import_sources: []
)
......
class Admin::ProjectsController < Admin::ApplicationController
before_action :project, only: [:show, :transfer]
before_action :project, only: [:show, :transfer, :repository_check]
before_action :group, only: [:show, :transfer]
def index
......@@ -8,6 +8,7 @@ class Admin::ProjectsController < Admin::ApplicationController
@projects = @projects.where("projects.visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present?
@projects = @projects.with_push if params[:with_push].present?
@projects = @projects.abandoned if params[:abandoned].present?
@projects = @projects.where(last_repository_check_failed: true) if params[:last_repository_check_failed].present?
@projects = @projects.non_archived unless params[:with_archived].present?
@projects = @projects.search(params[:name]) if params[:name].present?
@projects = @projects.sort(@sort = params[:sort])
......@@ -30,6 +31,15 @@ class Admin::ProjectsController < Admin::ApplicationController
redirect_to admin_namespace_project_path(@project.namespace, @project)
end
def repository_check
RepositoryCheck::SingleRepositoryWorker.perform_async(@project.id)
redirect_to(
admin_namespace_project_path(@project.namespace, @project),
notice: 'Repository check was triggered.'
)
end
protected
def project
......
......@@ -3,6 +3,7 @@ require 'fogbugz'
class ApplicationController < ActionController::Base
include Gitlab::CurrentSettings
include Gitlab::GonHelper
include GitlabRoutingHelper
include PageLayoutHelper
......@@ -13,7 +14,7 @@ class ApplicationController < ActionController::Base
before_action :check_password_expiration
before_action :check_2fa_requirement
before_action :ldap_security_check
before_action :sentry_user_context
before_action :sentry_context
before_action :default_headers
before_action :add_gon_variables
before_action :configure_permitted_parameters, if: :devise_controller?
......@@ -40,13 +41,15 @@ class ApplicationController < ActionController::Base
protected
def sentry_user_context
if Rails.env.production? && current_application_settings.sentry_enabled && current_user
Raven.user_context(
id: current_user.id,
email: current_user.email,
username: current_user.username,
)
def sentry_context
if Rails.env.production? && current_application_settings.sentry_enabled
if current_user
Raven.user_context(
id: current_user.id,
email: current_user.email,
username: current_user.username,
)
end
Raven.tags_context(program: sentry_program_context)
end
......@@ -158,20 +161,6 @@ class ApplicationController < ActionController::Base
end
end
def add_gon_variables
gon.api_version = API::API.version
gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
gon.default_issues_tracker = Project.new.default_issue_tracker.to_param
gon.max_file_size = current_application_settings.max_attachment_size
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
if current_user
gon.current_user_id = current_user.id
gon.api_token = current_user.private_token
end
end
def validate_user_service_ticket!
return unless signed_in? && session[:service_tickets]
......
......@@ -12,8 +12,15 @@ class AutocompleteController < ApplicationController
if params[:search].blank?
# Include current user if available to filter by "Me"
if params[:current_user] && current_user
@users = [*@users, current_user].uniq
@users = [*@users, current_user]
end
if params[:author_id].present?
author = User.find_by_id(params[:author_id])
@users = [author, *@users] if author
end
@users.uniq!
end
render json: @users, only: [:name, :username, :id], methods: [:avatar_url]
......
......@@ -40,6 +40,7 @@ class GroupsController < Groups::ApplicationController
@last_push = current_user.recent_push if current_user
@projects = @projects.includes(:namespace)
@projects = @projects.sorted_by_activity
@projects = filter_projects(@projects)
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]) if params[:filter_projects].blank?
......
class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
include Gitlab::CurrentSettings
include Gitlab::GonHelper
include PageLayoutHelper
before_action :verify_user_oauth_applications_enabled
before_action :authenticate_user!
before_action :add_gon_variables
layout 'profile'
......
......@@ -10,6 +10,11 @@ class Profiles::KeysController < Profiles::ApplicationController
@key = current_user.keys.find(params[:id])
end
# Back-compat: We need to support this URL since git-annex webapp points to it
def new
redirect_to profile_keys_path
end
def create
@key = current_user.keys.new(key_params)
......
......@@ -3,7 +3,8 @@ class Projects::IssuesController < Projects::ApplicationController
include IssuableActions
before_action :module_enabled
before_action :issue, only: [:edit, :update, :show]
before_action :issue,
only: [:edit, :update, :show, :referenced_merge_requests, :related_branches]
# Allow read any issue
before_action :authorize_read_issue!, only: [:show]
......@@ -17,9 +18,6 @@ class Projects::IssuesController < Projects::ApplicationController
# Allow issues bulk update
before_action :authorize_admin_issues!, only: [:bulk_update]
# Cross-reference merge requests
before_action :closed_by_merge_requests, only: [:show]
respond_to :html
def index
......@@ -62,11 +60,9 @@ class Projects::IssuesController < Projects::ApplicationController
end
def show
@note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.nonawards.with_associations.fresh
@note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.nonawards.with_associations.fresh
@noteable = @issue
@merge_requests = @issue.referenced_merge_requests(current_user)
@related_branches = @issue.related_branches - @merge_requests.map(&:source_branch)
respond_to do |format|
format.html
......@@ -118,15 +114,39 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
def referenced_merge_requests
@merge_requests = @issue.referenced_merge_requests(current_user)
@closed_by_merge_requests = @issue.closed_by_merge_requests(current_user)
respond_to do |format|
format.json do
render json: {
html: view_to_html_string('projects/issues/_merge_requests')
}
end
end
end
def related_branches
merge_requests = @issue.referenced_merge_requests(current_user)
@related_branches = @issue.related_branches -
merge_requests.map(&:source_branch)
respond_to do |format|
format.json do
render json: {
html: view_to_html_string('projects/issues/_related_branches')
}
end
end
end
def bulk_update
result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute
redirect_back_or_default(default: { action: 'index' }, options: { notice: "#{result[:count]} issues updated" })
end
def closed_by_merge_requests
@closed_by_merge_requests ||= @issue.closed_by_merge_requests(current_user)
end
protected
def issue
......
......@@ -40,10 +40,11 @@ module DiffHelper
(unfold) ? 'unfold js-unfold' : ''
end
def diff_line_content(line)
def diff_line_content(line, line_type = nil)
if line.blank?
" &nbsp;".html_safe
else
line[0] = ' ' if %w[new old].include?(line_type)
line
end
end
......
......@@ -6,12 +6,13 @@ module SelectsHelper
value = opts[:selected] || ''
placeholder = opts[:placeholder] || 'Search for a user'
null_user = opts[:null_user] || false
any_user = opts[:any_user] || false
email_user = opts[:email_user] || false
first_user = opts[:first_user] && current_user ? current_user.username : false
current_user = opts[:current_user] || false
project = opts[:project] || @project
null_user = opts[:null_user] || false
any_user = opts[:any_user] || false
email_user = opts[:email_user] || false
first_user = opts[:first_user] && current_user ? current_user.username : false
current_user = opts[:current_user] || false
author_id = opts[:author_id] || ''
project = opts[:project] || @project
html = {
class: css_class,
......@@ -21,7 +22,8 @@ module SelectsHelper
any_user: any_user,
email_user: email_user,
first_user: first_user,
current_user: current_user
current_user: current_user,
author_id: author_id
}
}
......
class RepositoryCheckMailer < BaseMailer
def notify(failed_count)
if failed_count == 1
@message = "One project failed its last repository check"
else
@message = "#{failed_count} projects failed their last repository check"
end
mail(
to: User.admins.pluck(:email),
subject: @message
)
end
end
......@@ -153,7 +153,8 @@ class ApplicationSetting < ActiveRecord::Base
require_two_factor_authentication: false,
two_factor_grace_period: 48,
recaptcha_enabled: false,
akismet_enabled: false
akismet_enabled: false,
repository_checks_enabled: true,
)
end
......
......@@ -154,7 +154,7 @@ class Commit
id: id,
message: safe_message,
timestamp: committed_date.xmlschema,
url: commit_url,
url: Gitlab::UrlBuilder.build(self),
author: {
name: author_name,
email: author_email
......@@ -168,10 +168,6 @@ class Commit
data
end
def commit_url
project.present? ? "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/#{id}" : ""
end
# Discover issues should be closed when this commit is pushed to a project's
# default branch.
def closes_issues(current_user = self.committer)
......
......@@ -106,7 +106,7 @@ class Issue < ActiveRecord::Base
def related_branches
project.repository.branch_names.select do |branch|
branch.end_with?("-#{iid}")
branch =~ /\A#{iid}-(?!\d+-stable)/i
end
end
......@@ -151,7 +151,7 @@ class Issue < ActiveRecord::Base
end
def to_branch_name
"#{title.parameterize}-#{iid}"
"#{iid}-#{title.parameterize}"
end
def can_be_worked_on?(current_user)
......
# == Schema Information
#
# Table name: oauth_access_tokens
#
# id :integer not null, primary key
# resource_owner_id :integer
# application_id :integer
# token :string not null
# refresh_token :string
# expires_in :integer
# revoked_at :datetime
# created_at :datetime not null
# scopes :string
#
class OauthAccessToken < ActiveRecord::Base
belongs_to :resource_owner, class_name: 'User'
belongs_to :application, class_name: 'Doorkeeper::Application'
end
......@@ -409,6 +409,35 @@ class Project < ActiveRecord::Base
self.import_data.destroy if self.import_data
end
def import_url=(value)
import_url = Gitlab::ImportUrl.new(value)
create_or_update_import_data(credentials: import_url.credentials)
super(import_url.sanitized_url)
end
def import_url
if import_data && super
import_url = Gitlab::ImportUrl.new(super, credentials: import_data.credentials)
import_url.full_url
else
super
end
end
def create_or_update_import_data(data: nil, credentials: nil)
project_import_data = import_data || build_import_data
if data
project_import_data.data ||= {}
project_import_data.data = project_import_data.data.merge(data)
end
if credentials
project_import_data.credentials ||= {}
project_import_data.credentials = project_import_data.credentials.merge(credentials)
end
project_import_data.save
end
def import?
external_import? || forked?
end
......@@ -865,7 +894,9 @@ class Project < ActiveRecord::Base
def change_head(branch)
repository.before_change_head
gitlab_shell.update_repository_head(self.path_with_namespace, branch)
repository.rugged.references.create('HEAD',
"refs/heads/#{branch}",
force: true)
reload_default_branch
end
......
......@@ -12,8 +12,20 @@ require 'file_size_validator'
class ProjectImportData < ActiveRecord::Base
belongs_to :project
attr_encrypted :credentials,
key: Gitlab::Application.secrets.db_key_base,
marshal: true,
encode: true,
mode: :per_attribute_iv_and_salt
serialize :data, JSON
validates :project, presence: true
before_validation :symbolize_credentials
def symbolize_credentials
# bang doesn't work here - attr_encrypted makes it not to work
self.credentials = self.credentials.deep_symbolize_keys unless self.credentials.blank?
end
end
......@@ -82,17 +82,17 @@ class BambooService < CiService
end
def build_info(sha)
url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}")
url = URI.join(bamboo_url, "/rest/api/latest/result?label=#{sha}").to_s
if username.blank? && password.blank?
@response = HTTParty.get(parsed_url.to_s, verify: false)
@response = HTTParty.get(url, verify: false)
else
get_url = "#{url}&os_authType=basic"
url << '&os_authType=basic'
auth = {
username: username,
password: password,
username: username,
password: password
}
@response = HTTParty.get(get_url, verify: false, basic_auth: auth)
@response = HTTParty.get(url, verify: false, basic_auth: auth)
end
end
......@@ -101,11 +101,11 @@ class BambooService < CiService
if @response.code != 200 || @response['results']['results']['size'] == '0'
# If actual build link can't be determined, send user to build summary page.
"#{bamboo_url}/browse/#{build_key}"
URI.join(bamboo_url, "/browse/#{build_key}").to_s
else
# If actual build link is available, go to build result page.
result_key = @response['results']['results']['result']['planResultKey']['key']
"#{bamboo_url}/browse/#{result_key}"
URI.join(bamboo_url, "/browse/#{result_key}").to_s
end
end
......@@ -134,7 +134,7 @@ class BambooService < CiService
return unless supported_events.include?(data[:object_kind])
# Bamboo requires a GET and does not take any data.
self.class.get("#{bamboo_url}/updateAndBuild.action?buildKey=#{build_key}",
verify: false)
url = URI.join(bamboo_url, "/updateAndBuild.action?buildKey=#{build_key}").to_s
self.class.get(url, verify: false)
end
end
......@@ -23,7 +23,7 @@ class BuildsEmailService < Service
prop_accessor :recipients
boolean_accessor :add_pusher
boolean_accessor :notify_only_broken_builds
validates :recipients, presence: true, if: :activated?
validates :recipients, presence: true, if: ->(s) { s.activated? && !s.add_pusher? }
def initialize_properties
if properties.nil?
......@@ -87,10 +87,14 @@ class BuildsEmailService < Service
end
def all_recipients(data)
all_recipients = recipients.split(',').compact.reject(&:blank?)
all_recipients = []
unless recipients.blank?
all_recipients += recipients.split(',').compact.reject(&:blank?)
end
if add_pusher? && data[:user][:email]
all_recipients << "#{data[:user][:email]}"
all_recipients << data[:user][:email]
end
all_recipients
......
......@@ -85,13 +85,15 @@ class TeamcityService < CiService
end
def build_info(sha)
url = URI.parse("#{teamcity_url}/httpAuth/app/rest/builds/"\
"branch:unspecified:any,number:#{sha}")
url = URI.join(
teamcity_url,
"/httpAuth/app/rest/builds/branch:unspecified:any,number:#{sha}"
).to_s
auth = {
username: username,
password: password,
password: password
}
@response = HTTParty.get("#{url}", verify: false, basic_auth: auth)
@response = HTTParty.get(url, verify: false, basic_auth: auth)
end
def build_page(sha, ref)
......@@ -100,12 +102,14 @@ class TeamcityService < CiService
if @response.code != 200
# If actual build link can't be determined,
# send user to build summary page.
"#{teamcity_url}/viewLog.html?buildTypeId=#{build_type}"
URI.join(teamcity_url, "/viewLog.html?buildTypeId=#{build_type}").to_s
else
# If actual build link is available, go to build result page.
built_id = @response['build']['id']
"#{teamcity_url}/viewLog.html?buildId=#{built_id}"\
"&buildTypeId=#{build_type}"
URI.join(
teamcity_url,
"/viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}"
).to_s
end
end
......@@ -140,12 +144,13 @@ class TeamcityService < CiService
branch = Gitlab::Git.ref_name(data[:ref])
self.class.post("#{teamcity_url}/httpAuth/app/rest/buildQueue",
body: "<build branchName=\"#{branch}\">"\
"<buildType id=\"#{build_type}\"/>"\
'</build>',
headers: { 'Content-type' => 'application/xml' },
basic_auth: auth
)
self.class.post(
URI.join(teamcity_url, '/httpAuth/app/rest/buildQueue').to_s,
body: "<build branchName=\"#{branch}\">"\
"<buildType id=\"#{build_type}\"/>"\
'</build>',
headers: { 'Content-type' => 'application/xml' },
basic_auth: auth
)
end
end
......@@ -169,7 +169,12 @@ class Repository
def rm_tag(tag_name)
before_remove_tag
gitlab_shell.rm_tag(path_with_namespace, tag_name)
begin
rugged.tags.delete(tag_name)
true
rescue Rugged::ReferenceError
false
end
end
def branch_names
......@@ -797,7 +802,7 @@ class Repository
def search_files(query, ref)
offset = 2
args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -e #{query} #{ref || root_ref})
args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -e #{Regexp.escape(query)} #{ref || root_ref})
Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
end
......
......@@ -3,7 +3,7 @@ module Issues
def hook_data(issue, action)
issue_data = issue.to_hook_data(current_user)
issue_url = Gitlab::UrlBuilder.new(:issue).build(issue.id)
issue_url = Gitlab::UrlBuilder.build(issue)
issue_data[:object_attributes].merge!(url: issue_url, action: action)
issue_data
end
......
......@@ -20,8 +20,7 @@ module MergeRequests
def hook_data(merge_request, action)
hook_data = merge_request.to_hook_data(current_user)
merge_request_url = Gitlab::UrlBuilder.new(:merge_request).build(merge_request.id)
hook_data[:object_attributes][:url] = merge_request_url
hook_data[:object_attributes][:url] = Gitlab::UrlBuilder.build(merge_request)
hook_data[:object_attributes][:action] = action
hook_data
end
......
......@@ -51,7 +51,7 @@ module MergeRequests
# be interpreted as the use wants to close that issue on this project
# Pattern example: 112-fix-mep-mep
# Will lead to appending `Closes #112` to the description
if match = merge_request.source_branch.match(/-(\d+)\z/)
if match = merge_request.source_branch.match(/\A(\d+)-/)
iid = match[1]
closes_issue = "Closes ##{iid}"
......
......@@ -26,7 +26,9 @@ module Projects
GitlabShellOneShotWorker.perform_async(:gc, @project.path_with_namespace)
ensure
@project.update_column(:pushes_since_gc, 0)
Gitlab::Metrics.measure(:reset_pushes_since_gc) do
@project.update_column(:pushes_since_gc, 0)
end
end
def needed?
......@@ -34,14 +36,18 @@ module Projects
end
def increment!
@project.increment!(:pushes_since_gc)
Gitlab::Metrics.measure(:increment_pushes_since_gc) do
@project.increment!(:pushes_since_gc)
end
end
private
def try_obtain_lease
lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT)
lease.try_obtain
Gitlab::Metrics.measure(:obtain_housekeeping_lease) do
lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT)
lease.try_obtain
end
end
end
end
module Projects
class ParticipantsService < BaseService
def execute(note_type, note_id)
participating =
if note_type && note_id
participants_in(note_type, note_id)
else
[]
end
def execute(noteable_type, noteable_id)
@noteable_type = noteable_type
@noteable_id = noteable_id
project_members = sorted(project.team.members)
participants = all_members + groups + project_members + participating
participants = target_owner + participants_in_target + all_members + groups + project_members
participants.uniq
end
def participants_in(type, id)
target =
case type
def target
@target ||=
case @noteable_type
when "Issue"
project.issues.find_by_iid(id)
project.issues.find_by_iid(@noteable_id)
when "MergeRequest"
project.merge_requests.find_by_iid(id)
project.merge_requests.find_by_iid(@noteable_id)
when "Commit"
project.commit(id)
project.commit(@noteable_id)
else
nil
end
end
def target_owner
return [] unless target && target.author.present?
[{
name: target.author.name,
username: target.author.username
}]
end
def participants_in_target
return [] unless target
users = target.participants(current_user)
......@@ -30,13 +39,13 @@ module Projects
end
def sorted(users)
users.uniq.to_a.compact.sort_by(&:username).map do |user|
users.uniq.to_a.compact.sort_by(&:username).map do |user|
{ username: user.username, name: user.name }
end
end
def groups
current_user.authorized_groups.sort_by(&:path).map do |group|
current_user.authorized_groups.sort_by(&:path).map do |group|
count = group.users.count
{ username: group.path, name: group.name, count: count }
end
......
......@@ -34,8 +34,9 @@ module Projects
raise TransferError.new("Project with same path in target namespace already exists")
end
# Apply new namespace id
# Apply new namespace id and visibility level
project.namespace = new_namespace
project.visibility_level = new_namespace.visibility_level unless project.visibility_level_allowed_by_group?
project.save!
# Notifications
......@@ -56,7 +57,7 @@ module Projects
Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path)
project.old_path_with_namespace = old_path
SystemHooksService.new.execute_hooks_for(project, :transfer)
true
end
......
......@@ -222,7 +222,7 @@ class SystemNoteService
# Called when a branch is created from the 'new branch' button on a issue
# Example note text:
#
# "Started branch `issue-branch-button-201`"
# "Started branch `201-issue-branch-button`"
def self.new_issue_branch(issue, project, author, branch)
h = Gitlab::Routing.url_helpers
link = h.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch)
......
......@@ -271,5 +271,24 @@
.col-sm-10
= f.text_field :sentry_dsn, class: 'form-control'
%fieldset
%legend Repository Checks
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :repository_checks_enabled do
= f.check_box :repository_checks_enabled
Enable Repository Checks
.help-block
GitLab will periodically run
%a{ href: 'https://www.kernel.org/pub/software/scm/git/docs/git-fsck.html', target: 'blank' } 'git fsck'
in all project and wiki repositories to look for silent disk corruption issues.
.form-group
.col-sm-offset-2.col-sm-10
= link_to 'Clear all repository checks', clear_repository_check_states_admin_application_settings_path, data: { confirm: 'This will clear repository check states for ALL projects in the database. This cannot be undone. Are you sure?' }, method: :put, class: "btn btn-sm btn-remove"
.help-block
If you got a lot of false alarms from repository checks you can choose to clear all repository check information from the database.
.form-actions
= f.submit 'Save', class: 'btn btn-save'
- page_title "Logs"
- loggers = [Gitlab::GitLogger, Gitlab::AppLogger,
Gitlab::ProductionLogger, Gitlab::SidekiqLogger]
Gitlab::ProductionLogger, Gitlab::SidekiqLogger,
Gitlab::RepositoryCheckLogger]
%ul.nav-links.log-tabs
- loggers.each do |klass|
%li{ class: (klass == Gitlab::GitLogger ? 'active' : '') }
......
......@@ -3,7 +3,7 @@
.row.prepend-top-default
%aside.col-md-3
.admin-filter
.panel.admin-filter
= form_tag admin_namespaces_projects_path, method: :get, class: '' do
.form-group
= label_tag :name, 'Name:'
......@@ -38,7 +38,13 @@
%span.descr
= visibility_level_icon(level)
= label
%hr
%fieldset
%strong Problems
.checkbox
= label_tag :last_repository_check_failed do
= check_box_tag :last_repository_check_failed, 1, params[:last_repository_check_failed]
%span Last repository check failed
= hidden_field_tag :sort, params[:sort]
= button_tag "Search", class: "btn submit btn-primary"
= link_to "Reset", admin_namespaces_projects_path, class: "btn btn-cancel"
......
......@@ -5,6 +5,16 @@
%i.fa.fa-pencil-square-o
Edit
%hr
- if @project.last_repository_check_failed?
.row
.col-md-12
.panel
.panel-heading.alert.alert-danger
Last repository check
= "(#{time_ago_in_words(@project.last_repository_check_at)} ago)"
failed. See
= link_to 'repocheck.log', admin_logs_path
for error messages.
.row
.col-md-6
.panel.panel-default
......@@ -95,6 +105,32 @@
.col-sm-offset-2.col-sm-10
= f.submit 'Transfer', class: 'btn btn-primary'
.panel.panel-default.repository-check
.panel-heading
Repository check
.panel-body
= form_for @project, url: repository_check_admin_namespace_project_path(@project.namespace, @project), method: :post do |f|
.form-group
- if @project.last_repository_check_at.nil?
This repository has never been checked.
- else
This repository was last checked
= @project.last_repository_check_at.to_s(:medium) + '.'
The check
- if @project.last_repository_check_failed?
= succeed '.' do
%strong.cred failed
See
= link_to 'repocheck.log', admin_logs_path
for error messages.
- else
passed.
= link_to icon('question-circle'), help_page_path('administration', 'repository_checks')
.form-group
= f.submit 'Trigger repository check', class: 'btn btn-primary'
.col-md-6
- if @group
.panel.panel-default
......
......@@ -68,7 +68,7 @@
%td= app.name
%td= token.created_at
%td= token.scopes
%td= render 'delete_form', application: app
%td= render 'doorkeeper/authorized_applications/delete_form', application: app
- @authorized_anonymous_tokens.each do |token|
%tr
%td
......
......@@ -8,7 +8,7 @@
This will create milestone in every selected project
%hr
= form_for @milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form gfm-form js-quick-submit js-requires-input' } do |f|
= form_for @milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form common-note-form js-quick-submit js-requires-input' } do |f|
.row
- if @milestone.errors.any?
#error_explanation
......@@ -27,7 +27,7 @@
= f.label :description, "Description", class: "control-label"
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
= render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
= render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...'
.clearfix
.error-alert
.form-group
......
......@@ -8,7 +8,7 @@
.navbar-collapse.collapse
%ul.nav.navbar-nav
%li.hidden-sm.hidden-xs
= render 'layouts/search'
= render 'layouts/search' unless current_controller?(:search)
%li.visible-sm.visible-xs
= link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('search')
......
......@@ -52,6 +52,9 @@
%li
phpunit --coverage-text --colors=never (PHP) -
%code ^\s*Lines:\s*\d+.\d+\%
%li
gcovr (C/C++) -
%code ^TOTAL.*\s+(\d+\%)$
.form-group
.col-sm-offset-2.col-sm-10
......
.zen-backdrop
- classes << ' js-gfm-input js-autosize markdown-area'
- if defined?(f) && f
= f.text_area attr, class: classes, placeholder: "Write a comment or drag your files here..."
= f.text_area attr, class: classes, placeholder: placeholder
- else
= text_area_tag attr, nil, class: classes, placeholder: "Write a comment or drag your files here..."
= text_area_tag attr, nil, class: classes, placeholder: placeholder
%a.zen-cotrol.zen-control-leave.js-zen-leave{ href: "#" }
= icon('compress')
- if @lines.present?
- if @form.unfold? && @form.since != 1 && !@form.bottom?
%tr.line_holder{ id: @form.since }
= render "projects/diffs/match_line", {line: @match_line,
line_old: @form.since, line_new: @form.since, bottom: false, new_file: false}
= render "projects/diffs/match_line", { line: @match_line,
line_old: @form.since, line_new: @form.since, bottom: false, new_file: false }
- @lines.each_with_index do |line, index|
- line_new = index + @form.since
- line_old = line_new - @form.offset
%tr.line_holder
%td.old_line.diff-line-num{data: {linenumber: line_old}}
%td.old_line.diff-line-num{ data: { linenumber: line_old } }
= link_to raw(line_old), "#"
%td.new_line.diff-line-num
%td.new_line.diff-line-num{ data: { linenumber: line_old } }
= link_to raw(line_new) , "#"
%td.line_content.noteable_line==#{' ' * @form.indent}#{line}
- if @form.unfold? && @form.bottom? && @form.to < @blob.loc
%tr.line_holder{ id: @form.to }
= render "projects/diffs/match_line", {line: @match_line,
line_old: @form.to, line_new: @form.to, bottom: true, new_file: false}
= render "projects/diffs/match_line", { line: @match_line,
line_old: @form.to, line_new: @form.to, bottom: true, new_file: false }
......@@ -10,7 +10,7 @@
= cache(cache_key) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
.commit-row-title
%span.item-title.str-truncated
%span.item-title
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
- if commit.description?
%a.text-expander.js-toggle-button ...
......
- type = line.type
%tr.line_holder{id: line_code, class: type}
%tr.line_holder{ id: line_code, class: type }
- case type
- when 'match'
= render "projects/diffs/match_line", {line: line.text,
line_old: line.old_pos, line_new: line.new_pos, bottom: false, new_file: diff_file.new_file}
= render "projects/diffs/match_line", { line: line.text,
line_old: line.old_pos, line_new: line.new_pos, bottom: false, new_file: diff_file.new_file }
- when 'nonewline'
%td.old_line.diff-line-num
%td.new_line.diff-line-num
%td.line_content.match= line.text
- else
%td.old_line.diff-line-num{class: type}
- link_text = raw(type == "new" ? "&nbsp;" : line.old_pos)
%td.old_line.diff-line-num{ class: type, data: { linenumber: line.new_pos } }
- link_text = type == "new" ? "&nbsp;".html_safe : line.old_pos
- if defined?(plain) && plain
= link_text
- else
= link_to link_text, "##{line_code}", id: line_code
= link_to "", "##{line_code}", id: line_code, data: { linenumber: link_text }
- if @comments_allowed && can?(current_user, :create_note, @project)
= link_to_new_diff_note(line_code)
%td.new_line.diff-line-num{class: type, data: {linenumber: line.new_pos}}
- link_text = raw(type == "old" ? "&nbsp;" : line.new_pos)
%td.new_line.diff-line-num{ class: type, data: { linenumber: line.new_pos } }
- link_text = type == "old" ? "&nbsp;".html_safe : line.new_pos
- if defined?(plain) && plain
= link_text
- else
= link_to link_text, "##{line_code}", id: line_code
%td.line_content{class: "noteable_line #{type} #{line_code}", data: { line_code: line_code }}= diff_line_content(line.text)
= link_to "", "##{line_code}", id: line_code, data: { linenumber: link_text }
%td.line_content{ class: ['noteable_line', type, line_code], data: { line_code: line_code } }= diff_line_content(line.text, type)
......@@ -14,11 +14,11 @@
%td.new_line.diff-line-num
%td.line_content.parallel.match= left[:text]
- else
%td.old_line.diff-line-num{id: left[:line_code], class: "#{left[:type]}"}
%td.old_line.diff-line-num{id: left[:line_code], class: "#{left[:type]} #{'empty-cell' if !left[:number]}"}
= link_to raw(left[:number]), "##{left[:line_code]}", id: left[:line_code]
- if @comments_allowed && can?(current_user, :create_note, @project)
= link_to_new_diff_note(left[:line_code], 'old')
%td.line_content{class: "parallel noteable_line #{left[:type]} #{left[:line_code]}", data: { line_code: left[:line_code] }}= diff_line_content(left[:text])
%td.line_content{class: "parallel noteable_line #{left[:type]} #{left[:line_code]} #{'empty-cell' if left[:text].empty?}", data: { line_code: left[:line_code] }}= diff_line_content(left[:text])
- if right[:type] == 'new'
- new_line_class = 'new'
......@@ -27,11 +27,11 @@
- new_line_class = nil
- new_line_code = left[:line_code]
%td.new_line.diff-line-num{id: new_line_code, class: "#{new_line_class}", data: { linenumber: right[:number] }}
%td.new_line.diff-line-num{id: new_line_code, class: "#{new_line_class} #{'empty-cell' if !right[:number]}", data: { linenumber: right[:number] }}
= link_to raw(right[:number]), "##{new_line_code}", id: new_line_code
- if @comments_allowed && can?(current_user, :create_note, @project)
= link_to_new_diff_note(right[:line_code], 'new')
%td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code}", data: { line_code: new_line_code }}= diff_line_content(right[:text])
%td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code} #{'empty-cell' if right[:text].empty?}", data: { line_code: new_line_code }}= diff_line_content(right[:text])
- if @reply_allowed
- comments_left, comments_right = organize_comments(left[:type], right[:type], left[:line_code], right[:line_code])
......
......@@ -210,6 +210,7 @@
%li Be careful. Changing the project's namespace can have unintended side effects.
%li You can only transfer the project to namespaces you manage.
%li You will need to update your local repositories to point to the new location.
%li Project visibility level will be changed to match namespace rules when transfering to a group.
.form-actions
= f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
- else
......
= form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form js-quick-submit js-requires-input' } do |f|
= form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form common-note-form js-quick-submit js-requires-input' } do |f|
= render 'shared/issuable/form', f: f, issuable: @issue
:javascript
......
......@@ -27,7 +27,7 @@
= icon('thumbs-down')
= downvotes
- note_count = issue.notes.user.count
- note_count = issue.notes.user.nonawards.count
- if note_count > 0
%li
= link_to issue_path(issue) + "#notes" do
......
......@@ -64,9 +64,11 @@
= @issue.description
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
.merge-requests
= render 'merge_requests'
= render 'related_branches'
#merge-requests{'data-url' => referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue)}
// This element is filled in using JavaScript.
#related-branches{'data-url' => related_branches_namespace_project_issue_url(@project.namespace, @project, @issue)}
// This element is filled in using JavaScript.
.content-block.content-block-small
= render 'new_branch'
......
......@@ -14,7 +14,7 @@
.label-subscription{data: {url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)}}
.subscription-status{data: {status: label_subscription_status(label)}}
%a.subscribe-button.btn.action-buttons{data: {toggle: "tooltip"}}
%button.js-subscribe-button.label-subscribe-button.btn.action-buttons{ type: "button", data: { toggle: "tooltip" } }
%span= label_subscription_toggle_button_text(label)
- if can? current_user, :admin_label, @project
......
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input js-quick-submit' } do |f|
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal common-note-form js-requires-input js-quick-submit' } do |f|
= render 'shared/issuable/form', f: f, issuable: @merge_request
:javascript
......
......@@ -35,7 +35,7 @@
= icon('thumbs-down')
= downvotes
- note_count = merge_request.mr_and_commit_notes.user.count
- note_count = merge_request.mr_and_commit_notes.user.nonawards.count
- if note_count > 0
%li
= link_to merge_request_path(merge_request) + "#notes" do
......
......@@ -16,11 +16,9 @@
= dropdown_title("Select source project")
= dropdown_filter("Search projects")
= dropdown_content do
- is_active = f.object.source_project_id == @merge_request.source_project.id
%ul
%li
%a{ href: "#", class: "#{("is-active" if is_active)}", data: { id: @merge_request.source_project.id } }
= @merge_request.source_project_path
= render 'projects/merge_requests/dropdowns/project',
projects: [@merge_request.source_project],
selected: f.object.source_project_id
.merge-request-select.dropdown
= f.hidden_field :source_branch
= dropdown_toggle "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" }
......@@ -28,11 +26,9 @@
= dropdown_title("Select source branch")
= dropdown_filter("Search branches")
= dropdown_content do
%ul
- @merge_request.source_branches.each do |branch|
%li
%a{ href: "#", class: "#{("is-active" if f.object.source_branch == branch)}", data: { id: branch } }
= branch
= render 'projects/merge_requests/dropdowns/branch',
branches: @merge_request.source_branches,
selected: f.object.source_branch
.panel-footer
= icon('spinner spin', class: 'js-source-loading')
%ul.list-unstyled.mr_source_commit
......@@ -50,11 +46,9 @@
= dropdown_title("Select target project")
= dropdown_filter("Search projects")
= dropdown_content do
%ul
- projects.each do |project|
%li
%a{ href: "#", class: "#{("is-active" if f.object.target_project_id == project.id)}", data: { id: project.id } }
= project.path_with_namespace
= render 'projects/merge_requests/dropdowns/project',
projects: projects,
selected: f.object.target_project_id
.merge-request-select.dropdown
= f.hidden_field :target_branch
= dropdown_toggle f.object.target_branch, { toggle: "dropdown", field_name: "#{f.object_name}[target_branch]" }, { toggle_class: "js-compare-dropdown js-target-branch" }
......@@ -62,11 +56,9 @@
= dropdown_title("Select target branch")
= dropdown_filter("Search branches")
= dropdown_content do
%ul
- @merge_request.target_branches.each do |branch|
%li
%a{ href: "#", class: "#{("is-active" if f.object.target_branch == branch)}", data: { id: branch } }
= branch
= render 'projects/merge_requests/dropdowns/branch',
branches: @merge_request.target_branches,
selected: f.object.target_branch
.panel-footer
= icon('spinner spin', class: "js-target-loading")
%ul.list-unstyled.mr_target_commit
......
......@@ -10,7 +10,7 @@
%span.pull-right
= link_to 'Change branches', mr_change_branches_path(@merge_request)
%hr
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input' } do |f|
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal common-note-form js-requires-input' } do |f|
= render 'shared/issuable/form', f: f, issuable: @merge_request
= f.hidden_field :source_project_id
= f.hidden_field :source_branch
......
......@@ -32,8 +32,7 @@
%span Request to merge
%span.label-branch= source_branch_with_namespace(@merge_request)
%span into
= link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
= @merge_request.target_branch
= link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"
- if @merge_request.open? && @merge_request.diverged_from_target_branch?
%span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind)
......@@ -51,7 +50,7 @@
%li.notes-tab
= link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do
Discussion
%span.badge= @merge_request.mr_and_commit_notes.user.count
%span.badge= @merge_request.mr_and_commit_notes.user.nonawards.count
%li.commits-tab
= link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
Commits
......
%ul
- branches.each do |branch|
%li
%a{ href: '#', class: "#{('is-active' if selected == branch)}", data: { id: branch } }
= branch
%ul
- projects.each do |project|
%li
%a{ href: "#", class: "#{('is-active' if selected == project.id)}", data: { id: project.id } }
= project.path_with_namespace
%ul
- @target_branches.each do |branch|
%li
%a{ href: "#", class: "#{("is-active" if "a" == branch)}", data: { id: branch } }
= branch
= render 'projects/merge_requests/dropdowns/branch',
branches: @target_branches,
selected: nil
= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form js-quick-submit js-requires-input'} do |f|
= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form common-note-form js-quick-submit js-requires-input'} do |f|
= form_errors(@milestone)
.row
.col-md-6
.form-group
......@@ -11,7 +10,7 @@
= f.label :description, "Description", class: "control-label"
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
= render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
= render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...'
= render 'projects/notes/hints'
.clearfix
.error-alert
......
......@@ -2,7 +2,7 @@
= form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true, html: { class: 'edit-note common-note-form js-quick-submit' } do |f|
= note_target_fields(note)
= render layout: 'projects/md_preview', locals: { preview_class: 'md-preview' } do
= render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text js-task-list-field'
= render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text js-task-list-field', placeholder: "Write a comment or drag your files here..."
= render 'projects/notes/hints'
.note-form-actions.clearfix
......
= form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new-note js-new-note-form js-quick-submit common-note-form gfm-form" }, authenticity_token: true do |f|
= form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new-note js-new-note-form js-quick-submit common-note-form" }, authenticity_token: true do |f|
= hidden_field_tag :view, diff_view
= hidden_field_tag :line_type
= note_target_fields(@note)
......@@ -8,7 +8,7 @@
= f.hidden_field :noteable_type
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
= render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text'
= render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text', placeholder: "Write a comment or drag your files here..."
= render 'projects/notes/hints'
.error-alert
......
......@@ -20,11 +20,9 @@
%td.new_line.diff-line-num= "..."
%td.line_content.match= line.text
- else
%td.old_line.diff-line-num
= raw(type == "new" ? "&nbsp;" : line.old_pos)
%td.new_line.diff-line-num
= raw(type == "old" ? "&nbsp;" : line.new_pos)
%td.line_content{class: "noteable_line #{type} #{line_code}", line_code: line_code}= diff_line_content(line.text)
%td.old_line.diff-line-num{ data: { linenumber: type == "new" ? "&nbsp;".html_safe : line.old_pos } }
%td.new_line.diff-line-num{ data: { linenumber: type == "old" ? "&nbsp;".html_safe : line.new_pos } }
%td.line_content{ class: ['noteable_line', type, line_code], line_code: line_code }= diff_line_content(line.text, type)
- if line_code == note.line_code
= render "projects/notes/diff_notes_with_reply", notes: discussion_notes
......@@ -9,11 +9,11 @@
%strong #{@tag.name}
.prepend-top-default
= form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal gfm-form release-form js-quick-submit' }) do |f|
= form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal common-note-form release-form js-quick-submit' }) do |f|
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
= render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
= render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here..."
= render 'projects/notes/hints'
.error-alert
.form-actions.prepend-top-default
= f.submit 'Save changes', class: 'btn btn-save'
= link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel"
.error-alert
.form-actions.prepend-top-default
= f.submit 'Save changes', class: 'btn btn-save'
= link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel"
......@@ -10,7 +10,7 @@
New Tag
%hr
= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal gfm-form tag-form js-quick-submit js-requires-input" do
= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal common-note-form tag-form js-quick-submit js-requires-input" do
.form-group
= label_tag :tag_name, nil, class: 'control-label'
.col-sm-10
......@@ -30,9 +30,9 @@
= label_tag :release_description, 'Release notes', class: 'control-label'
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
= render 'projects/zen', attr: :release_description, classes: 'description form-control'
= render 'projects/zen', attr: :release_description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here..."
= render 'projects/notes/hints'
.help-block Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page.
.help-block Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page.
.form-actions
= button_tag 'Create tag', class: 'btn btn-create', tabindex: 3
= link_to 'Cancel', namespace_project_tags_path(@project.namespace, @project), class: 'btn btn-cancel'
......
= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form prepend-top-default js-quick-submit' } do |f|
= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form common-note-form prepend-top-default js-quick-submit' } do |f|
= form_errors(@page)
= f.hidden_field :title, value: @page.title
......@@ -11,7 +11,7 @@
= f.label :content, class: 'control-label'
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
= render 'projects/zen', f: f, attr: :content, classes: 'description form-control'
= render 'projects/zen', f: f, attr: :content, classes: 'note-textarea', placeholder: 'Write your content or drag files here...'
= render 'projects/notes/hints'
.clearfix
......
%p
#{@message}.
%p
= link_to "See the affected projects in the GitLab admin panel", admin_namespaces_projects_url(last_repository_check_failed: 1)
#{@message}.
\
View details: #{admin_namespaces_projects_url(last_repository_check_failed: 1)}
- project = note.project
- note_url = Gitlab::UrlBuilder.new(:note).build(note.id)
- note_url = Gitlab::UrlBuilder.build(note)
- noteable_identifier = note.noteable.try(:iid) || note.noteable.id
.search-result-row
%h5.note-search-caption.str-truncated
......
......@@ -29,7 +29,8 @@
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
= render 'projects/zen', f: f, attr: :description,
classes: 'description form-control'
classes: 'note-textarea',
placeholder: "Write a comment or drag your files here..."
= render 'projects/notes/hints'
.clearfix
.error-alert
......@@ -70,13 +71,13 @@
- if can? current_user, :admin_milestone, issuable.project
= link_to 'Create new milestone', new_namespace_project_milestone_path(issuable.project.namespace, issuable.project), target: :blank
.form-group
- has_labels = issuable.project.labels.any?
= f.label :label_ids, "Labels", class: 'control-label'
.col-sm-10
- if issuable.project.labels.any?
.col-sm-10{ class: ('issuable-form-padding-top' if !has_labels) }
- if has_labels
= f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
{ selected: issuable.label_ids }, multiple: true, class: 'select2', data: { placeholder: "Select labels" }
- else
.prepend-top-10
%span.light No labels yet.
&nbsp;
- if can? current_user, :admin_label, issuable.project
......@@ -128,8 +129,6 @@
- else
.pull-right
- if current_user.can?(:"destroy_#{issuable.to_ability_name}", @project)
= link_to polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" },
method: :delete, class: 'btn btn-grouped' do
= icon('trash-o')
Delete
= link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" },
method: :delete, class: 'btn btn-danger btn-grouped'
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel'
......@@ -49,7 +49,7 @@
.selectbox.hide-collapsed
= f.hidden_field 'assignee_id', value: issuable.assignee_id, id: 'issue_assignee_id'
= dropdown_tag('Select assignee', options: { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), field_name: "#{issuable.to_ability_name}[assignee_id]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } })
= dropdown_tag('Select assignee', options: { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_id]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } })
.block.milestone
.sidebar-collapsed-icon
......@@ -128,7 +128,7 @@
.title.hide-collapsed
Notifications
- subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
%button.btn.btn-block.btn-gray.subscribe-button.hide-collapsed{:type => 'button'}
%button.btn.btn-block.btn-gray.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" }
%span= subscribed ? 'Unsubscribe' : 'Subscribe'
.subscription-status.hide-collapsed{data: {status: subscribtion_status}}
.unsubscribed{class: ( 'hidden' if subscribed )}
......@@ -152,4 +152,4 @@
new LabelsSelect();
new IssuableContext('#{current_user.to_json(only: [:username, :id, :name])}');
new Subscription('.subscription')
new Sidebar();
\ No newline at end of file
new Sidebar();
......@@ -4,15 +4,16 @@
%li
%span.label-row
= link_to milestones_label_path(options) do
- render_colored_label(label, tooltip: false)
%span.prepend-left-10
%span.label-name
= link_to milestones_label_path(options) do
- render_colored_label(label, tooltip: false)
%span.prepend-description-left
= markdown(label.description, pipeline: :single_line)
.pull-right
%strong.issues-count
.pull-info-right
%span.append-right-20
= link_to milestones_label_path(options.merge(state: 'opened')) do
- pluralize milestone_issues_by_label_count(@milestone, label, state: :opened), 'open issue'
%strong.issues-count
%span.append-right-20
= link_to milestones_label_path(options.merge(state: 'closed')) do
- pluralize milestone_issues_by_label_count(@milestone, label, state: :closed), 'closed issue'
class AdminEmailWorker
include Sidekiq::Worker
sidekiq_options retry: false # this job auto-repeats via sidekiq-cron
def perform
repository_check_failed_count = Project.where(last_repository_check_failed: true).count
return if repository_check_failed_count.zero?
RepositoryCheckMailer.notify(repository_check_failed_count).deliver_now
end
end
......@@ -40,7 +40,7 @@ class PostReceive
if Gitlab::Git.tag_ref?(ref)
GitTagPushService.new.execute(post_received.project, @user, oldrev, newrev, ref)
else
elsif Gitlab::Git.branch_ref?(ref)
GitPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute
end
end
......
module RepositoryCheck
class BatchWorker
include Sidekiq::Worker
RUN_TIME = 3600
sidekiq_options retry: false
def perform
start = Time.now
# This loop will break after a little more than one hour ('a little
# more' because `git fsck` may take a few minutes), or if it runs out of
# projects to check. By default sidekiq-cron will start a new
# RepositoryCheckWorker each hour so that as long as there are repositories to
# check, only one (or two) will be checked at a time.
project_ids.each do |project_id|
break if Time.now - start >= RUN_TIME
break unless current_settings.repository_checks_enabled
next unless try_obtain_lease(project_id)
SingleRepositoryWorker.new.perform(project_id)
end
end
private
# Project.find_each does not support WHERE clauses and
# Project.find_in_batches does not support ordering. So we just build an
# array of ID's. This is OK because we do it only once an hour, because
# getting ID's from Postgres is not terribly slow, and because no user
# has to sit and wait for this query to finish.
def project_ids
limit = 10_000
never_checked_projects = Project.where('last_repository_check_at IS NULL').limit(limit).
pluck(:id)
old_check_projects = Project.where('last_repository_check_at < ?', 1.month.ago).
reorder('last_repository_check_at ASC').limit(limit).pluck(:id)
never_checked_projects + old_check_projects
end
def try_obtain_lease(id)
# Use a 24-hour timeout because on servers/projects where 'git fsck' is
# super slow we definitely do not want to run it twice in parallel.
Gitlab::ExclusiveLease.new(
"project_repository_check:#{id}",
timeout: 24.hours
).try_obtain
end
def current_settings
# No caching of the settings! If we cache them and an admin disables
# this feature, an active RepositoryCheckWorker would keep going for up
# to 1 hour after the feature was disabled.
if Rails.env.test?
Gitlab::CurrentSettings.fake_application_settings
else
ApplicationSetting.current
end
end
end
end
module RepositoryCheck
class ClearWorker
include Sidekiq::Worker
sidekiq_options retry: false
def perform
# Do small batched updates because these updates will be slow and locking
Project.select(:id).find_in_batches(batch_size: 100) do |batch|
Project.where(id: batch.map(&:id)).update_all(
last_repository_check_failed: nil,
last_repository_check_at: nil,
)
end
end
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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