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. 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)
- All service classes (those residing in app/services) are now instrumented (Yorick Peterse) - The Projects::HousekeepingService class has extra instrumentation
- Developers can now add custom tags to transactions (Yorick Peterse) - 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) - 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) - 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). - 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) - 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 - 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 - 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) - 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 - 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 - 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
- API: Ability to update a group (Robert Schilling) - API: Ability to update a group (Robert Schilling)
- API: Ability to move issues (Robert Schilling) - API: Ability to move issues (Robert Schilling)
- Fix Error 500 after renaming a project path (Stan Hu) - 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 - Fix avatar stretching by providing a cropping feature
- API: Expose `subscribed` for issues and merge requests (Robert Schilling) - API: Expose `subscribed` for issues and merge requests (Robert Schilling)
- Allow SAML to handle external users based on user's information !3530 - Allow SAML to handle external users based on user's information !3530
- Allow Omniauth providers to be marked as `external` !3657 - Allow Omniauth providers to be marked as `external` !3657
- Add endpoints to archive or unarchive a project !3372 - 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 - 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 automated repository integrity checks
- API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling) - 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 - Add default scope to projects to exclude projects pending deletion
- Allow to close merge requests which source projects(forks) are deleted. - Allow to close merge requests which source projects(forks) are deleted.
- Ensure empty recipients are rejected in BuildsEmailService - 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: Ability to filter milestones by state `active` and `closed` (Robert Schilling)
- API: Fix milestone filtering by `iid` (Robert Schilling) - API: Fix milestone filtering by `iid` (Robert Schilling)
- API: Delete notes of issues, snippets, and merge requests (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.) - Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.)
- Better errors handling when creating milestones inside groups - 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 - 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.) - 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) - Gracefully handle notes on deleted commits in merge requests (Stan Hu)
- Decouple membership and notifications - Decouple membership and notifications
- Fix creation of merge requests for orphaned branches (Stan Hu) - Fix creation of merge requests for orphaned branches (Stan Hu)
...@@ -52,10 +67,19 @@ v 8.7.0 (unreleased) ...@@ -52,10 +67,19 @@ v 8.7.0 (unreleased)
- API: Expose 'updated_at' for issue, snippet, and merge request notes (Robert Schilling) - 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 - 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 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 v 8.6.6
- Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk) - Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413
- Project switcher uses new dropdown styling - 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 v 8.6.5
- Fix importing from GitHub Enterprise. !3529 - Fix importing from GitHub Enterprise. !3529
...@@ -255,7 +279,7 @@ v 8.5.1 ...@@ -255,7 +279,7 @@ v 8.5.1
v 8.5.0 v 8.5.0
- Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu) - 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) - 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 - 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) - Update New Relic gem to 3.14.1.311 (Stan Hu)
......
...@@ -22,7 +22,17 @@ ...@@ -22,7 +22,17 @@
#= require cal-heatmap #= require cal-heatmap
#= require turbolinks #= require turbolinks
#= require autosave #= 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 select2
#= require raphael #= require raphael
#= require g.raphael #= require g.raphael
......
...@@ -29,7 +29,11 @@ $(document).on 'keydown.quick_submit', '.js-quick-submit', (e) -> ...@@ -29,7 +29,11 @@ $(document).on 'keydown.quick_submit', '.js-quick-submit', (e) ->
e.preventDefault() e.preventDefault()
$form = $(e.target).closest('form') $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() $form.submit()
# If the user tabs to a submit button on a `js-quick-submit` form, display a # If the user tabs to a submit button on a `js-quick-submit` form, display a
......
...@@ -28,26 +28,26 @@ class Dispatcher ...@@ -28,26 +28,26 @@ class Dispatcher
new Todos() new Todos()
when 'projects:milestones:new', 'projects:milestones:edit' when 'projects:milestones:new', 'projects:milestones:edit'
new ZenMode() new ZenMode()
new DropzoneInput($('.milestone-form')) new GLForm($('.milestone-form'))
when 'groups:milestones:new' when 'groups:milestones:new'
new ZenMode() new ZenMode()
when 'projects:compare:show' when 'projects:compare:show'
new Diff() new Diff()
when 'projects:issues:new','projects:issues:edit' when 'projects:issues:new','projects:issues:edit'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new DropzoneInput($('.issue-form')) new GLForm($('.issue-form'))
new IssuableForm($('.issue-form')) new IssuableForm($('.issue-form'))
when 'projects:merge_requests:new', 'projects:merge_requests:edit' when 'projects:merge_requests:new', 'projects:merge_requests:edit'
new Diff() new Diff()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new DropzoneInput($('.merge-request-form')) new GLForm($('.merge-request-form'))
new IssuableForm($('.merge-request-form')) new IssuableForm($('.merge-request-form'))
when 'projects:tags:new' when 'projects:tags:new'
new ZenMode() new ZenMode()
new DropzoneInput($('.tag-form')) new GLForm($('.tag-form'))
when 'projects:releases:edit' when 'projects:releases:edit'
new ZenMode() new ZenMode()
new DropzoneInput($('.release-form')) new GLForm($('.release-form'))
when 'projects:merge_requests:show' when 'projects:merge_requests:show'
new Diff() new Diff()
shortcut_handler = new ShortcutsIssuable(true) shortcut_handler = new ShortcutsIssuable(true)
...@@ -137,7 +137,7 @@ class Dispatcher ...@@ -137,7 +137,7 @@ class Dispatcher
new Wikis() new Wikis()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new ZenMode() new ZenMode()
new DropzoneInput($('.wiki-form')) new GLForm($('.wiki-form'))
when 'snippets' when 'snippets'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new ZenMode() if path[2] == 'show' new ZenMode() if path[2] == 'show'
......
...@@ -15,11 +15,13 @@ class @DropzoneInput ...@@ -15,11 +15,13 @@ class @DropzoneInput
project_uploads_path = window.project_uploads_path or null project_uploads_path = window.project_uploads_path or null
max_file_size = gon.max_file_size or 10 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.wrap "<div class=\"div-dropzone\"></div>"
form_textarea.on 'paste', (event) => form_textarea.on 'paste', (event) =>
handlePaste(event) handlePaste(event)
$mdArea = $(form_textarea).closest('.md-area')
$(form).setupMarkdownPreview() $(form).setupMarkdownPreview()
form_dropzone = $(form).find('.div-dropzone') form_dropzone = $(form).find('.div-dropzone')
...@@ -49,17 +51,16 @@ class @DropzoneInput ...@@ -49,17 +51,16 @@ class @DropzoneInput
$(".div-dropzone-alert").alert "close" $(".div-dropzone-alert").alert "close"
dragover: -> dragover: ->
form_textarea.addClass "div-dropzone-focus" $mdArea.addClass 'is-dropzone-hover'
form.find(".div-dropzone-hover").css "opacity", 0.7 form.find(".div-dropzone-hover").css "opacity", 0.7
return return
dragleave: -> dragleave: ->
form_textarea.removeClass "div-dropzone-focus" $mdArea.removeClass 'is-dropzone-hover'
form.find(".div-dropzone-hover").css "opacity", 0 form.find(".div-dropzone-hover").css "opacity", 0
return return
drop: -> drop: ->
form_textarea.removeClass "div-dropzone-focus"
form.find(".div-dropzone-hover").css "opacity", 0 form.find(".div-dropzone-hover").css "opacity", 0
form_textarea.focus() form_textarea.focus()
return return
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
window.GitLab ?= {} window.GitLab ?= {}
GitLab.GfmAutoComplete = GitLab.GfmAutoComplete =
dataLoading: false
dataSource: '' dataSource: ''
# Emoji # Emoji
...@@ -17,17 +19,41 @@ GitLab.GfmAutoComplete = ...@@ -17,17 +19,41 @@ GitLab.GfmAutoComplete =
template: '<li><small>${id}</small> ${title}</li>' template: '<li><small>${id}</small> ${title}</li>'
# Add GFM auto-completion to all input fields, that accept GFM input. # Add GFM auto-completion to all input fields, that accept GFM input.
setup: -> setup: (wrap) ->
input = $('.js-gfm-input') @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 # Emoji
input.atwho @input.atwho
at: ':' at: ':'
displayTpl: @Emoji.template displayTpl: @Emoji.template
insertTpl: ':${name}:' insertTpl: ':${name}:'
# Team Members # Team Members
input.atwho @input.atwho
at: '@' at: '@'
displayTpl: @Members.template displayTpl: @Members.template
insertTpl: '${atwho-at}${username}' insertTpl: '${atwho-at}${username}'
...@@ -42,7 +68,7 @@ GitLab.GfmAutoComplete = ...@@ -42,7 +68,7 @@ GitLab.GfmAutoComplete =
title: sanitize(title) title: sanitize(title)
search: sanitize("#{m.username} #{m.name}") search: sanitize("#{m.username} #{m.name}")
input.atwho @input.atwho
at: '#' at: '#'
alias: 'issues' alias: 'issues'
searchKey: 'search' searchKey: 'search'
...@@ -55,7 +81,7 @@ GitLab.GfmAutoComplete = ...@@ -55,7 +81,7 @@ GitLab.GfmAutoComplete =
title: sanitize(i.title) title: sanitize(i.title)
search: "#{i.iid} #{i.title}" search: "#{i.iid} #{i.title}"
input.atwho @input.atwho
at: '!' at: '!'
alias: 'mergerequests' alias: 'mergerequests'
searchKey: 'search' searchKey: 'search'
...@@ -68,13 +94,18 @@ GitLab.GfmAutoComplete = ...@@ -68,13 +94,18 @@ GitLab.GfmAutoComplete =
title: sanitize(m.title) title: sanitize(m.title)
search: "#{m.iid} #{m.title}" search: "#{m.iid} #{m.title}"
if @dataSource destroyAtWho: ->
$.getJSON(@dataSource).done (data) -> @input.atwho('destroy')
# load members
input.atwho 'load', '@', data.members fetchData: (dataSource) ->
# load issues $.getJSON(dataSource)
input.atwho 'load', 'issues', data.issues
# load merge requests loadData: (data) ->
input.atwho 'load', 'mergerequests', data.mergerequests # load members
# load emojis @input.atwho 'load', '@', data.members
input.atwho 'load', ':', data.emojis # 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 ...@@ -10,6 +10,9 @@ class @Issue
@initTaskList() @initTaskList()
@initIssueBtnEventListeners() @initIssueBtnEventListeners()
@initMergeRequests()
@initRelatedBranches()
initTaskList: -> initTaskList: ->
$('.detail-page-description .js-task-list-container').taskList('enable') $('.detail-page-description .js-task-list-container').taskList('enable')
$(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList $(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
...@@ -69,3 +72,23 @@ class @Issue ...@@ -69,3 +72,23 @@ class @Issue
type: 'PATCH' type: 'PATCH'
url: $('form.js-issuable-update').attr('action') url: $('form.js-issuable-update').attr('action')
data: patchData 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 ...@@ -34,7 +34,7 @@ class @LabelsSelect
labelHTMLTemplate = _.template( labelHTMLTemplate = _.template(
'<% _.each(labels, function(label){ %> '<% _.each(labels, function(label){ %>
<a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name=<%= label.title %>"> <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 %> <%= label.title %>
</span> </span>
</a> </a>
...@@ -165,6 +165,8 @@ class @LabelsSelect ...@@ -165,6 +165,8 @@ class @LabelsSelect
.html(template) .html(template)
$sidebarCollapsedValue.text(labelCount) $sidebarCollapsedValue.text(labelCount)
$('.has-tooltip', $value).tooltip(container: 'body')
$value $value
.find('a') .find('a')
.each((i) -> .each((i) ->
......
...@@ -85,8 +85,10 @@ class @MergeRequestTabs ...@@ -85,8 +85,10 @@ class @MergeRequestTabs
scrollToElement: (container) -> scrollToElement: (container) ->
if window.location.hash if window.location.hash
$el = $("div#{container} #{window.location.hash}") navBarHeight = $('.navbar-gitlab').outerHeight()
$('body').scrollTo($el.offset().top) if $el.length
$el = $("#{container} #{window.location.hash}")
$.scrollTo("#{container} #{window.location.hash}", offset: -navBarHeight) if $el.length
# Activate a tab based on the current action # Activate a tab based on the current action
activateTab: (action) -> activateTab: (action) ->
...@@ -152,12 +154,38 @@ class @MergeRequestTabs ...@@ -152,12 +154,38 @@ class @MergeRequestTabs
@_get @_get
url: "#{source}.json" + @_location.search url: "#{source}.json" + @_location.search
success: (data) => success: (data) =>
document.querySelector("div#diffs").innerHTML = data.html $('#diffs').html data.html
gl.utils.localTimeAgo($('.js-timeago', 'div#diffs')) gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'))
$('div#diffs .js-syntax-highlight').syntaxHighlight() $('#diffs .js-syntax-highlight').syntaxHighlight()
@expandViewContainer() if @diffViewType() is 'parallel' @expandViewContainer() if @diffViewType() is 'parallel'
@diffsLoaded = true @diffsLoaded = true
@scrollToElement("#diffs") @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) -> loadBuilds: (source) ->
return if @buildsLoaded return if @buildsLoaded
......
...@@ -283,32 +283,10 @@ class @Notes ...@@ -283,32 +283,10 @@ class @Notes
show the form show the form
### ###
setupNoteForm: (form) -> setupNoteForm: (form) ->
disableButtonIfEmptyField form.find(".js-note-text"), form.find(".js-comment-button") new GLForm form
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")
textarea = form.find(".js-note-text") 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, [ new Autosave textarea, [
"Note" "Note"
form.find("#note_commit_id").val() form.find("#note_commit_id").val()
...@@ -317,11 +295,6 @@ class @Notes ...@@ -317,11 +295,6 @@ class @Notes
form.find("#note_noteable_id").val() 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 Called in response to the new note form being submitted
...@@ -375,34 +348,15 @@ class @Notes ...@@ -375,34 +348,15 @@ class @Notes
note = $(this).closest(".note") note = $(this).closest(".note")
note.addClass "is-editting" note.addClass "is-editting"
form = note.find(".note-edit-form") form = note.find(".note-edit-form")
isNewForm = form.is(':not(.gfm-form)')
if isNewForm
form.addClass('gfm-form')
form.addClass('current-note-edit-form') form.addClass('current-note-edit-form')
# Show the attachment delete link # Show the attachment delete link
note.find(".js-note-attachment-delete").show() note.find(".js-note-attachment-delete").show()
# Setup markdown form new GLForm 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
if isNewForm form.find(".js-note-text").focus()
disableButtonIfEmptyField textarea, form.find(".js-comment-button")
### ###
Called in response to clicking the edit note link Called in response to clicking the edit note link
...@@ -559,6 +513,9 @@ class @Notes ...@@ -559,6 +513,9 @@ class @Notes
removeDiscussionNoteForm: (form)-> removeDiscussionNoteForm: (form)->
row = form.closest("tr") row = form.closest("tr")
glForm = form.data 'gl-form'
glForm.destroy()
form.find(".js-note-text").data("autosave").reset() form.find(".js-note-text").data("autosave").reset()
# show the reply button (will only work for replies) # show the reply button (will only work for replies)
...@@ -570,7 +527,6 @@ class @Notes ...@@ -570,7 +527,6 @@ class @Notes
# only remove the form # only remove the form
form.remove() form.remove()
cancelDiscussionForm: (e) => cancelDiscussionForm: (e) =>
e.preventDefault() e.preventDefault()
form = $(e.target).closest(".js-discussion-note-form") form = $(e.target).closest(".js-discussion-note-form")
......
...@@ -2,7 +2,7 @@ class @Subscription ...@@ -2,7 +2,7 @@ class @Subscription
constructor: (container) -> constructor: (container) ->
$container = $(container) $container = $(container)
@url = $container.attr('data-url') @url = $container.attr('data-url')
@subscribe_button = $container.find('.subscribe-button') @subscribe_button = $container.find('.js-subscribe-button')
@subscription_status = $container.find('.subscription-status') @subscription_status = $container.find('.subscription-status')
@subscribe_button.unbind('click').click(@toggleSubscription) @subscribe_button.unbind('click').click(@toggleSubscription)
......
...@@ -12,6 +12,7 @@ class @UsersSelect ...@@ -12,6 +12,7 @@ class @UsersSelect
showNullUser = $dropdown.data('null-user') showNullUser = $dropdown.data('null-user')
showAnyUser = $dropdown.data('any-user') showAnyUser = $dropdown.data('any-user')
firstUser = $dropdown.data('first-user') firstUser = $dropdown.data('first-user')
@authorId = $dropdown.data('author-id')
selectedId = $dropdown.data('selected') selectedId = $dropdown.data('selected')
defaultLabel = $dropdown.data('default-label') defaultLabel = $dropdown.data('default-label')
issueURL = $dropdown.data('issueUpdate') issueURL = $dropdown.data('issueUpdate')
...@@ -207,6 +208,7 @@ class @UsersSelect ...@@ -207,6 +208,7 @@ class @UsersSelect
@projectId = $(select).data('project-id') @projectId = $(select).data('project-id')
@groupId = $(select).data('group-id') @groupId = $(select).data('group-id')
@showCurrentUser = $(select).data('current-user') @showCurrentUser = $(select).data('current-user')
@authorId = $(select).data('author-id')
showNullUser = $(select).data('null-user') showNullUser = $(select).data('null-user')
showAnyUser = $(select).data('any-user') showAnyUser = $(select).data('any-user')
showEmailUser = $(select).data('email-user') showEmailUser = $(select).data('email-user')
...@@ -312,6 +314,7 @@ class @UsersSelect ...@@ -312,6 +314,7 @@ class @UsersSelect
project_id: @projectId project_id: @projectId
group_id: @groupId group_id: @groupId
current_user: @showCurrentUser current_user: @showCurrentUser
author_id: @authorId
dataType: "json" dataType: "json"
).done (users) -> ).done (users) ->
callback(users) callback(users)
......
/** /**
* Styles that apply to all GFM related forms. * 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 { .gfm-commit, .gfm-commit_range {
font-family: $monospace_font; font-family: $monospace_font;
......
.div-dropzone-wrapper { .div-dropzone-wrapper {
.div-dropzone { .div-dropzone {
position: relative; 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 { .div-dropzone-hover {
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
margin-top: -0.5em; margin-top: -11.5px;
margin-left: -0.6em; margin-left: -15px;
opacity: 0; opacity: 0;
font-size: 50px; font-size: 30px;
transition: opacity 200ms ease-in-out; transition: opacity 200ms ease-in-out;
pointer-events: none; pointer-events: none;
} }
......
...@@ -43,7 +43,6 @@ ...@@ -43,7 +43,6 @@
@import "bootstrap/modals"; @import "bootstrap/modals";
@import "bootstrap/tooltip"; @import "bootstrap/tooltip";
@import "bootstrap/popovers"; @import "bootstrap/popovers";
@import "bootstrap/carousel";
// Utility classes // Utility classes
.clearfix { .clearfix {
......
...@@ -250,14 +250,6 @@ a > code { ...@@ -250,14 +250,6 @@ a > code {
* Textareas intended for GFM * Textareas intended for GFM
* *
*/ */
.js-gfm-input {
font-family: $monospace_font;
color: $gl-text-color;
}
.md-preview {
}
.strikethrough { .strikethrough {
text-decoration: line-through; text-decoration: line-through;
} }
......
...@@ -150,6 +150,7 @@ $light-grey-header: #faf9f9; ...@@ -150,6 +150,7 @@ $light-grey-header: #faf9f9;
*/ */
$gl-primary: $blue-normal; $gl-primary: $blue-normal;
$gl-success: $green-normal; $gl-success: $green-normal;
$gl-success-focus: rgba($gl-success, .4);
$gl-info: $blue-normal; $gl-info: $blue-normal;
$gl-warning: $orange-normal; $gl-warning: $orange-normal;
$gl-danger: $red-normal; $gl-danger: $red-normal;
......
...@@ -21,6 +21,12 @@ ...@@ -21,6 +21,12 @@
// Diff line // Diff line
.line_holder { .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 { .diff-line-num.new, .line_content.new {
@include diff_background(rgba(51, 255, 51, 0.1), rgba(51, 255, 51, 0.2), #808080); @include diff_background(rgba(51, 255, 51, 0.1), rgba(51, 255, 51, 0.2), #808080);
} }
......
...@@ -21,6 +21,12 @@ ...@@ -21,6 +21,12 @@
// Diff line // Diff line
.line_holder { .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 { .diff-line-num.new, .line_content.new {
@include diff_background(rgba(166, 226, 46, 0.1), rgba(166, 226, 46, 0.15), #808080); @include diff_background(rgba(166, 226, 46, 0.1), rgba(166, 226, 46, 0.15), #808080);
} }
......
...@@ -21,6 +21,12 @@ ...@@ -21,6 +21,12 @@
// Diff line // Diff line
.line_holder { .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 { .diff-line-num.new, .line_content.new {
@include diff_background(rgba(133, 153, 0, 0.15), rgba(133, 153, 0, 0.25), #113b46); @include diff_background(rgba(133, 153, 0, 0.15), rgba(133, 153, 0, 0.25), #113b46);
} }
......
...@@ -21,6 +21,12 @@ ...@@ -21,6 +21,12 @@
// Diff line // Diff line
.line_holder { .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 { .diff-line-num.new, .line_content.new {
@include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.25), #c5d0d4); @include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.25), #c5d0d4);
} }
......
...@@ -21,6 +21,12 @@ ...@@ -21,6 +21,12 @@
// Diff line // Diff line
.line_holder { .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 { .diff-line-num {
&.old { &.old {
background-color: $line-number-old; background-color: $line-number-old;
......
...@@ -75,6 +75,11 @@ li.commit { ...@@ -75,6 +75,11 @@ li.commit {
} }
} }
.item-title {
display: inline-block;
max-width: 70%;
}
.commit-row-description { .commit-row-description {
font-size: 14px; font-size: 14px;
border-left: 1px solid #eee; border-left: 1px solid #eee;
......
...@@ -67,6 +67,24 @@ ...@@ -67,6 +67,24 @@
line-height: $code_line_height; line-height: $code_line_height;
font-size: $code_font_size; font-size: $code_font_size;
&.noteable_line {
position: relative;
&.old {
&:before {
content: '-';
position: absolute;
}
}
&.new {
&:before {
content: '+';
position: absolute;
}
}
}
span { span {
white-space: pre; white-space: pre;
} }
...@@ -391,3 +409,23 @@ ...@@ -391,3 +409,23 @@
margin-bottom: 0; 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 @@ ...@@ -173,12 +173,6 @@
} }
} }
.subscribe-button {
span {
margin-top: 0;
}
}
&.right-sidebar-collapsed { &.right-sidebar-collapsed {
/* Extra small devices (phones, less than 768px) */ /* Extra small devices (phones, less than 768px) */
display: none; display: none;
...@@ -322,3 +316,9 @@ ...@@ -322,3 +316,9 @@
color: #8c8c8c; color: #8c8c8c;
} }
} }
.issuable-form-padding-top {
@media (min-width: $screen-sm-min) {
padding-top: 7px;
}
}
...@@ -79,19 +79,30 @@ ...@@ -79,19 +79,30 @@
color: $white-light; 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 { .manage-labels-list {
.prepend-left-10 { .prepend-left-10, .prepend-description-left {
display: inline-block; display: inline-block;
width: 40%; width: 40%;
vertical-align: middle; vertical-align: middle;
@media (max-width: $screen-xs-min) { @include labels-mobile;
display: block; }
width: 100%;
margin-left: 0; .prepend-description-left {
padding: 10px 0; width: 57%;
}
@include labels-mobile;
} }
.pull-info-right { .pull-info-right {
...@@ -106,7 +117,7 @@ ...@@ -106,7 +117,7 @@
padding: 6px; padding: 6px;
color: $gl-text-color; color: $gl-text-color;
&.subscribe-button { &.label-subscribe-button {
padding-left: 0; padding-left: 0;
} }
} }
......
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
} }
.note-textarea { .note-textarea {
display: block;
padding: 10px 0; padding: 10px 0;
font-family: $regular_font; font-family: $regular_font;
border: 0; border: 0;
...@@ -63,7 +64,7 @@ ...@@ -63,7 +64,7 @@
&.is-focused { &.is-focused {
border-color: $focus-border-color; 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); 0 0 4px rgba($focus-border-color, .4);
.comment-toolbar, .comment-toolbar,
...@@ -72,6 +73,17 @@ ...@@ -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 { p {
code { code {
white-space: normal; white-space: normal;
......
...@@ -276,8 +276,7 @@ ul.notes { ...@@ -276,8 +276,7 @@ ul.notes {
.diff-file tr.line_holder { .diff-file tr.line_holder {
@mixin show-add-diff-note { @mixin show-add-diff-note {
filter: alpha(opacity=100); display: inline-block;
opacity: 1.0;
} }
.add-diff-note { .add-diff-note {
...@@ -291,13 +290,8 @@ ul.notes { ...@@ -291,13 +290,8 @@ ul.notes {
position: absolute; position: absolute;
z-index: 10; z-index: 10;
width: 32px; width: 32px;
transition: all 0.2s ease;
// "hide" it by default // "hide" it by default
opacity: 0.0; display: none;
filter: alpha(opacity=0);
&:hover { &:hover {
background: $gl-info; background: $gl-info;
color: #fff; color: #fff;
......
...@@ -19,6 +19,15 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -19,6 +19,15 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
redirect_to admin_runners_path redirect_to admin_runners_path
end 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 private
def set_application_setting def set_application_setting
...@@ -82,6 +91,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -82,6 +91,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:akismet_enabled, :akismet_enabled,
:akismet_api_key, :akismet_api_key,
:email_author_in_body, :email_author_in_body,
:repository_checks_enabled,
restricted_visibility_levels: [], restricted_visibility_levels: [],
import_sources: [] import_sources: []
) )
......
class Admin::ProjectsController < Admin::ApplicationController 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] before_action :group, only: [:show, :transfer]
def index def index
...@@ -8,6 +8,7 @@ class Admin::ProjectsController < Admin::ApplicationController ...@@ -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.where("projects.visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present?
@projects = @projects.with_push if params[:with_push].present? @projects = @projects.with_push if params[:with_push].present?
@projects = @projects.abandoned if params[:abandoned].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.non_archived unless params[:with_archived].present?
@projects = @projects.search(params[:name]) if params[:name].present? @projects = @projects.search(params[:name]) if params[:name].present?
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
...@@ -30,6 +31,15 @@ class Admin::ProjectsController < Admin::ApplicationController ...@@ -30,6 +31,15 @@ class Admin::ProjectsController < Admin::ApplicationController
redirect_to admin_namespace_project_path(@project.namespace, @project) redirect_to admin_namespace_project_path(@project.namespace, @project)
end 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 protected
def project def project
......
...@@ -3,6 +3,7 @@ require 'fogbugz' ...@@ -3,6 +3,7 @@ require 'fogbugz'
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
include Gitlab::GonHelper
include GitlabRoutingHelper include GitlabRoutingHelper
include PageLayoutHelper include PageLayoutHelper
...@@ -13,7 +14,7 @@ class ApplicationController < ActionController::Base ...@@ -13,7 +14,7 @@ class ApplicationController < ActionController::Base
before_action :check_password_expiration before_action :check_password_expiration
before_action :check_2fa_requirement before_action :check_2fa_requirement
before_action :ldap_security_check before_action :ldap_security_check
before_action :sentry_user_context before_action :sentry_context
before_action :default_headers before_action :default_headers
before_action :add_gon_variables before_action :add_gon_variables
before_action :configure_permitted_parameters, if: :devise_controller? before_action :configure_permitted_parameters, if: :devise_controller?
...@@ -40,13 +41,15 @@ class ApplicationController < ActionController::Base ...@@ -40,13 +41,15 @@ class ApplicationController < ActionController::Base
protected protected
def sentry_user_context def sentry_context
if Rails.env.production? && current_application_settings.sentry_enabled && current_user if Rails.env.production? && current_application_settings.sentry_enabled
Raven.user_context( if current_user
id: current_user.id, Raven.user_context(
email: current_user.email, id: current_user.id,
username: current_user.username, email: current_user.email,
) username: current_user.username,
)
end
Raven.tags_context(program: sentry_program_context) Raven.tags_context(program: sentry_program_context)
end end
...@@ -158,20 +161,6 @@ class ApplicationController < ActionController::Base ...@@ -158,20 +161,6 @@ class ApplicationController < ActionController::Base
end end
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! def validate_user_service_ticket!
return unless signed_in? && session[:service_tickets] return unless signed_in? && session[:service_tickets]
......
...@@ -12,8 +12,15 @@ class AutocompleteController < ApplicationController ...@@ -12,8 +12,15 @@ class AutocompleteController < ApplicationController
if params[:search].blank? if params[:search].blank?
# Include current user if available to filter by "Me" # Include current user if available to filter by "Me"
if params[:current_user] && current_user if params[:current_user] && current_user
@users = [*@users, current_user].uniq @users = [*@users, current_user]
end end
if params[:author_id].present?
author = User.find_by_id(params[:author_id])
@users = [author, *@users] if author
end
@users.uniq!
end end
render json: @users, only: [:name, :username, :id], methods: [:avatar_url] render json: @users, only: [:name, :username, :id], methods: [:avatar_url]
......
...@@ -40,6 +40,7 @@ class GroupsController < Groups::ApplicationController ...@@ -40,6 +40,7 @@ class GroupsController < Groups::ApplicationController
@last_push = current_user.recent_push if current_user @last_push = current_user.recent_push if current_user
@projects = @projects.includes(:namespace) @projects = @projects.includes(:namespace)
@projects = @projects.sorted_by_activity
@projects = filter_projects(@projects) @projects = filter_projects(@projects)
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]) if params[:filter_projects].blank? @projects = @projects.page(params[:page]) if params[:filter_projects].blank?
......
class Oauth::ApplicationsController < Doorkeeper::ApplicationsController class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
include Gitlab::GonHelper
include PageLayoutHelper include PageLayoutHelper
before_action :verify_user_oauth_applications_enabled before_action :verify_user_oauth_applications_enabled
before_action :authenticate_user! before_action :authenticate_user!
before_action :add_gon_variables
layout 'profile' layout 'profile'
......
...@@ -10,6 +10,11 @@ class Profiles::KeysController < Profiles::ApplicationController ...@@ -10,6 +10,11 @@ class Profiles::KeysController < Profiles::ApplicationController
@key = current_user.keys.find(params[:id]) @key = current_user.keys.find(params[:id])
end 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 def create
@key = current_user.keys.new(key_params) @key = current_user.keys.new(key_params)
......
...@@ -3,7 +3,8 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -3,7 +3,8 @@ class Projects::IssuesController < Projects::ApplicationController
include IssuableActions include IssuableActions
before_action :module_enabled 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 # Allow read any issue
before_action :authorize_read_issue!, only: [:show] before_action :authorize_read_issue!, only: [:show]
...@@ -17,9 +18,6 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -17,9 +18,6 @@ class Projects::IssuesController < Projects::ApplicationController
# Allow issues bulk update # Allow issues bulk update
before_action :authorize_admin_issues!, only: [: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 respond_to :html
def index def index
...@@ -62,11 +60,9 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -62,11 +60,9 @@ class Projects::IssuesController < Projects::ApplicationController
end end
def show def show
@note = @project.notes.new(noteable: @issue) @note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.nonawards.with_associations.fresh @notes = @issue.notes.nonawards.with_associations.fresh
@noteable = @issue @noteable = @issue
@merge_requests = @issue.referenced_merge_requests(current_user)
@related_branches = @issue.related_branches - @merge_requests.map(&:source_branch)
respond_to do |format| respond_to do |format|
format.html format.html
...@@ -118,15 +114,39 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -118,15 +114,39 @@ class Projects::IssuesController < Projects::ApplicationController
end end
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 def bulk_update
result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute
redirect_back_or_default(default: { action: 'index' }, options: { notice: "#{result[:count]} issues updated" }) redirect_back_or_default(default: { action: 'index' }, options: { notice: "#{result[:count]} issues updated" })
end end
def closed_by_merge_requests
@closed_by_merge_requests ||= @issue.closed_by_merge_requests(current_user)
end
protected protected
def issue def issue
......
...@@ -40,10 +40,11 @@ module DiffHelper ...@@ -40,10 +40,11 @@ module DiffHelper
(unfold) ? 'unfold js-unfold' : '' (unfold) ? 'unfold js-unfold' : ''
end end
def diff_line_content(line) def diff_line_content(line, line_type = nil)
if line.blank? if line.blank?
" &nbsp;".html_safe " &nbsp;".html_safe
else else
line[0] = ' ' if %w[new old].include?(line_type)
line line
end end
end end
......
...@@ -6,12 +6,13 @@ module SelectsHelper ...@@ -6,12 +6,13 @@ module SelectsHelper
value = opts[:selected] || '' value = opts[:selected] || ''
placeholder = opts[:placeholder] || 'Search for a user' placeholder = opts[:placeholder] || 'Search for a user'
null_user = opts[:null_user] || false null_user = opts[:null_user] || false
any_user = opts[:any_user] || false any_user = opts[:any_user] || false
email_user = opts[:email_user] || false email_user = opts[:email_user] || false
first_user = opts[:first_user] && current_user ? current_user.username : false first_user = opts[:first_user] && current_user ? current_user.username : false
current_user = opts[:current_user] || false current_user = opts[:current_user] || false
project = opts[:project] || @project author_id = opts[:author_id] || ''
project = opts[:project] || @project
html = { html = {
class: css_class, class: css_class,
...@@ -21,7 +22,8 @@ module SelectsHelper ...@@ -21,7 +22,8 @@ module SelectsHelper
any_user: any_user, any_user: any_user,
email_user: email_user, email_user: email_user,
first_user: first_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 ...@@ -153,7 +153,8 @@ class ApplicationSetting < ActiveRecord::Base
require_two_factor_authentication: false, require_two_factor_authentication: false,
two_factor_grace_period: 48, two_factor_grace_period: 48,
recaptcha_enabled: false, recaptcha_enabled: false,
akismet_enabled: false akismet_enabled: false,
repository_checks_enabled: true,
) )
end end
......
...@@ -154,7 +154,7 @@ class Commit ...@@ -154,7 +154,7 @@ class Commit
id: id, id: id,
message: safe_message, message: safe_message,
timestamp: committed_date.xmlschema, timestamp: committed_date.xmlschema,
url: commit_url, url: Gitlab::UrlBuilder.build(self),
author: { author: {
name: author_name, name: author_name,
email: author_email email: author_email
...@@ -168,10 +168,6 @@ class Commit ...@@ -168,10 +168,6 @@ class Commit
data data
end 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 # Discover issues should be closed when this commit is pushed to a project's
# default branch. # default branch.
def closes_issues(current_user = self.committer) def closes_issues(current_user = self.committer)
......
...@@ -106,7 +106,7 @@ class Issue < ActiveRecord::Base ...@@ -106,7 +106,7 @@ class Issue < ActiveRecord::Base
def related_branches def related_branches
project.repository.branch_names.select do |branch| project.repository.branch_names.select do |branch|
branch.end_with?("-#{iid}") branch =~ /\A#{iid}-(?!\d+-stable)/i
end end
end end
...@@ -151,7 +151,7 @@ class Issue < ActiveRecord::Base ...@@ -151,7 +151,7 @@ class Issue < ActiveRecord::Base
end end
def to_branch_name def to_branch_name
"#{title.parameterize}-#{iid}" "#{iid}-#{title.parameterize}"
end end
def can_be_worked_on?(current_user) 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 ...@@ -409,6 +409,35 @@ class Project < ActiveRecord::Base
self.import_data.destroy if self.import_data self.import_data.destroy if self.import_data
end 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? def import?
external_import? || forked? external_import? || forked?
end end
...@@ -865,7 +894,9 @@ class Project < ActiveRecord::Base ...@@ -865,7 +894,9 @@ class Project < ActiveRecord::Base
def change_head(branch) def change_head(branch)
repository.before_change_head 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 reload_default_branch
end end
......
...@@ -12,8 +12,20 @@ require 'file_size_validator' ...@@ -12,8 +12,20 @@ require 'file_size_validator'
class ProjectImportData < ActiveRecord::Base class ProjectImportData < ActiveRecord::Base
belongs_to :project 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 serialize :data, JSON
validates :project, presence: true 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 end
...@@ -82,17 +82,17 @@ class BambooService < CiService ...@@ -82,17 +82,17 @@ class BambooService < CiService
end end
def build_info(sha) 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? if username.blank? && password.blank?
@response = HTTParty.get(parsed_url.to_s, verify: false) @response = HTTParty.get(url, verify: false)
else else
get_url = "#{url}&os_authType=basic" url << '&os_authType=basic'
auth = { auth = {
username: username, username: username,
password: password, password: password
} }
@response = HTTParty.get(get_url, verify: false, basic_auth: auth) @response = HTTParty.get(url, verify: false, basic_auth: auth)
end end
end end
...@@ -101,11 +101,11 @@ class BambooService < CiService ...@@ -101,11 +101,11 @@ class BambooService < CiService
if @response.code != 200 || @response['results']['results']['size'] == '0' if @response.code != 200 || @response['results']['results']['size'] == '0'
# If actual build link can't be determined, send user to build summary page. # 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 else
# If actual build link is available, go to build result page. # If actual build link is available, go to build result page.
result_key = @response['results']['results']['result']['planResultKey']['key'] result_key = @response['results']['results']['result']['planResultKey']['key']
"#{bamboo_url}/browse/#{result_key}" URI.join(bamboo_url, "/browse/#{result_key}").to_s
end end
end end
...@@ -134,7 +134,7 @@ class BambooService < CiService ...@@ -134,7 +134,7 @@ class BambooService < CiService
return unless supported_events.include?(data[:object_kind]) return unless supported_events.include?(data[:object_kind])
# Bamboo requires a GET and does not take any data. # Bamboo requires a GET and does not take any data.
self.class.get("#{bamboo_url}/updateAndBuild.action?buildKey=#{build_key}", url = URI.join(bamboo_url, "/updateAndBuild.action?buildKey=#{build_key}").to_s
verify: false) self.class.get(url, verify: false)
end end
end end
...@@ -23,7 +23,7 @@ class BuildsEmailService < Service ...@@ -23,7 +23,7 @@ class BuildsEmailService < Service
prop_accessor :recipients prop_accessor :recipients
boolean_accessor :add_pusher boolean_accessor :add_pusher
boolean_accessor :notify_only_broken_builds 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 def initialize_properties
if properties.nil? if properties.nil?
...@@ -87,10 +87,14 @@ class BuildsEmailService < Service ...@@ -87,10 +87,14 @@ class BuildsEmailService < Service
end end
def all_recipients(data) 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] if add_pusher? && data[:user][:email]
all_recipients << "#{data[:user][:email]}" all_recipients << data[:user][:email]
end end
all_recipients all_recipients
......
...@@ -85,13 +85,15 @@ class TeamcityService < CiService ...@@ -85,13 +85,15 @@ class TeamcityService < CiService
end end
def build_info(sha) def build_info(sha)
url = URI.parse("#{teamcity_url}/httpAuth/app/rest/builds/"\ url = URI.join(
"branch:unspecified:any,number:#{sha}") teamcity_url,
"/httpAuth/app/rest/builds/branch:unspecified:any,number:#{sha}"
).to_s
auth = { auth = {
username: username, 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 end
def build_page(sha, ref) def build_page(sha, ref)
...@@ -100,12 +102,14 @@ class TeamcityService < CiService ...@@ -100,12 +102,14 @@ class TeamcityService < CiService
if @response.code != 200 if @response.code != 200
# If actual build link can't be determined, # If actual build link can't be determined,
# send user to build summary page. # 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 else
# If actual build link is available, go to build result page. # If actual build link is available, go to build result page.
built_id = @response['build']['id'] built_id = @response['build']['id']
"#{teamcity_url}/viewLog.html?buildId=#{built_id}"\ URI.join(
"&buildTypeId=#{build_type}" teamcity_url,
"/viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}"
).to_s
end end
end end
...@@ -140,12 +144,13 @@ class TeamcityService < CiService ...@@ -140,12 +144,13 @@ class TeamcityService < CiService
branch = Gitlab::Git.ref_name(data[:ref]) branch = Gitlab::Git.ref_name(data[:ref])
self.class.post("#{teamcity_url}/httpAuth/app/rest/buildQueue", self.class.post(
body: "<build branchName=\"#{branch}\">"\ URI.join(teamcity_url, '/httpAuth/app/rest/buildQueue').to_s,
"<buildType id=\"#{build_type}\"/>"\ body: "<build branchName=\"#{branch}\">"\
'</build>', "<buildType id=\"#{build_type}\"/>"\
headers: { 'Content-type' => 'application/xml' }, '</build>',
basic_auth: auth headers: { 'Content-type' => 'application/xml' },
) basic_auth: auth
)
end end
end end
...@@ -169,7 +169,12 @@ class Repository ...@@ -169,7 +169,12 @@ class Repository
def rm_tag(tag_name) def rm_tag(tag_name)
before_remove_tag 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 end
def branch_names def branch_names
...@@ -797,7 +802,7 @@ class Repository ...@@ -797,7 +802,7 @@ class Repository
def search_files(query, ref) def search_files(query, ref)
offset = 2 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(/^--$/) Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
end end
......
...@@ -3,7 +3,7 @@ module Issues ...@@ -3,7 +3,7 @@ module Issues
def hook_data(issue, action) def hook_data(issue, action)
issue_data = issue.to_hook_data(current_user) 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[:object_attributes].merge!(url: issue_url, action: action)
issue_data issue_data
end end
......
...@@ -20,8 +20,7 @@ module MergeRequests ...@@ -20,8 +20,7 @@ module MergeRequests
def hook_data(merge_request, action) def hook_data(merge_request, action)
hook_data = merge_request.to_hook_data(current_user) 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] = Gitlab::UrlBuilder.build(merge_request)
hook_data[:object_attributes][:url] = merge_request_url
hook_data[:object_attributes][:action] = action hook_data[:object_attributes][:action] = action
hook_data hook_data
end end
......
...@@ -51,7 +51,7 @@ module MergeRequests ...@@ -51,7 +51,7 @@ module MergeRequests
# be interpreted as the use wants to close that issue on this project # be interpreted as the use wants to close that issue on this project
# Pattern example: 112-fix-mep-mep # Pattern example: 112-fix-mep-mep
# Will lead to appending `Closes #112` to the description # 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] iid = match[1]
closes_issue = "Closes ##{iid}" closes_issue = "Closes ##{iid}"
......
...@@ -26,7 +26,9 @@ module Projects ...@@ -26,7 +26,9 @@ module Projects
GitlabShellOneShotWorker.perform_async(:gc, @project.path_with_namespace) GitlabShellOneShotWorker.perform_async(:gc, @project.path_with_namespace)
ensure 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 end
def needed? def needed?
...@@ -34,14 +36,18 @@ module Projects ...@@ -34,14 +36,18 @@ module Projects
end end
def increment! def increment!
@project.increment!(:pushes_since_gc) Gitlab::Metrics.measure(:increment_pushes_since_gc) do
@project.increment!(:pushes_since_gc)
end
end end
private private
def try_obtain_lease def try_obtain_lease
lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT) Gitlab::Metrics.measure(:obtain_housekeeping_lease) do
lease.try_obtain lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT)
lease.try_obtain
end
end end
end end
end end
module Projects module Projects
class ParticipantsService < BaseService class ParticipantsService < BaseService
def execute(note_type, note_id) def execute(noteable_type, noteable_id)
participating = @noteable_type = noteable_type
if note_type && note_id @noteable_id = noteable_id
participants_in(note_type, note_id)
else
[]
end
project_members = sorted(project.team.members) 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 participants.uniq
end end
def participants_in(type, id) def target
target = @target ||=
case type case @noteable_type
when "Issue" when "Issue"
project.issues.find_by_iid(id) project.issues.find_by_iid(@noteable_id)
when "MergeRequest" when "MergeRequest"
project.merge_requests.find_by_iid(id) project.merge_requests.find_by_iid(@noteable_id)
when "Commit" when "Commit"
project.commit(id) project.commit(@noteable_id)
else
nil
end 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 return [] unless target
users = target.participants(current_user) users = target.participants(current_user)
...@@ -30,13 +39,13 @@ module Projects ...@@ -30,13 +39,13 @@ module Projects
end end
def sorted(users) 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 } { username: user.username, name: user.name }
end end
end end
def groups 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 count = group.users.count
{ username: group.path, name: group.name, count: count } { username: group.path, name: group.name, count: count }
end end
......
...@@ -34,8 +34,9 @@ module Projects ...@@ -34,8 +34,9 @@ module Projects
raise TransferError.new("Project with same path in target namespace already exists") raise TransferError.new("Project with same path in target namespace already exists")
end end
# Apply new namespace id # Apply new namespace id and visibility level
project.namespace = new_namespace project.namespace = new_namespace
project.visibility_level = new_namespace.visibility_level unless project.visibility_level_allowed_by_group?
project.save! project.save!
# Notifications # Notifications
...@@ -56,7 +57,7 @@ module Projects ...@@ -56,7 +57,7 @@ module Projects
Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path) Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path)
project.old_path_with_namespace = old_path project.old_path_with_namespace = old_path
SystemHooksService.new.execute_hooks_for(project, :transfer) SystemHooksService.new.execute_hooks_for(project, :transfer)
true true
end end
......
...@@ -222,7 +222,7 @@ class SystemNoteService ...@@ -222,7 +222,7 @@ class SystemNoteService
# Called when a branch is created from the 'new branch' button on a issue # Called when a branch is created from the 'new branch' button on a issue
# Example note text: # Example note text:
# #
# "Started branch `issue-branch-button-201`" # "Started branch `201-issue-branch-button`"
def self.new_issue_branch(issue, project, author, branch) def self.new_issue_branch(issue, project, author, branch)
h = Gitlab::Routing.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)
......
...@@ -271,5 +271,24 @@ ...@@ -271,5 +271,24 @@
.col-sm-10 .col-sm-10
= f.text_field :sentry_dsn, class: 'form-control' = 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 .form-actions
= f.submit 'Save', class: 'btn btn-save' = f.submit 'Save', class: 'btn btn-save'
- page_title "Logs" - page_title "Logs"
- loggers = [Gitlab::GitLogger, Gitlab::AppLogger, - loggers = [Gitlab::GitLogger, Gitlab::AppLogger,
Gitlab::ProductionLogger, Gitlab::SidekiqLogger] Gitlab::ProductionLogger, Gitlab::SidekiqLogger,
Gitlab::RepositoryCheckLogger]
%ul.nav-links.log-tabs %ul.nav-links.log-tabs
- loggers.each do |klass| - loggers.each do |klass|
%li{ class: (klass == Gitlab::GitLogger ? 'active' : '') } %li{ class: (klass == Gitlab::GitLogger ? 'active' : '') }
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
.row.prepend-top-default .row.prepend-top-default
%aside.col-md-3 %aside.col-md-3
.admin-filter .panel.admin-filter
= form_tag admin_namespaces_projects_path, method: :get, class: '' do = form_tag admin_namespaces_projects_path, method: :get, class: '' do
.form-group .form-group
= label_tag :name, 'Name:' = label_tag :name, 'Name:'
...@@ -38,7 +38,13 @@ ...@@ -38,7 +38,13 @@
%span.descr %span.descr
= visibility_level_icon(level) = visibility_level_icon(level)
= label = 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] = hidden_field_tag :sort, params[:sort]
= button_tag "Search", class: "btn submit btn-primary" = button_tag "Search", class: "btn submit btn-primary"
= link_to "Reset", admin_namespaces_projects_path, class: "btn btn-cancel" = link_to "Reset", admin_namespaces_projects_path, class: "btn btn-cancel"
......
...@@ -5,6 +5,16 @@ ...@@ -5,6 +5,16 @@
%i.fa.fa-pencil-square-o %i.fa.fa-pencil-square-o
Edit Edit
%hr %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 .row
.col-md-6 .col-md-6
.panel.panel-default .panel.panel-default
...@@ -95,6 +105,32 @@ ...@@ -95,6 +105,32 @@
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
= f.submit 'Transfer', class: 'btn btn-primary' = 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 .col-md-6
- if @group - if @group
.panel.panel-default .panel.panel-default
......
...@@ -68,7 +68,7 @@ ...@@ -68,7 +68,7 @@
%td= app.name %td= app.name
%td= token.created_at %td= token.created_at
%td= token.scopes %td= token.scopes
%td= render 'delete_form', application: app %td= render 'doorkeeper/authorized_applications/delete_form', application: app
- @authorized_anonymous_tokens.each do |token| - @authorized_anonymous_tokens.each do |token|
%tr %tr
%td %td
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
This will create milestone in every selected project This will create milestone in every selected project
%hr %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 .row
- if @milestone.errors.any? - if @milestone.errors.any?
#error_explanation #error_explanation
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
= f.label :description, "Description", class: "control-label" = f.label :description, "Description", class: "control-label"
.col-sm-10 .col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do = 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 .clearfix
.error-alert .error-alert
.form-group .form-group
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
.navbar-collapse.collapse .navbar-collapse.collapse
%ul.nav.navbar-nav %ul.nav.navbar-nav
%li.hidden-sm.hidden-xs %li.hidden-sm.hidden-xs
= render 'layouts/search' = render 'layouts/search' unless current_controller?(:search)
%li.visible-sm.visible-xs %li.visible-sm.visible-xs
= link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('search') = icon('search')
......
...@@ -52,6 +52,9 @@ ...@@ -52,6 +52,9 @@
%li %li
phpunit --coverage-text --colors=never (PHP) - phpunit --coverage-text --colors=never (PHP) -
%code ^\s*Lines:\s*\d+.\d+\% %code ^\s*Lines:\s*\d+.\d+\%
%li
gcovr (C/C++) -
%code ^TOTAL.*\s+(\d+\%)$
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
......
.zen-backdrop .zen-backdrop
- classes << ' js-gfm-input js-autosize markdown-area' - classes << ' js-gfm-input js-autosize markdown-area'
- if defined?(f) && f - 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 - 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: "#" } %a.zen-cotrol.zen-control-leave.js-zen-leave{ href: "#" }
= icon('compress') = icon('compress')
- if @lines.present? - if @lines.present?
- if @form.unfold? && @form.since != 1 && !@form.bottom? - if @form.unfold? && @form.since != 1 && !@form.bottom?
%tr.line_holder{ id: @form.since } %tr.line_holder{ id: @form.since }
= render "projects/diffs/match_line", {line: @match_line, = render "projects/diffs/match_line", { line: @match_line,
line_old: @form.since, line_new: @form.since, bottom: false, new_file: false} line_old: @form.since, line_new: @form.since, bottom: false, new_file: false }
- @lines.each_with_index do |line, index| - @lines.each_with_index do |line, index|
- line_new = index + @form.since - line_new = index + @form.since
- line_old = line_new - @form.offset - line_old = line_new - @form.offset
%tr.line_holder %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), "#" = 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) , "#" = link_to raw(line_new) , "#"
%td.line_content.noteable_line==#{' ' * @form.indent}#{line} %td.line_content.noteable_line==#{' ' * @form.indent}#{line}
- if @form.unfold? && @form.bottom? && @form.to < @blob.loc - if @form.unfold? && @form.bottom? && @form.to < @blob.loc
%tr.line_holder{ id: @form.to } %tr.line_holder{ id: @form.to }
= render "projects/diffs/match_line", {line: @match_line, = render "projects/diffs/match_line", { line: @match_line,
line_old: @form.to, line_new: @form.to, bottom: true, new_file: false} line_old: @form.to, line_new: @form.to, bottom: true, new_file: false }
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
= cache(cache_key) do = cache(cache_key) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" } %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
.commit-row-title .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" = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
- if commit.description? - if commit.description?
%a.text-expander.js-toggle-button ... %a.text-expander.js-toggle-button ...
......
- type = line.type - type = line.type
%tr.line_holder{id: line_code, class: type} %tr.line_holder{ id: line_code, class: type }
- case type - case type
- when 'match' - when 'match'
= render "projects/diffs/match_line", {line: line.text, = 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} line_old: line.old_pos, line_new: line.new_pos, bottom: false, new_file: diff_file.new_file }
- when 'nonewline' - when 'nonewline'
%td.old_line.diff-line-num %td.old_line.diff-line-num
%td.new_line.diff-line-num %td.new_line.diff-line-num
%td.line_content.match= line.text %td.line_content.match= line.text
- else - else
%td.old_line.diff-line-num{class: type} %td.old_line.diff-line-num{ class: type, data: { linenumber: line.new_pos } }
- link_text = raw(type == "new" ? "&nbsp;" : line.old_pos) - link_text = type == "new" ? "&nbsp;".html_safe : line.old_pos
- if defined?(plain) && plain - if defined?(plain) && plain
= link_text = link_text
- else - 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) - if @comments_allowed && can?(current_user, :create_note, @project)
= link_to_new_diff_note(line_code) = link_to_new_diff_note(line_code)
%td.new_line.diff-line-num{class: type, data: {linenumber: line.new_pos}} %td.new_line.diff-line-num{ class: type, data: { linenumber: line.new_pos } }
- link_text = raw(type == "old" ? "&nbsp;" : line.new_pos) - link_text = type == "old" ? "&nbsp;".html_safe : line.new_pos
- if defined?(plain) && plain - if defined?(plain) && plain
= link_text = link_text
- else - else
= link_to link_text, "##{line_code}", id: line_code = 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) %td.line_content{ class: ['noteable_line', type, line_code], data: { line_code: line_code } }= diff_line_content(line.text, type)
...@@ -14,11 +14,11 @@ ...@@ -14,11 +14,11 @@
%td.new_line.diff-line-num %td.new_line.diff-line-num
%td.line_content.parallel.match= left[:text] %td.line_content.parallel.match= left[:text]
- else - 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] = link_to raw(left[:number]), "##{left[:line_code]}", id: left[:line_code]
- if @comments_allowed && can?(current_user, :create_note, @project) - if @comments_allowed && can?(current_user, :create_note, @project)
= link_to_new_diff_note(left[:line_code], 'old') = 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' - if right[:type] == 'new'
- new_line_class = 'new' - new_line_class = 'new'
...@@ -27,11 +27,11 @@ ...@@ -27,11 +27,11 @@
- new_line_class = nil - new_line_class = nil
- new_line_code = left[:line_code] - 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 = link_to raw(right[:number]), "##{new_line_code}", id: new_line_code
- if @comments_allowed && can?(current_user, :create_note, @project) - if @comments_allowed && can?(current_user, :create_note, @project)
= link_to_new_diff_note(right[:line_code], 'new') = 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 - if @reply_allowed
- comments_left, comments_right = organize_comments(left[:type], right[:type], left[:line_code], right[:line_code]) - comments_left, comments_right = organize_comments(left[:type], right[:type], left[:line_code], right[:line_code])
......
...@@ -210,6 +210,7 @@ ...@@ -210,6 +210,7 @@
%li Be careful. Changing the project's namespace can have unintended side effects. %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 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 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 .form-actions
= f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) } = f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
- else - 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 = render 'shared/issuable/form', f: f, issuable: @issue
:javascript :javascript
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
= icon('thumbs-down') = icon('thumbs-down')
= downvotes = downvotes
- note_count = issue.notes.user.count - note_count = issue.notes.user.nonawards.count
- if note_count > 0 - if note_count > 0
%li %li
= link_to issue_path(issue) + "#notes" do = link_to issue_path(issue) + "#notes" do
......
...@@ -64,9 +64,11 @@ ...@@ -64,9 +64,11 @@
= @issue.description = @issue.description
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago') = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
.merge-requests #merge-requests{'data-url' => referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue)}
= render 'merge_requests' // This element is filled in using JavaScript.
= render 'related_branches'
#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 .content-block.content-block-small
= render 'new_branch' = render 'new_branch'
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
.label-subscription{data: {url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)}} .label-subscription{data: {url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)}}
.subscription-status{data: {status: label_subscription_status(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) %span= label_subscription_toggle_button_text(label)
- if can? current_user, :admin_label, @project - 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 = render 'shared/issuable/form', f: f, issuable: @merge_request
:javascript :javascript
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
= icon('thumbs-down') = icon('thumbs-down')
= downvotes = 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 - if note_count > 0
%li %li
= link_to merge_request_path(merge_request) + "#notes" do = link_to merge_request_path(merge_request) + "#notes" do
......
...@@ -16,11 +16,9 @@ ...@@ -16,11 +16,9 @@
= dropdown_title("Select source project") = dropdown_title("Select source project")
= dropdown_filter("Search projects") = dropdown_filter("Search projects")
= dropdown_content do = dropdown_content do
- is_active = f.object.source_project_id == @merge_request.source_project.id = render 'projects/merge_requests/dropdowns/project',
%ul projects: [@merge_request.source_project],
%li selected: f.object.source_project_id
%a{ href: "#", class: "#{("is-active" if is_active)}", data: { id: @merge_request.source_project.id } }
= @merge_request.source_project_path
.merge-request-select.dropdown .merge-request-select.dropdown
= f.hidden_field :source_branch = 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" } = 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 @@ ...@@ -28,11 +26,9 @@
= dropdown_title("Select source branch") = dropdown_title("Select source branch")
= dropdown_filter("Search branches") = dropdown_filter("Search branches")
= dropdown_content do = dropdown_content do
%ul = render 'projects/merge_requests/dropdowns/branch',
- @merge_request.source_branches.each do |branch| branches: @merge_request.source_branches,
%li selected: f.object.source_branch
%a{ href: "#", class: "#{("is-active" if f.object.source_branch == branch)}", data: { id: branch } }
= branch
.panel-footer .panel-footer
= icon('spinner spin', class: 'js-source-loading') = icon('spinner spin', class: 'js-source-loading')
%ul.list-unstyled.mr_source_commit %ul.list-unstyled.mr_source_commit
...@@ -50,11 +46,9 @@ ...@@ -50,11 +46,9 @@
= dropdown_title("Select target project") = dropdown_title("Select target project")
= dropdown_filter("Search projects") = dropdown_filter("Search projects")
= dropdown_content do = dropdown_content do
%ul = render 'projects/merge_requests/dropdowns/project',
- projects.each do |project| projects: projects,
%li selected: f.object.target_project_id
%a{ href: "#", class: "#{("is-active" if f.object.target_project_id == project.id)}", data: { id: project.id } }
= project.path_with_namespace
.merge-request-select.dropdown .merge-request-select.dropdown
= f.hidden_field :target_branch = 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" } = 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 @@ ...@@ -62,11 +56,9 @@
= dropdown_title("Select target branch") = dropdown_title("Select target branch")
= dropdown_filter("Search branches") = dropdown_filter("Search branches")
= dropdown_content do = dropdown_content do
%ul = render 'projects/merge_requests/dropdowns/branch',
- @merge_request.target_branches.each do |branch| branches: @merge_request.target_branches,
%li selected: f.object.target_branch
%a{ href: "#", class: "#{("is-active" if f.object.target_branch == branch)}", data: { id: branch } }
= branch
.panel-footer .panel-footer
= icon('spinner spin', class: "js-target-loading") = icon('spinner spin', class: "js-target-loading")
%ul.list-unstyled.mr_target_commit %ul.list-unstyled.mr_target_commit
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
%span.pull-right %span.pull-right
= link_to 'Change branches', mr_change_branches_path(@merge_request) = link_to 'Change branches', mr_change_branches_path(@merge_request)
%hr %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 = render 'shared/issuable/form', f: f, issuable: @merge_request
= f.hidden_field :source_project_id = f.hidden_field :source_project_id
= f.hidden_field :source_branch = f.hidden_field :source_branch
......
...@@ -32,8 +32,7 @@ ...@@ -32,8 +32,7 @@
%span Request to merge %span Request to merge
%span.label-branch= source_branch_with_namespace(@merge_request) %span.label-branch= source_branch_with_namespace(@merge_request)
%span into %span into
= link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do = link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"
= @merge_request.target_branch
- if @merge_request.open? && @merge_request.diverged_from_target_branch? - if @merge_request.open? && @merge_request.diverged_from_target_branch?
%span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind) %span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind)
...@@ -51,7 +50,7 @@ ...@@ -51,7 +50,7 @@
%li.notes-tab %li.notes-tab
= link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do
Discussion 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 %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 = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
Commits 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 = render 'projects/merge_requests/dropdowns/branch',
- @target_branches.each do |branch| branches: @target_branches,
%li selected: nil
%a{ href: "#", class: "#{("is-active" if "a" == branch)}", data: { id: branch } }
= branch
= 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) = form_errors(@milestone)
.row .row
.col-md-6 .col-md-6
.form-group .form-group
...@@ -11,7 +10,7 @@ ...@@ -11,7 +10,7 @@
= f.label :description, "Description", class: "control-label" = f.label :description, "Description", class: "control-label"
.col-sm-10 .col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do = 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' = render 'projects/notes/hints'
.clearfix .clearfix
.error-alert .error-alert
......
...@@ -2,7 +2,7 @@ ...@@ -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| = 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) = note_target_fields(note)
= render layout: 'projects/md_preview', locals: { preview_class: 'md-preview' } do = 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' = render 'projects/notes/hints'
.note-form-actions.clearfix .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 :view, diff_view
= hidden_field_tag :line_type = hidden_field_tag :line_type
= note_target_fields(@note) = note_target_fields(@note)
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
= f.hidden_field :noteable_type = f.hidden_field :noteable_type
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do = 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' = render 'projects/notes/hints'
.error-alert .error-alert
......
...@@ -20,11 +20,9 @@ ...@@ -20,11 +20,9 @@
%td.new_line.diff-line-num= "..." %td.new_line.diff-line-num= "..."
%td.line_content.match= line.text %td.line_content.match= line.text
- else - else
%td.old_line.diff-line-num %td.old_line.diff-line-num{ data: { linenumber: type == "new" ? "&nbsp;".html_safe : line.old_pos } }
= raw(type == "new" ? "&nbsp;" : line.old_pos) %td.new_line.diff-line-num{ data: { linenumber: type == "old" ? "&nbsp;".html_safe : line.new_pos } }
%td.new_line.diff-line-num %td.line_content{ class: ['noteable_line', type, line_code], line_code: line_code }= diff_line_content(line.text, type)
= 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)
- if line_code == note.line_code - if line_code == note.line_code
= render "projects/notes/diff_notes_with_reply", notes: discussion_notes = render "projects/notes/diff_notes_with_reply", notes: discussion_notes
...@@ -9,11 +9,11 @@ ...@@ -9,11 +9,11 @@
%strong #{@tag.name} %strong #{@tag.name}
.prepend-top-default .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 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' = render 'projects/notes/hints'
.error-alert .error-alert
.form-actions.prepend-top-default .form-actions.prepend-top-default
= f.submit 'Save changes', class: 'btn btn-save' = 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" = link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel"
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
New Tag New Tag
%hr %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 .form-group
= label_tag :tag_name, nil, class: 'control-label' = label_tag :tag_name, nil, class: 'control-label'
.col-sm-10 .col-sm-10
...@@ -30,9 +30,9 @@ ...@@ -30,9 +30,9 @@
= label_tag :release_description, 'Release notes', class: 'control-label' = label_tag :release_description, 'Release notes', class: 'control-label'
.col-sm-10 .col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do = 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' = 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 .form-actions
= button_tag 'Create tag', class: 'btn btn-create', tabindex: 3 = button_tag 'Create tag', class: 'btn btn-create', tabindex: 3
= link_to 'Cancel', namespace_project_tags_path(@project.namespace, @project), class: 'btn btn-cancel' = 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) = form_errors(@page)
= f.hidden_field :title, value: @page.title = f.hidden_field :title, value: @page.title
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
= f.label :content, class: 'control-label' = f.label :content, class: 'control-label'
.col-sm-10 .col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do = 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' = render 'projects/notes/hints'
.clearfix .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 - 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 - 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
......
...@@ -29,7 +29,8 @@ ...@@ -29,7 +29,8 @@
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
= render 'projects/zen', f: f, attr: :description, = 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' = render 'projects/notes/hints'
.clearfix .clearfix
.error-alert .error-alert
...@@ -70,13 +71,13 @@ ...@@ -70,13 +71,13 @@
- if can? current_user, :admin_milestone, issuable.project - 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 = link_to 'Create new milestone', new_namespace_project_milestone_path(issuable.project.namespace, issuable.project), target: :blank
.form-group .form-group
- has_labels = issuable.project.labels.any?
= f.label :label_ids, "Labels", class: 'control-label' = f.label :label_ids, "Labels", class: 'control-label'
.col-sm-10 .col-sm-10{ class: ('issuable-form-padding-top' if !has_labels) }
- if issuable.project.labels.any? - if has_labels
= f.collection_select :label_ids, issuable.project.labels.all, :id, :name, = f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
{ selected: issuable.label_ids }, multiple: true, class: 'select2', data: { placeholder: "Select labels" } { selected: issuable.label_ids }, multiple: true, class: 'select2', data: { placeholder: "Select labels" }
- else - else
.prepend-top-10
%span.light No labels yet. %span.light No labels yet.
&nbsp; &nbsp;
- if can? current_user, :admin_label, issuable.project - if can? current_user, :admin_label, issuable.project
...@@ -128,8 +129,6 @@ ...@@ -128,8 +129,6 @@
- else - else
.pull-right .pull-right
- if current_user.can?(:"destroy_#{issuable.to_ability_name}", @project) - 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?" }, = 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-grouped' do method: :delete, class: 'btn btn-danger btn-grouped'
= icon('trash-o')
Delete
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel' = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel'
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
.selectbox.hide-collapsed .selectbox.hide-collapsed
= f.hidden_field 'assignee_id', value: issuable.assignee_id, id: 'issue_assignee_id' = 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 .block.milestone
.sidebar-collapsed-icon .sidebar-collapsed-icon
...@@ -128,7 +128,7 @@ ...@@ -128,7 +128,7 @@
.title.hide-collapsed .title.hide-collapsed
Notifications Notifications
- subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed' - 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' %span= subscribed ? 'Unsubscribe' : 'Subscribe'
.subscription-status.hide-collapsed{data: {status: subscribtion_status}} .subscription-status.hide-collapsed{data: {status: subscribtion_status}}
.unsubscribed{class: ( 'hidden' if subscribed )} .unsubscribed{class: ( 'hidden' if subscribed )}
...@@ -152,4 +152,4 @@ ...@@ -152,4 +152,4 @@
new LabelsSelect(); new LabelsSelect();
new IssuableContext('#{current_user.to_json(only: [:username, :id, :name])}'); new IssuableContext('#{current_user.to_json(only: [:username, :id, :name])}');
new Subscription('.subscription') new Subscription('.subscription')
new Sidebar(); new Sidebar();
\ No newline at end of file
...@@ -4,15 +4,16 @@ ...@@ -4,15 +4,16 @@
%li %li
%span.label-row %span.label-row
= link_to milestones_label_path(options) do %span.label-name
- render_colored_label(label, tooltip: false) = link_to milestones_label_path(options) do
%span.prepend-left-10 - render_colored_label(label, tooltip: false)
%span.prepend-description-left
= markdown(label.description, pipeline: :single_line) = markdown(label.description, pipeline: :single_line)
.pull-right .pull-info-right
%strong.issues-count %span.append-right-20
= link_to milestones_label_path(options.merge(state: 'opened')) do = link_to milestones_label_path(options.merge(state: 'opened')) do
- pluralize milestone_issues_by_label_count(@milestone, label, state: :opened), 'open issue' - 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 = link_to milestones_label_path(options.merge(state: 'closed')) do
- pluralize milestone_issues_by_label_count(@milestone, label, state: :closed), 'closed issue' - 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 ...@@ -40,7 +40,7 @@ class PostReceive
if Gitlab::Git.tag_ref?(ref) if Gitlab::Git.tag_ref?(ref)
GitTagPushService.new.execute(post_received.project, @user, oldrev, newrev, 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 GitPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute
end end
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