mirror of
https://github.com/wahyd4/gitlabhq.git
synced 2026-08-20 10:06:04 +10:00
Merge branch 'master' into 8-7-stable
In doing this I'm breaking my own rules of not merging master into a stable branch during the feature freeze. I'm breaking this rule for two reasons: 1. Somewhere down the line I messed up picking commits into the stable branch and as a result I basically can't merge anything anymore without resolving 9000 merge conflicts. 2. All merge requests merged into master had the "Pick into Stable" label anyway except for one commit that bumps the version. As such merging master into this branch is not really different from picking things manually.
This commit is contained in:
@@ -1,26 +1,35 @@
|
||||
Please view this file on the master branch, on stable branches it's out of date.
|
||||
|
||||
v 8.7.0 (unreleased)
|
||||
- The Projects::HousekeepingService class has extra instrumentation (Yorick Peterse)
|
||||
- Fix revoking of authorized OAuth applications (Connor Shea)
|
||||
- All service classes (those residing in app/services) are now instrumented (Yorick Peterse)
|
||||
- Developers can now add custom tags to transactions (Yorick Peterse)
|
||||
- Loading of an issue's referenced merge requests and related branches is now done asynchronously (Yorick Peterse)
|
||||
- The number of InfluxDB points stored per UDP packet can now be configured
|
||||
- Transactions for /internal/allowed now have an "action" tag set
|
||||
- Method instrumentation now uses Module#prepend instead of aliasing methods
|
||||
- Repository.clean_old_archives is now instrumented
|
||||
- Add support for environment variables on a job level in CI configuration file
|
||||
- SQL query counts are now tracked per transaction
|
||||
- The Projects::HousekeepingService class has extra instrumentation
|
||||
- All service classes (those residing in app/services) are now instrumented
|
||||
- Developers can now add custom tags to transactions
|
||||
- Loading of an issue's referenced merge requests and related branches is now done asynchronously
|
||||
- Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea)
|
||||
- Project switcher uses new dropdown styling
|
||||
- Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea)
|
||||
- Do not include award_emojis in issue and merge_request comment_count !3610 (Lucas Charles)
|
||||
- Restrict user profiles when public visibility level is restricted.
|
||||
- All images in discussions and wikis now link to their source files !3464 (Connor Shea).
|
||||
- Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu)
|
||||
- Add setting for customizing the list of trusted proxies !3524
|
||||
- Allow projects to be transfered to a lower visibility level group
|
||||
- Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524
|
||||
- Improved Markdown rendering performance !3389 (Yorick Peterse)
|
||||
- Improved Markdown rendering performance !3389
|
||||
- Make shared runners text in box configurable
|
||||
- Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu)
|
||||
- API: Ability to subscribe and unsubscribe from issues and merge requests (Robert Schilling)
|
||||
- Expose project badges in project settings
|
||||
- Make /profile/keys/new redirect to /profile/keys for back-compat. !3717
|
||||
- Preserve time notes/comments have been updated at when moving issue
|
||||
- Make HTTP(s) label consistent on clone bar (Stan Hu)
|
||||
- Add support for `after_script`, requires Runner 1.2 (Kamil Trzciński)
|
||||
- Expose label description in API (Mariusz Jachimowicz)
|
||||
- API: Ability to update a group (Robert Schilling)
|
||||
- API: Ability to move issues (Robert Schilling)
|
||||
@@ -42,18 +51,22 @@ v 8.7.0 (unreleased)
|
||||
- Add default scope to projects to exclude projects pending deletion
|
||||
- Allow to close merge requests which source projects(forks) are deleted.
|
||||
- Ensure empty recipients are rejected in BuildsEmailService
|
||||
- Use rugged to change HEAD in Project#change_head (P.S.V.R)
|
||||
- API: Ability to filter milestones by state `active` and `closed` (Robert Schilling)
|
||||
- API: Fix milestone filtering by `iid` (Robert Schilling)
|
||||
- Make before_script and after_script overridable on per-job (Kamil Trzciński)
|
||||
- API: Delete notes of issues, snippets, and merge requests (Robert Schilling)
|
||||
- Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.)
|
||||
- Better errors handling when creating milestones inside groups
|
||||
- Fix high CPU usage when PostReceive receives refs/merge-requests/<id>
|
||||
- Hide `Create a group` help block when creating a new project in a group
|
||||
- Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.)
|
||||
- Allow issues and merge requests to be assigned to the author !2765
|
||||
- Gracefully handle notes on deleted commits in merge requests (Stan Hu)
|
||||
- Decouple membership and notifications
|
||||
- Fix creation of merge requests for orphaned branches (Stan Hu)
|
||||
- API: Ability to retrieve a single tag (Robert Schilling)
|
||||
- While signing up, don't persist the user password across form redisplays
|
||||
- Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla)
|
||||
- Remove "Congratulations!" tweet button on newly-created project. (Connor Shea)
|
||||
- Fix admin/projects when using visibility levels on search (PotHix)
|
||||
@@ -62,18 +75,39 @@ v 8.7.0 (unreleased)
|
||||
- API: Do not leak group existence via return code (Robert Schilling)
|
||||
- ClosingIssueExtractor regex now also works with colons. e.g. "Fixes: #1234" !3591
|
||||
- Update number of Todos in the sidebar when it's marked as "Done". !3600
|
||||
- Sanitize branch names created for confidential issues
|
||||
- API: Expose 'updated_at' for issue, snippet, and merge request notes (Robert Schilling)
|
||||
- API: User can leave a project through the API when not master or owner. !3613
|
||||
- Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu)
|
||||
- Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld)
|
||||
- Improved markdown forms
|
||||
- Delete tags using Rugged for performance reasons (Robert Schilling)
|
||||
- Diffs load at the correct point when linking from from number
|
||||
- Selected diff rows highlight
|
||||
- Fix emoji categories in the emoji picker
|
||||
- API: Properly display annotated tags for GET /projects/:id/repository/tags (Robert Schilling)
|
||||
- Add encrypted credentials for imported projects and migrate old ones
|
||||
- Author and participants are displayed first on users autocompletion
|
||||
- Show number sign on external issue reference text (Florent Baldino)
|
||||
- Updated print style for issues
|
||||
- Use GitHub Issue/PR number as iid to keep references
|
||||
- Import GitHub labels
|
||||
- Import GitHub milestones
|
||||
- Fix emoji catgories in the emoji picker
|
||||
- Execute system web hooks on push to the project
|
||||
- Allow enable/disable push events for system hooks
|
||||
|
||||
v 8.6.7 (unreleased)
|
||||
- Fix vulnerability that made it possible to enumerate private projects belonging to group
|
||||
|
||||
v 8.6.6
|
||||
- Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413
|
||||
- Fix error on language detection when repository has no HEAD (e.g., master branch) (Jeroen Bobbeldijk). !3654
|
||||
- Fix revoking of authorized OAuth applications (Connor Shea). !3690
|
||||
- Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk)
|
||||
- Project switcher uses new dropdown styling
|
||||
- Issuable header is consistent between issues and merge requests
|
||||
- Improved spacing in issuable header on mobile
|
||||
|
||||
v 8.6.5
|
||||
- Fix importing from GitHub Enterprise. !3529
|
||||
@@ -273,7 +307,7 @@ v 8.5.1
|
||||
|
||||
v 8.5.0
|
||||
- Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu)
|
||||
- Cache various Repository methods to improve performance (Yorick Peterse)
|
||||
- Cache various Repository methods to improve performance
|
||||
- Fix duplicated branch creation/deletion Webhooks/service notifications when using Web UI (Stan Hu)
|
||||
- Ensure rake tasks that don't need a DB connection can be run without one
|
||||
- Update New Relic gem to 3.14.1.311 (Stan Hu)
|
||||
|
||||
@@ -315,7 +315,7 @@ end
|
||||
|
||||
gem "newrelic_rpm", '~> 3.14'
|
||||
|
||||
gem 'octokit', '~> 3.8.0'
|
||||
gem 'octokit', '~> 4.3.0'
|
||||
|
||||
gem "mail_room", "~> 0.6.1"
|
||||
|
||||
|
||||
+5
-5
@@ -485,8 +485,8 @@ GEM
|
||||
multi_json (~> 1.3)
|
||||
multi_xml (~> 0.5)
|
||||
rack (~> 1.2)
|
||||
octokit (3.8.0)
|
||||
sawyer (~> 0.6.0, >= 0.5.3)
|
||||
octokit (4.3.0)
|
||||
sawyer (~> 0.7.0, >= 0.5.3)
|
||||
omniauth (1.3.1)
|
||||
hashie (>= 1.2, < 4)
|
||||
rack (>= 1.0, < 3)
|
||||
@@ -712,8 +712,8 @@ GEM
|
||||
sprockets (>= 2.8, < 4.0)
|
||||
sprockets-rails (>= 2.0, < 4.0)
|
||||
tilt (>= 1.1, < 3)
|
||||
sawyer (0.6.0)
|
||||
addressable (~> 2.3.5)
|
||||
sawyer (0.7.0)
|
||||
addressable (>= 2.3.5, < 2.5)
|
||||
faraday (~> 0.8, < 0.10)
|
||||
scss_lint (0.47.1)
|
||||
rake (>= 0.9, < 11)
|
||||
@@ -968,7 +968,7 @@ DEPENDENCIES
|
||||
newrelic_rpm (~> 3.14)
|
||||
nokogiri (~> 1.6.7, >= 1.6.7.2)
|
||||
oauth2 (~> 1.0.0)
|
||||
octokit (~> 3.8.0)
|
||||
octokit (~> 4.3.0)
|
||||
omniauth (~> 1.3.1)
|
||||
omniauth-auth0 (~> 1.4.1)
|
||||
omniauth-azure-oauth2 (~> 0.0.6)
|
||||
|
||||
@@ -174,7 +174,7 @@ $ ->
|
||||
$('.trigger-submit').on 'change', ->
|
||||
$(@).parents('form').submit()
|
||||
|
||||
gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), false)
|
||||
gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true)
|
||||
|
||||
# Flash
|
||||
if (flash = $(".flash-container")).length > 0
|
||||
|
||||
@@ -61,6 +61,7 @@ class @DropzoneInput
|
||||
return
|
||||
|
||||
drop: ->
|
||||
$mdArea.removeClass 'is-dropzone-hover'
|
||||
form.find(".div-dropzone-hover").css "opacity", 0
|
||||
form_textarea.focus()
|
||||
return
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
window.GitLab ?= {}
|
||||
GitLab.GfmAutoComplete =
|
||||
dataLoading: false
|
||||
|
||||
dataSource: ''
|
||||
|
||||
# Emoji
|
||||
@@ -17,17 +19,41 @@ GitLab.GfmAutoComplete =
|
||||
template: '<li><small>${id}</small> ${title}</li>'
|
||||
|
||||
# Add GFM auto-completion to all input fields, that accept GFM input.
|
||||
setup: ->
|
||||
input = $('.js-gfm-input')
|
||||
setup: (wrap) ->
|
||||
@input = $('.js-gfm-input')
|
||||
|
||||
# destroy previous instances
|
||||
@destroyAtWho()
|
||||
|
||||
# set up instances
|
||||
@setupAtWho()
|
||||
|
||||
if @dataSource
|
||||
if !@dataLoading
|
||||
@dataLoading = true
|
||||
|
||||
# We should wait until initializations are done
|
||||
# and only trigger the last .setup since
|
||||
# The previous .dataSource belongs to the previous issuable
|
||||
# and the last one will have the **proper** .dataSource property
|
||||
# TODO: Make this a singleton and turn off events when moving to another page
|
||||
setTimeout( =>
|
||||
fetch = @fetchData(@dataSource)
|
||||
fetch.done (data) =>
|
||||
@dataLoading = false
|
||||
@loadData(data)
|
||||
, 1000)
|
||||
|
||||
|
||||
setupAtWho: ->
|
||||
# Emoji
|
||||
input.atwho
|
||||
@input.atwho
|
||||
at: ':'
|
||||
displayTpl: @Emoji.template
|
||||
insertTpl: ':${name}:'
|
||||
|
||||
# Team Members
|
||||
input.atwho
|
||||
@input.atwho
|
||||
at: '@'
|
||||
displayTpl: @Members.template
|
||||
insertTpl: '${atwho-at}${username}'
|
||||
@@ -42,7 +68,7 @@ GitLab.GfmAutoComplete =
|
||||
title: sanitize(title)
|
||||
search: sanitize("#{m.username} #{m.name}")
|
||||
|
||||
input.atwho
|
||||
@input.atwho
|
||||
at: '#'
|
||||
alias: 'issues'
|
||||
searchKey: 'search'
|
||||
@@ -55,7 +81,7 @@ GitLab.GfmAutoComplete =
|
||||
title: sanitize(i.title)
|
||||
search: "#{i.iid} #{i.title}"
|
||||
|
||||
input.atwho
|
||||
@input.atwho
|
||||
at: '!'
|
||||
alias: 'mergerequests'
|
||||
searchKey: 'search'
|
||||
@@ -68,13 +94,18 @@ GitLab.GfmAutoComplete =
|
||||
title: sanitize(m.title)
|
||||
search: "#{m.iid} #{m.title}"
|
||||
|
||||
if @dataSource
|
||||
$.getJSON(@dataSource).done (data) ->
|
||||
# load members
|
||||
input.atwho 'load', '@', data.members
|
||||
# load issues
|
||||
input.atwho 'load', 'issues', data.issues
|
||||
# load merge requests
|
||||
input.atwho 'load', 'mergerequests', data.mergerequests
|
||||
# load emojis
|
||||
input.atwho 'load', ':', data.emojis
|
||||
destroyAtWho: ->
|
||||
@input.atwho('destroy')
|
||||
|
||||
fetchData: (dataSource) ->
|
||||
$.getJSON(dataSource)
|
||||
|
||||
loadData: (data) ->
|
||||
# load members
|
||||
@input.atwho 'load', '@', data.members
|
||||
# load issues
|
||||
@input.atwho 'load', 'issues', data.issues
|
||||
# load merge requests
|
||||
@input.atwho 'load', 'mergerequests', data.mergerequests
|
||||
# load emojis
|
||||
@input.atwho 'load', ':', data.emojis
|
||||
|
||||
@@ -32,10 +32,8 @@ class GitLabDropdownFilter
|
||||
else if @input.val() is "" and $inputContainer.hasClass HAS_VALUE_CLASS
|
||||
$inputContainer.removeClass HAS_VALUE_CLASS
|
||||
|
||||
if keyCode is 13 and @input.val() isnt ""
|
||||
if @options.enterCallback
|
||||
@options.enterCallback()
|
||||
return
|
||||
if keyCode is 13
|
||||
return false
|
||||
|
||||
clearTimeout timeout
|
||||
timeout = setTimeout =>
|
||||
@@ -132,7 +130,6 @@ class GitLabDropdown
|
||||
@filterInput = @getElement(FILTER_INPUT)
|
||||
@highlight = false
|
||||
@filterInputBlur = true
|
||||
@enterCallback = true
|
||||
} = @options
|
||||
|
||||
self = @
|
||||
@@ -157,6 +154,9 @@ class GitLabDropdown
|
||||
@fullData = data
|
||||
|
||||
@parseData @fullData
|
||||
|
||||
if @options.filterable
|
||||
@filterInput.trigger 'keyup'
|
||||
}
|
||||
|
||||
# Init filterable
|
||||
@@ -178,9 +178,6 @@ class GitLabDropdown
|
||||
callback: (data) =>
|
||||
currentIndex = -1
|
||||
@parseData data
|
||||
enterCallback: =>
|
||||
if @enterCallback
|
||||
@selectRowAtIndex 0
|
||||
|
||||
# Event listeners
|
||||
|
||||
|
||||
@@ -4,18 +4,33 @@ class @ImporterStatus
|
||||
this.setAutoUpdate()
|
||||
|
||||
initStatusPage: ->
|
||||
$(".js-add-to-import").click (event) =>
|
||||
new_namespace = null
|
||||
tr = $(event.currentTarget).closest("tr")
|
||||
id = tr.attr("id").replace("repo_", "")
|
||||
if tr.find(".import-target input").length > 0
|
||||
new_namespace = tr.find(".import-target input").prop("value")
|
||||
tr.find(".import-target").empty().append(new_namespace + "/" + tr.find(".import-target").data("project_name"))
|
||||
$.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script'
|
||||
$('.js-add-to-import')
|
||||
.off 'click'
|
||||
.on 'click', (e) =>
|
||||
new_namespace = null
|
||||
$btn = $(e.currentTarget)
|
||||
$tr = $btn.closest('tr')
|
||||
id = $tr.attr('id').replace('repo_', '')
|
||||
if $tr.find('.import-target input').length > 0
|
||||
new_namespace = $tr.find('.import-target input').prop('value')
|
||||
$tr.find('.import-target').empty().append("#{new_namespace} / #{$tr.find('.import-target').data('project_name')}")
|
||||
|
||||
$(".js-import-all").click (event) =>
|
||||
$(".js-add-to-import").each ->
|
||||
$(this).click()
|
||||
$btn
|
||||
.disable()
|
||||
.addClass 'is-loading'
|
||||
|
||||
$.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script'
|
||||
|
||||
$('.js-import-all')
|
||||
.off 'click'
|
||||
.on 'click', (e) ->
|
||||
$btn = $(@)
|
||||
$btn
|
||||
.disable()
|
||||
.addClass 'is-loading'
|
||||
|
||||
$('.js-add-to-import').each ->
|
||||
$(this).trigger('click')
|
||||
|
||||
setAutoUpdate: ->
|
||||
setInterval (=>
|
||||
|
||||
@@ -183,9 +183,10 @@ class @MergeRequestTabs
|
||||
else
|
||||
$diffLine = $('td', $diffLine)
|
||||
|
||||
$diffLine.addClass 'hll'
|
||||
diffLineTop = $diffLine.offset().top
|
||||
navBarHeight = $('.navbar-gitlab').outerHeight()
|
||||
if $diffLine.length
|
||||
$diffLine.addClass 'hll'
|
||||
diffLineTop = $diffLine.offset().top
|
||||
navBarHeight = $('.navbar-gitlab').outerHeight()
|
||||
|
||||
loadBuilds: (source) ->
|
||||
return if @buildsLoaded
|
||||
|
||||
@@ -75,6 +75,9 @@ class @Notes
|
||||
# when issue status changes, we need to refresh data
|
||||
$(document).on "issuable:change", @refresh
|
||||
|
||||
# when a key is clicked on the notes
|
||||
$(document).on "keydown", ".js-note-text", @keydownNoteText
|
||||
|
||||
cleanBinding: ->
|
||||
$(document).off "ajax:success", ".js-main-target-form"
|
||||
$(document).off "ajax:success", ".js-discussion-note-form"
|
||||
@@ -92,10 +95,19 @@ class @Notes
|
||||
$(document).off "click", ".js-note-target-reopen"
|
||||
$(document).off "click", ".js-note-target-close"
|
||||
$(document).off "click", ".js-note-discard"
|
||||
$(document).off "keydown", ".js-note-text"
|
||||
|
||||
$('.note .js-task-list-container').taskList('disable')
|
||||
$(document).off 'tasklist:changed', '.note .js-task-list-container'
|
||||
|
||||
keydownNoteText: (e) ->
|
||||
$this = $(this)
|
||||
if $this.val() is '' and e.which is 38 #aka the up key
|
||||
myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last")
|
||||
if myLastNote.length
|
||||
myLastNoteEditBtn = myLastNote.find('.js-note-edit')
|
||||
myLastNoteEditBtn.trigger('click', [true, myLastNote])
|
||||
|
||||
initRefresh: ->
|
||||
clearInterval(Notes.interval)
|
||||
Notes.interval = setInterval =>
|
||||
@@ -343,7 +355,7 @@ class @Notes
|
||||
Adds a hidden div with the original content of the note to fill the edit note form with
|
||||
if the user cancels
|
||||
###
|
||||
showEditForm: (e) ->
|
||||
showEditForm: (e, scrollTo, myLastNote) ->
|
||||
e.preventDefault()
|
||||
note = $(this).closest(".note")
|
||||
note.addClass "is-editting"
|
||||
@@ -354,9 +366,27 @@ class @Notes
|
||||
# Show the attachment delete link
|
||||
note.find(".js-note-attachment-delete").show()
|
||||
|
||||
new GLForm form
|
||||
done = ($noteText) ->
|
||||
# Neat little trick to put the cursor at the end
|
||||
noteTextVal = $noteText.val()
|
||||
$noteText.val('').val(noteTextVal);
|
||||
|
||||
form.find(".js-note-text").focus()
|
||||
new GLForm form
|
||||
if scrollTo? and myLastNote?
|
||||
# scroll to the bottom
|
||||
# so the open of the last element doesn't make a jump
|
||||
$('html, body').scrollTop($(document).height());
|
||||
$('html, body').animate({
|
||||
scrollTop: myLastNote.offset().top - 150
|
||||
}, 500, ->
|
||||
$noteText = form.find(".js-note-text")
|
||||
$noteText.focus()
|
||||
done($noteText)
|
||||
);
|
||||
else
|
||||
$noteText = form.find('.js-note-text')
|
||||
$noteText.focus()
|
||||
done($noteText)
|
||||
|
||||
###
|
||||
Called in response to clicking the edit note link
|
||||
|
||||
@@ -45,9 +45,10 @@ class @Profile
|
||||
|
||||
saveForm: ->
|
||||
self = @
|
||||
|
||||
formData = new FormData(@form[0])
|
||||
formData.append('user[avatar]', @avatarGlCrop.getBlob(), 'avatar.png')
|
||||
|
||||
avatarBlob = @avatarGlCrop.getBlob()
|
||||
formData.append('user[avatar]', avatarBlob, 'avatar.png') if avatarBlob?
|
||||
|
||||
$.ajax
|
||||
url: @form.attr('action')
|
||||
|
||||
@@ -12,6 +12,7 @@ class @UsersSelect
|
||||
showNullUser = $dropdown.data('null-user')
|
||||
showAnyUser = $dropdown.data('any-user')
|
||||
firstUser = $dropdown.data('first-user')
|
||||
@authorId = $dropdown.data('author-id')
|
||||
selectedId = $dropdown.data('selected')
|
||||
defaultLabel = $dropdown.data('default-label')
|
||||
issueURL = $dropdown.data('issueUpdate')
|
||||
@@ -207,6 +208,7 @@ class @UsersSelect
|
||||
@projectId = $(select).data('project-id')
|
||||
@groupId = $(select).data('group-id')
|
||||
@showCurrentUser = $(select).data('current-user')
|
||||
@authorId = $(select).data('author-id')
|
||||
showNullUser = $(select).data('null-user')
|
||||
showAnyUser = $(select).data('any-user')
|
||||
showEmailUser = $(select).data('email-user')
|
||||
@@ -312,6 +314,7 @@ class @UsersSelect
|
||||
project_id: @projectId
|
||||
group_id: @groupId
|
||||
current_user: @showCurrentUser
|
||||
author_id: @authorId
|
||||
dataType: "json"
|
||||
).done (users) ->
|
||||
callback(users)
|
||||
|
||||
@@ -71,7 +71,7 @@ header {
|
||||
.header-content {
|
||||
position: relative;
|
||||
height: $header-height;
|
||||
padding-right: 20px;
|
||||
padding-right: 40px;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
padding-right: 0;
|
||||
@@ -122,6 +122,10 @@ header {
|
||||
}
|
||||
}
|
||||
|
||||
.project-item-select-holder {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.impersonation i {
|
||||
color: $red-normal;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
.status-box {
|
||||
|
||||
|
||||
/* Extra small devices (phones, less than 768px) */
|
||||
/* No media query since this is the default in Bootstrap */
|
||||
padding: 5px 11px;
|
||||
|
||||
@@ -70,13 +70,6 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.issue-details {
|
||||
.creator,
|
||||
.page-title .btn-close {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
%ul.notes .note-role, .note-actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -39,8 +39,7 @@
|
||||
.diff-file {
|
||||
border: 1px solid $border-color;
|
||||
border-bottom: none;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,11 @@ li.commit {
|
||||
}
|
||||
}
|
||||
|
||||
.item-title {
|
||||
display: inline-block;
|
||||
max-width: 70%;
|
||||
}
|
||||
|
||||
.commit-row-description {
|
||||
font-size: 14px;
|
||||
border-left: 1px solid #eee;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.detail-page-header {
|
||||
padding: 11px 0;
|
||||
padding: $gl-padding-top 0;
|
||||
border-bottom: 1px solid $border-color;
|
||||
color: #5c5d5e;
|
||||
font-size: 16px;
|
||||
@@ -16,11 +16,6 @@
|
||||
.issue_created_ago, .author_link {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.issue-meta {
|
||||
display: inline-block;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-page-description {
|
||||
|
||||
+21
@@ -16,3 +16,24 @@ i.icon-gitorious-big {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.import-jobs-from-col,
|
||||
.import-jobs-to-col {
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
.import-jobs-status-col {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.btn-import {
|
||||
.loading-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.is-loading {
|
||||
.loading-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,10 +273,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.btn-default.gutter-toggle {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.detail-page-description {
|
||||
small {
|
||||
color: $gray-darkest;
|
||||
@@ -322,3 +318,50 @@
|
||||
padding-top: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
.issuable-status-box {
|
||||
float: none;
|
||||
display: inline-block;
|
||||
margin-top: 0;
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.issuable-header {
|
||||
position: relative;
|
||||
padding-left: 45px;
|
||||
padding-right: 45px;
|
||||
line-height: 35px;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
float: left;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.issuable-actions {
|
||||
padding-top: 10px;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
float: right;
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.issuable-gutter-toggle {
|
||||
@media (max-width: $screen-sm-max) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.issuable-meta {
|
||||
display: inline-block;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
@@ -86,41 +86,9 @@ form.edit-issue {
|
||||
@media (max-width: $screen-xs-max) {
|
||||
.issue-btn-group {
|
||||
width: 100%;
|
||||
margin-top: 5px;
|
||||
|
||||
.btn-group {
|
||||
width: 100%;
|
||||
|
||||
ul {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
|
||||
&:first-child:not(:last-child) {
|
||||
|
||||
}
|
||||
|
||||
&:not(:first-child):not(:last-child) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
&:last-child:not(:first-child) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.issue {
|
||||
&:hover .issue-actions {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.issue-updated-at {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,11 +101,3 @@ form.edit-issue {
|
||||
color: $gl-text-color;
|
||||
margin-left: 52px;
|
||||
}
|
||||
|
||||
.editor-details {
|
||||
display: block;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,11 +61,11 @@
|
||||
padding: $gl-padding-top $gl-padding;
|
||||
border: 1px solid $note-form-border-color;
|
||||
border-radius: $border-radius-base;
|
||||
transition: border-color ease-in-out 0.15s,
|
||||
box-shadow ease-in-out 0.15s;
|
||||
|
||||
&.is-focused {
|
||||
border-color: $focus-border-color;
|
||||
box-shadow: 0 0 2px $black-transparent,
|
||||
0 0 4px rgba($focus-border-color, .4);
|
||||
@extend .form-control:focus;
|
||||
|
||||
.comment-toolbar,
|
||||
.nav-links {
|
||||
|
||||
@@ -171,6 +171,11 @@ ul.notes {
|
||||
|
||||
&.parallel {
|
||||
border-width: 1px;
|
||||
|
||||
.code,
|
||||
code {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.notes {
|
||||
@@ -198,6 +203,12 @@ ul.notes {
|
||||
color: $notes-light-color;
|
||||
}
|
||||
|
||||
.discussion-headline-light {
|
||||
a {
|
||||
color: $gl-link-color;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions for Discussions/Notes
|
||||
*/
|
||||
@@ -209,6 +220,17 @@ ul.notes {
|
||||
color: $notes-action-color;
|
||||
}
|
||||
|
||||
.discussion-actions {
|
||||
@media (max-width: $screen-sm-max) {
|
||||
float: none;
|
||||
margin-left: 0;
|
||||
|
||||
.note-action-button {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.note-action-button,
|
||||
.discussion-action-button {
|
||||
display: inline-block;
|
||||
|
||||
@@ -1,17 +1,37 @@
|
||||
/* Generic print styles */
|
||||
header, nav, nav.main-nav, nav.navbar-collapse, nav.navbar-collapse.collapse {display: none!important;}
|
||||
.profiler-results {display: none;}
|
||||
|
||||
/* Styles targeted specifically at printing files */
|
||||
.tree-ref-holder, .tree-holder .breadcrumb, .blob-commit-info {display: none;}
|
||||
.file-title {display: none;}
|
||||
.file-holder {border: none;}
|
||||
|
||||
.wiki h1, .wiki h2, .wiki h3, .wiki h4, .wiki h5, .wiki h6 {margin-top: 17px; }
|
||||
.wiki h1 {font-size: 30px;}
|
||||
.wiki h2 {font-size: 22px;}
|
||||
.wiki h3 {font-size: 18px; font-weight: bold; }
|
||||
|
||||
.sidebar-wrapper { display: none; }
|
||||
.nav { display: none; }
|
||||
.btn { display: none; }
|
||||
header,
|
||||
nav,
|
||||
nav.main-nav,
|
||||
nav.navbar-collapse,
|
||||
nav.navbar-collapse.collapse,
|
||||
.profiler-results,
|
||||
.tree-ref-holder,
|
||||
.tree-holder .breadcrumb,
|
||||
.blob-commit-info,
|
||||
.file-title,
|
||||
.file-holder,
|
||||
.sidebar-wrapper,
|
||||
.nav,
|
||||
.btn,
|
||||
ul.notes-form,
|
||||
.merge-request-ci-status .ci-status-link:after,
|
||||
.issuable-gutter-toggle,
|
||||
.gutter-toggle,
|
||||
.issuable-details .content-block-small,
|
||||
.edit-link,
|
||||
.note-action-button {
|
||||
display: none!important;
|
||||
}
|
||||
|
||||
.page-gutter {
|
||||
padding-top: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.right-sidebar {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
|
||||
:admin_notification_email,
|
||||
:user_oauth_applications,
|
||||
:shared_runners_enabled,
|
||||
:shared_runners_text,
|
||||
:max_artifacts_size,
|
||||
:metrics_enabled,
|
||||
:metrics_host,
|
||||
@@ -92,6 +93,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
|
||||
:akismet_api_key,
|
||||
:email_author_in_body,
|
||||
:repository_checks_enabled,
|
||||
:metrics_packet_size,
|
||||
restricted_visibility_levels: [],
|
||||
import_sources: []
|
||||
)
|
||||
|
||||
@@ -39,6 +39,6 @@ class Admin::HooksController < Admin::ApplicationController
|
||||
end
|
||||
|
||||
def hook_params
|
||||
params.require(:hook).permit(:url, :enable_ssl_verification)
|
||||
params.require(:hook).permit(:url, :enable_ssl_verification, :push_events, :tag_push_events)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,8 +12,15 @@ class AutocompleteController < ApplicationController
|
||||
if params[:search].blank?
|
||||
# Include current user if available to filter by "Me"
|
||||
if params[:current_user] && current_user
|
||||
@users = [*@users, current_user].uniq
|
||||
@users = [*@users, current_user]
|
||||
end
|
||||
|
||||
if params[:author_id].present?
|
||||
author = User.find_by_id(params[:author_id])
|
||||
@users = [author, *@users] if author
|
||||
end
|
||||
|
||||
@users.uniq!
|
||||
end
|
||||
|
||||
render json: @users, only: [:name, :username, :id], methods: [:avatar_url]
|
||||
|
||||
@@ -51,6 +51,7 @@ class HelpController < ApplicationController
|
||||
end
|
||||
|
||||
def ui
|
||||
@user = User.new(id: 0, name: 'John Doe', username: '@johndoe')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -7,10 +7,12 @@ class Projects::GroupLinksController < Projects::ApplicationController
|
||||
end
|
||||
|
||||
def create
|
||||
link = project.project_group_links.new
|
||||
link.group_id = params[:link_group_id]
|
||||
link.group_access = params[:link_group_access]
|
||||
link.save
|
||||
group = Group.find(params[:link_group_id])
|
||||
return render_404 unless can?(current_user, :read_group, group)
|
||||
|
||||
project.project_group_links.create(
|
||||
group: group, group_access: params[:link_group_access]
|
||||
)
|
||||
|
||||
redirect_to namespace_project_group_links_path(project.namespace, project)
|
||||
end
|
||||
|
||||
@@ -60,8 +60,8 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||
end
|
||||
|
||||
def show
|
||||
@note = @project.notes.new(noteable: @issue)
|
||||
@notes = @issue.notes.nonawards.with_associations.fresh
|
||||
@note = @project.notes.new(noteable: @issue)
|
||||
@notes = @issue.notes.nonawards.with_associations.fresh
|
||||
@noteable = @issue
|
||||
|
||||
respond_to do |format|
|
||||
@@ -128,10 +128,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||
end
|
||||
|
||||
def related_branches
|
||||
merge_requests = @issue.referenced_merge_requests(current_user)
|
||||
|
||||
@related_branches = @issue.related_branches -
|
||||
merge_requests.map(&:source_branch)
|
||||
@related_branches = @issue.related_branches(current_user)
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
class UsersController < ApplicationController
|
||||
skip_before_action :authenticate_user!
|
||||
before_action :set_user
|
||||
before_action :user
|
||||
before_action :authorize_read_user!, only: [:show]
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
@@ -75,22 +76,26 @@ class UsersController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def set_user
|
||||
@user = User.find_by_username!(params[:username])
|
||||
def authorize_read_user!
|
||||
render_404 unless can?(current_user, :read_user, user)
|
||||
end
|
||||
|
||||
def user
|
||||
@user ||= User.find_by_username!(params[:username])
|
||||
end
|
||||
|
||||
def contributed_projects
|
||||
ContributedProjectsFinder.new(@user).execute(current_user)
|
||||
ContributedProjectsFinder.new(user).execute(current_user)
|
||||
end
|
||||
|
||||
def contributions_calendar
|
||||
@contributions_calendar ||= Gitlab::ContributionsCalendar.
|
||||
new(contributed_projects, @user)
|
||||
new(contributed_projects, user)
|
||||
end
|
||||
|
||||
def load_events
|
||||
# Get user activity feed for projects common for both users
|
||||
@events = @user.recent_events.
|
||||
@events = user.recent_events.
|
||||
merge(projects_for_current_user).
|
||||
references(:project).
|
||||
with_associations.
|
||||
@@ -99,16 +104,16 @@ class UsersController < ApplicationController
|
||||
|
||||
def load_projects
|
||||
@projects =
|
||||
PersonalProjectsFinder.new(@user).execute(current_user)
|
||||
PersonalProjectsFinder.new(user).execute(current_user)
|
||||
.page(params[:page])
|
||||
end
|
||||
|
||||
def load_contributed_projects
|
||||
@contributed_projects = contributed_projects.joined(@user)
|
||||
@contributed_projects = contributed_projects.joined(user)
|
||||
end
|
||||
|
||||
def load_groups
|
||||
@groups = JoinedGroupsFinder.new(@user).execute(current_user)
|
||||
@groups = JoinedGroupsFinder.new(user).execute(current_user)
|
||||
end
|
||||
|
||||
def projects_for_current_user
|
||||
|
||||
@@ -15,6 +15,10 @@ module ApplicationSettingsHelper
|
||||
current_application_settings.sign_in_text
|
||||
end
|
||||
|
||||
def shared_runners_text
|
||||
current_application_settings.shared_runners_text
|
||||
end
|
||||
|
||||
def user_oauth_applications?
|
||||
current_application_settings.user_oauth_applications
|
||||
end
|
||||
|
||||
@@ -183,7 +183,7 @@ module CommitsHelper
|
||||
|
||||
options = {
|
||||
class: "commit-#{options[:source]}-link has-tooltip",
|
||||
data: { 'original-title'.to_sym => sanitize(source_email) }
|
||||
title: source_email
|
||||
}
|
||||
|
||||
if user.nil?
|
||||
|
||||
@@ -55,6 +55,15 @@ module IssuablesHelper
|
||||
h(milestone_title.presence || default_label)
|
||||
end
|
||||
|
||||
def issuable_meta(issuable, project, text)
|
||||
output = content_tag :strong, "#{text} #{issuable.to_reference}", class: "identifier"
|
||||
output << " opened #{time_ago_with_tooltip(issuable.created_at)} by".html_safe
|
||||
output << content_tag(:strong) do
|
||||
author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs")
|
||||
author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg")
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sidebar_gutter_collapsed?
|
||||
|
||||
@@ -52,7 +52,7 @@ module ProjectsHelper
|
||||
link_to(author_html, user_path(author), class: "author_link #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe
|
||||
else
|
||||
title = opts[:title].sub(":name", sanitize(author.name))
|
||||
link_to(author_html, user_path(author), class: "author_link has-tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe
|
||||
link_to(author_html, user_path(author), class: "author_link has-tooltip", title: title, data: { container: 'body' } ).html_safe
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@ module SelectsHelper
|
||||
value = opts[:selected] || ''
|
||||
placeholder = opts[:placeholder] || 'Search for a user'
|
||||
|
||||
null_user = opts[:null_user] || false
|
||||
any_user = opts[:any_user] || false
|
||||
email_user = opts[:email_user] || false
|
||||
first_user = opts[:first_user] && current_user ? current_user.username : false
|
||||
current_user = opts[:current_user] || false
|
||||
project = opts[:project] || @project
|
||||
null_user = opts[:null_user] || false
|
||||
any_user = opts[:any_user] || false
|
||||
email_user = opts[:email_user] || false
|
||||
first_user = opts[:first_user] && current_user ? current_user.username : false
|
||||
current_user = opts[:current_user] || false
|
||||
author_id = opts[:author_id] || ''
|
||||
project = opts[:project] || @project
|
||||
|
||||
html = {
|
||||
class: css_class,
|
||||
@@ -21,7 +22,8 @@ module SelectsHelper
|
||||
any_user: any_user,
|
||||
email_user: email_user,
|
||||
first_user: first_user,
|
||||
current_user: current_user
|
||||
current_user: current_user,
|
||||
author_id: author_id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ class RepositoryCheckMailer < BaseMailer
|
||||
|
||||
mail(
|
||||
to: User.admins.pluck(:email),
|
||||
subject: @message
|
||||
subject: "GitLab Admin | #{@message}"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
+22
-7
@@ -18,6 +18,7 @@ class Ability
|
||||
when Namespace then namespace_abilities(user, subject)
|
||||
when GroupMember then group_member_abilities(user, subject)
|
||||
when ProjectMember then project_member_abilities(user, subject)
|
||||
when User then user_abilities
|
||||
else []
|
||||
end.concat(global_abilities(user))
|
||||
end
|
||||
@@ -35,6 +36,8 @@ class Ability
|
||||
anonymous_project_abilities(subject)
|
||||
when subject.is_a?(Group) || subject.respond_to?(:group)
|
||||
anonymous_group_abilities(subject)
|
||||
when subject.is_a?(User)
|
||||
anonymous_user_abilities
|
||||
else
|
||||
[]
|
||||
end
|
||||
@@ -81,17 +84,17 @@ class Ability
|
||||
end
|
||||
|
||||
def anonymous_group_abilities(subject)
|
||||
rules = []
|
||||
|
||||
group = if subject.is_a?(Group)
|
||||
subject
|
||||
else
|
||||
subject.group
|
||||
end
|
||||
|
||||
if group && group.public?
|
||||
[:read_group]
|
||||
else
|
||||
[]
|
||||
end
|
||||
rules << :read_group if group.public?
|
||||
|
||||
rules
|
||||
end
|
||||
|
||||
def anonymous_personal_snippet_abilities(snippet)
|
||||
@@ -110,9 +113,14 @@ class Ability
|
||||
end
|
||||
end
|
||||
|
||||
def anonymous_user_abilities
|
||||
[:read_user] unless restricted_public_level?
|
||||
end
|
||||
|
||||
def global_abilities(user)
|
||||
rules = []
|
||||
rules << :create_group if user.can_create_group
|
||||
rules << :read_users_list
|
||||
rules
|
||||
end
|
||||
|
||||
@@ -163,7 +171,7 @@ class Ability
|
||||
@public_project_rules ||= project_guest_rules + [
|
||||
:download_code,
|
||||
:fork_project,
|
||||
:read_commit_status,
|
||||
:read_commit_status
|
||||
]
|
||||
end
|
||||
|
||||
@@ -284,7 +292,6 @@ class Ability
|
||||
|
||||
def group_abilities(user, group)
|
||||
rules = []
|
||||
|
||||
rules << :read_group if can_read_group?(user, group)
|
||||
|
||||
# Only group masters and group owners can create new projects
|
||||
@@ -456,6 +463,10 @@ class Ability
|
||||
rules
|
||||
end
|
||||
|
||||
def user_abilities
|
||||
[:read_user]
|
||||
end
|
||||
|
||||
def abilities
|
||||
@abilities ||= begin
|
||||
abilities = Six.new
|
||||
@@ -470,6 +481,10 @@ class Ability
|
||||
|
||||
private
|
||||
|
||||
def restricted_public_level?
|
||||
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
|
||||
end
|
||||
|
||||
def named_abilities(name)
|
||||
[
|
||||
:"read_#{name}",
|
||||
|
||||
+15
-3
@@ -365,11 +365,23 @@ module Ci
|
||||
self.update(erased_by: user, erased_at: Time.now)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def yaml_variables
|
||||
global_yaml_variables + job_yaml_variables
|
||||
end
|
||||
|
||||
def global_yaml_variables
|
||||
if commit.config_processor
|
||||
commit.config_processor.variables.map do |key, value|
|
||||
commit.config_processor.global_variables.map do |key, value|
|
||||
{ key: key, value: value, public: true }
|
||||
end
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def job_yaml_variables
|
||||
if commit.config_processor
|
||||
commit.config_processor.job_variables(name).map do |key, value|
|
||||
{ key: key, value: value, public: true }
|
||||
end
|
||||
else
|
||||
|
||||
@@ -7,11 +7,13 @@ module InternalId
|
||||
end
|
||||
|
||||
def set_iid
|
||||
records = project.send(self.class.name.tableize)
|
||||
records = records.with_deleted if self.paranoid?
|
||||
max_iid = records.maximum(:iid)
|
||||
if iid.blank?
|
||||
records = project.send(self.class.name.tableize)
|
||||
records = records.with_deleted if self.paranoid?
|
||||
max_iid = records.maximum(:iid)
|
||||
|
||||
self.iid = max_iid.to_i + 1
|
||||
self.iid = max_iid.to_i + 1
|
||||
end
|
||||
end
|
||||
|
||||
def to_param
|
||||
|
||||
@@ -37,4 +37,10 @@ class ExternalIssue
|
||||
def to_reference(_from_project = nil)
|
||||
id
|
||||
end
|
||||
|
||||
def reference_link_text(from_project = nil)
|
||||
return "##{id}" if /^\d+$/.match(id)
|
||||
|
||||
id
|
||||
end
|
||||
end
|
||||
|
||||
@@ -21,8 +21,6 @@
|
||||
class ProjectHook < WebHook
|
||||
belongs_to :project
|
||||
|
||||
scope :push_hooks, -> { where(push_events: true) }
|
||||
scope :tag_push_hooks, -> { where(tag_push_events: true) }
|
||||
scope :issue_hooks, -> { where(issues_events: true) }
|
||||
scope :note_hooks, -> { where(note_events: true) }
|
||||
scope :merge_request_hooks, -> { where(merge_requests_events: true) }
|
||||
|
||||
@@ -19,4 +19,7 @@
|
||||
#
|
||||
|
||||
class SystemHook < WebHook
|
||||
def async_execute(data, hook_name)
|
||||
Sidekiq::Client.enqueue(SystemHookWorker, id, data, hook_name)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,6 +30,9 @@ class WebHook < ActiveRecord::Base
|
||||
default_value_for :build_events, false
|
||||
default_value_for :enable_ssl_verification, true
|
||||
|
||||
scope :push_hooks, -> { where(push_events: true) }
|
||||
scope :tag_push_hooks, -> { where(tag_push_events: true) }
|
||||
|
||||
# HTTParty timeout
|
||||
default_timeout Gitlab.config.gitlab.webhook_timeout
|
||||
|
||||
|
||||
+14
-4
@@ -104,10 +104,16 @@ class Issue < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
def related_branches
|
||||
project.repository.branch_names.select do |branch|
|
||||
# All branches containing the current issue's ID, except for
|
||||
# those with a merge request open referencing the current issue.
|
||||
def related_branches(current_user)
|
||||
branches_with_iid = project.repository.branch_names.select do |branch|
|
||||
branch =~ /\A#{iid}-(?!\d+-stable)/i
|
||||
end
|
||||
|
||||
branches_with_merge_request = self.referenced_merge_requests(current_user).map(&:source_branch)
|
||||
|
||||
branches_with_iid - branches_with_merge_request
|
||||
end
|
||||
|
||||
# Reset issue events cache
|
||||
@@ -151,13 +157,17 @@ class Issue < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def to_branch_name
|
||||
"#{iid}-#{title.parameterize}"
|
||||
if self.confidential?
|
||||
"#{iid}-confidential-issue"
|
||||
else
|
||||
"#{iid}-#{title.parameterize}"
|
||||
end
|
||||
end
|
||||
|
||||
def can_be_worked_on?(current_user)
|
||||
!self.closed? &&
|
||||
!self.project.forked? &&
|
||||
self.related_branches.empty? &&
|
||||
self.related_branches(current_user).empty? &&
|
||||
self.closed_by_merge_requests(current_user).empty?
|
||||
end
|
||||
end
|
||||
|
||||
+46
-8
@@ -409,6 +409,35 @@ class Project < ActiveRecord::Base
|
||||
self.import_data.destroy if self.import_data
|
||||
end
|
||||
|
||||
def import_url=(value)
|
||||
import_url = Gitlab::ImportUrl.new(value)
|
||||
create_or_update_import_data(credentials: import_url.credentials)
|
||||
super(import_url.sanitized_url)
|
||||
end
|
||||
|
||||
def import_url
|
||||
if import_data && super
|
||||
import_url = Gitlab::ImportUrl.new(super, credentials: import_data.credentials)
|
||||
import_url.full_url
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def create_or_update_import_data(data: nil, credentials: nil)
|
||||
project_import_data = import_data || build_import_data
|
||||
if data
|
||||
project_import_data.data ||= {}
|
||||
project_import_data.data = project_import_data.data.merge(data)
|
||||
end
|
||||
if credentials
|
||||
project_import_data.credentials ||= {}
|
||||
project_import_data.credentials = project_import_data.credentials.merge(credentials)
|
||||
end
|
||||
|
||||
project_import_data.save
|
||||
end
|
||||
|
||||
def import?
|
||||
external_import? || forked?
|
||||
end
|
||||
@@ -802,8 +831,8 @@ class Project < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
def hook_attrs
|
||||
{
|
||||
def hook_attrs(backward: true)
|
||||
attrs = {
|
||||
name: name,
|
||||
description: description,
|
||||
web_url: web_url,
|
||||
@@ -814,12 +843,19 @@ class Project < ActiveRecord::Base
|
||||
visibility_level: visibility_level,
|
||||
path_with_namespace: path_with_namespace,
|
||||
default_branch: default_branch,
|
||||
# Backward compatibility
|
||||
homepage: web_url,
|
||||
url: url_to_repo,
|
||||
ssh_url: ssh_url_to_repo,
|
||||
http_url: http_url_to_repo
|
||||
}
|
||||
|
||||
# Backward compatibility
|
||||
if backward
|
||||
attrs.merge!({
|
||||
homepage: web_url,
|
||||
url: url_to_repo,
|
||||
ssh_url: ssh_url_to_repo,
|
||||
http_url: http_url_to_repo
|
||||
})
|
||||
end
|
||||
|
||||
attrs
|
||||
end
|
||||
|
||||
# Reset events cache related to this project
|
||||
@@ -865,7 +901,9 @@ class Project < ActiveRecord::Base
|
||||
|
||||
def change_head(branch)
|
||||
repository.before_change_head
|
||||
gitlab_shell.update_repository_head(self.path_with_namespace, branch)
|
||||
repository.rugged.references.create('HEAD',
|
||||
"refs/heads/#{branch}",
|
||||
force: true)
|
||||
reload_default_branch
|
||||
end
|
||||
|
||||
|
||||
@@ -12,8 +12,20 @@ require 'file_size_validator'
|
||||
|
||||
class ProjectImportData < ActiveRecord::Base
|
||||
belongs_to :project
|
||||
|
||||
attr_encrypted :credentials,
|
||||
key: Gitlab::Application.secrets.db_key_base,
|
||||
marshal: true,
|
||||
encode: true,
|
||||
mode: :per_attribute_iv_and_salt
|
||||
|
||||
serialize :data, JSON
|
||||
|
||||
validates :project, presence: true
|
||||
|
||||
before_validation :symbolize_credentials
|
||||
|
||||
def symbolize_credentials
|
||||
# bang doesn't work here - attr_encrypted makes it not to work
|
||||
self.credentials = self.credentials.deep_symbolize_keys unless self.credentials.blank?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,11 +12,13 @@ class Repository
|
||||
attr_accessor :path_with_namespace, :project
|
||||
|
||||
def self.clean_old_archives
|
||||
repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
|
||||
Gitlab::Metrics.measure(:clean_old_archives) do
|
||||
repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
|
||||
|
||||
return unless File.directory?(repository_downloads_path)
|
||||
return unless File.directory?(repository_downloads_path)
|
||||
|
||||
Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
|
||||
Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(path_with_namespace, project)
|
||||
@@ -169,7 +171,12 @@ class Repository
|
||||
def rm_tag(tag_name)
|
||||
before_remove_tag
|
||||
|
||||
gitlab_shell.rm_tag(path_with_namespace, tag_name)
|
||||
begin
|
||||
rugged.tags.delete(tag_name)
|
||||
true
|
||||
rescue Rugged::ReferenceError
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def branch_names
|
||||
|
||||
@@ -73,6 +73,7 @@ class GitPushService < BaseService
|
||||
@project.update_merge_requests(params[:oldrev], params[:newrev], params[:ref], current_user)
|
||||
|
||||
EventCreateService.new.push(@project, current_user, build_push_data)
|
||||
SystemHooksService.new.execute_hooks(build_push_data_system_hook.dup, :push_hooks)
|
||||
@project.execute_hooks(build_push_data.dup, :push_hooks)
|
||||
@project.execute_services(build_push_data.dup, :push_hooks)
|
||||
CreateCommitBuildsService.new.execute(@project, current_user, build_push_data)
|
||||
@@ -138,6 +139,11 @@ class GitPushService < BaseService
|
||||
build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], push_commits)
|
||||
end
|
||||
|
||||
def build_push_data_system_hook
|
||||
@push_data_system ||= Gitlab::PushDataBuilder.
|
||||
build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], [])
|
||||
end
|
||||
|
||||
def push_to_existing_branch?
|
||||
# Return if this is not a push to a branch (e.g. new commits)
|
||||
Gitlab::Git.branch_ref?(params[:ref]) && !Gitlab::Git.blank_ref?(params[:oldrev])
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
class GitTagPushService
|
||||
attr_accessor :project, :user, :push_data
|
||||
class GitTagPushService < BaseService
|
||||
attr_accessor :push_data
|
||||
|
||||
def execute(project, user, oldrev, newrev, ref)
|
||||
def execute
|
||||
project.repository.before_push_tag
|
||||
|
||||
@project, @user = project, user
|
||||
@push_data = build_push_data(oldrev, newrev, ref)
|
||||
@push_data = build_push_data
|
||||
|
||||
EventCreateService.new.push(project, user, @push_data)
|
||||
EventCreateService.new.push(project, current_user, @push_data)
|
||||
SystemHooksService.new.execute_hooks(build_system_push_data.dup, :tag_push_hooks)
|
||||
project.execute_hooks(@push_data.dup, :tag_push_hooks)
|
||||
project.execute_services(@push_data.dup, :tag_push_hooks)
|
||||
CreateCommitBuildsService.new.execute(project, @user, @push_data)
|
||||
CreateCommitBuildsService.new.execute(project, current_user, @push_data)
|
||||
ProjectCacheWorker.perform_async(project.id)
|
||||
|
||||
true
|
||||
@@ -18,14 +18,14 @@ class GitTagPushService
|
||||
|
||||
private
|
||||
|
||||
def build_push_data(oldrev, newrev, ref)
|
||||
def build_push_data
|
||||
commits = []
|
||||
message = nil
|
||||
|
||||
if !Gitlab::Git.blank_ref?(newrev)
|
||||
tag_name = Gitlab::Git.ref_name(ref)
|
||||
if !Gitlab::Git.blank_ref?(params[:newrev])
|
||||
tag_name = Gitlab::Git.ref_name(params[:ref])
|
||||
tag = project.repository.find_tag(tag_name)
|
||||
if tag && tag.target == newrev
|
||||
if tag && tag.target == params[:newrev]
|
||||
commit = project.commit(tag.target)
|
||||
commits = [commit].compact
|
||||
message = tag.message
|
||||
@@ -33,6 +33,11 @@ class GitTagPushService
|
||||
end
|
||||
|
||||
Gitlab::PushDataBuilder.
|
||||
build(project, user, oldrev, newrev, ref, commits, message)
|
||||
build(project, current_user, params[:oldrev], params[:newrev], params[:ref], commits, message)
|
||||
end
|
||||
|
||||
def build_system_push_data
|
||||
Gitlab::PushDataBuilder.
|
||||
build(project, current_user, params[:oldrev], params[:newrev], params[:ref], [], '')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,28 +1,37 @@
|
||||
module Projects
|
||||
class ParticipantsService < BaseService
|
||||
def execute(note_type, note_id)
|
||||
participating =
|
||||
if note_type && note_id
|
||||
participants_in(note_type, note_id)
|
||||
else
|
||||
[]
|
||||
end
|
||||
def execute(noteable_type, noteable_id)
|
||||
@noteable_type = noteable_type
|
||||
@noteable_id = noteable_id
|
||||
project_members = sorted(project.team.members)
|
||||
participants = all_members + groups + project_members + participating
|
||||
participants = target_owner + participants_in_target + all_members + groups + project_members
|
||||
participants.uniq
|
||||
end
|
||||
|
||||
def participants_in(type, id)
|
||||
target =
|
||||
case type
|
||||
def target
|
||||
@target ||=
|
||||
case @noteable_type
|
||||
when "Issue"
|
||||
project.issues.find_by_iid(id)
|
||||
project.issues.find_by_iid(@noteable_id)
|
||||
when "MergeRequest"
|
||||
project.merge_requests.find_by_iid(id)
|
||||
project.merge_requests.find_by_iid(@noteable_id)
|
||||
when "Commit"
|
||||
project.commit(id)
|
||||
project.commit(@noteable_id)
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def target_owner
|
||||
return [] unless target && target.author.present?
|
||||
|
||||
[{
|
||||
name: target.author.name,
|
||||
username: target.author.username
|
||||
}]
|
||||
end
|
||||
|
||||
def participants_in_target
|
||||
return [] unless target
|
||||
|
||||
users = target.participants(current_user)
|
||||
@@ -30,13 +39,13 @@ module Projects
|
||||
end
|
||||
|
||||
def sorted(users)
|
||||
users.uniq.to_a.compact.sort_by(&:username).map do |user|
|
||||
users.uniq.to_a.compact.sort_by(&:username).map do |user|
|
||||
{ username: user.username, name: user.name }
|
||||
end
|
||||
end
|
||||
|
||||
def groups
|
||||
current_user.authorized_groups.sort_by(&:path).map do |group|
|
||||
current_user.authorized_groups.sort_by(&:path).map do |group|
|
||||
count = group.users.count
|
||||
{ username: group.path, name: group.name, count: count }
|
||||
end
|
||||
|
||||
@@ -3,17 +3,13 @@ class SystemHooksService
|
||||
execute_hooks(build_event_data(model, event))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def execute_hooks(data)
|
||||
SystemHook.all.each do |sh|
|
||||
async_execute_hook(sh, data, 'system_hooks')
|
||||
def execute_hooks(data, hooks_scope = :all)
|
||||
SystemHook.send(hooks_scope).each do |hook|
|
||||
hook.async_execute(data, 'system_hooks')
|
||||
end
|
||||
end
|
||||
|
||||
def async_execute_hook(hook, data, hook_name)
|
||||
Sidekiq::Client.enqueue(SystemHookWorker, hook.id, data, hook_name)
|
||||
end
|
||||
private
|
||||
|
||||
def build_event_data(model, event)
|
||||
data = {
|
||||
|
||||
@@ -26,7 +26,9 @@
|
||||
.btn-group{ data: data_attrs }
|
||||
- restricted_level_checkboxes('restricted-visibility-help').each do |level|
|
||||
= level
|
||||
%span.help-block#restricted-visibility-help Selected levels cannot be used by non-admin users for projects or snippets
|
||||
%span.help-block#restricted-visibility-help
|
||||
Selected levels cannot be used by non-admin users for projects or snippets.
|
||||
If the public level is restricted, user profiles are only visible to logged in users.
|
||||
.form-group
|
||||
= f.label :import_sources, class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
@@ -153,7 +155,11 @@
|
||||
= f.label :shared_runners_enabled do
|
||||
= f.check_box :shared_runners_enabled
|
||||
Enable shared runners for new projects
|
||||
|
||||
.form-group
|
||||
= f.label :shared_runners_text, class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
= f.text_area :shared_runners_text, class: 'form-control', rows: 4
|
||||
.help-block Markdown enabled
|
||||
.form-group
|
||||
= f.label :max_artifacts_size, 'Maximum artifacts size (MB)', class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
@@ -212,6 +218,13 @@
|
||||
.help-block
|
||||
The sampling interval in seconds. Sampled data includes memory usage,
|
||||
retained Ruby objects, file descriptors and so on.
|
||||
.form-group
|
||||
= f.label :metrics_packet_size, 'Metrics per packet', class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
= f.number_field :metrics_packet_size, class: 'form-control'
|
||||
.help-block
|
||||
The amount of points to store in a single UDP packet. More points
|
||||
results in fewer but larger UDP packets being sent.
|
||||
|
||||
%fieldset
|
||||
%legend Spam and Anti-bot Protection
|
||||
@@ -280,7 +293,7 @@
|
||||
= f.check_box :repository_checks_enabled
|
||||
Enable Repository Checks
|
||||
.help-block
|
||||
GitLab will periodically run
|
||||
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
|
||||
@@ -288,7 +301,7 @@
|
||||
= link_to 'Clear all repository checks', clear_repository_check_states_admin_application_settings_path, data: { confirm: 'This will clear repository check states for ALL projects in the database. This cannot be undone. Are you sure?' }, method: :put, class: "btn btn-sm btn-remove"
|
||||
.help-block
|
||||
If you got a lot of false alarms from repository checks you can choose to clear all repository check information from the database.
|
||||
|
||||
|
||||
|
||||
.form-actions
|
||||
= f.submit 'Save', class: 'btn btn-save'
|
||||
|
||||
@@ -16,6 +16,27 @@
|
||||
= f.label :url, "URL:", class: 'control-label'
|
||||
.col-sm-10
|
||||
= f.text_field :url, class: "form-control"
|
||||
.form-group
|
||||
= f.label :url, "Trigger", class: 'control-label'
|
||||
.col-sm-10.prepend-top-10
|
||||
%div
|
||||
System hook will be triggered on set of events like creating project
|
||||
or adding ssh key. But you can also enable extra triggers like Push events.
|
||||
|
||||
%div.prepend-top-default
|
||||
= f.check_box :push_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= f.label :push_events, class: 'list-label' do
|
||||
%strong Push events
|
||||
%p.light
|
||||
This url will be triggered by a push to the repository
|
||||
%div
|
||||
= f.check_box :tag_push_events, class: 'pull-left'
|
||||
.prepend-left-20
|
||||
= f.label :tag_push_events, class: 'list-label' do
|
||||
%strong Tag push events
|
||||
%p.light
|
||||
This url will be triggered when a new tag is pushed to the repository
|
||||
.form-group
|
||||
= f.label :enable_ssl_verification, "SSL verification", class: 'control-label checkbox'
|
||||
.col-sm-10
|
||||
@@ -31,13 +52,16 @@
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
System hooks (#{@hooks.count})
|
||||
%ul.well-list
|
||||
%ul.content-list
|
||||
- @hooks.each do |hook|
|
||||
%li
|
||||
.list-item-name
|
||||
%strong= hook.url
|
||||
%p SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
|
||||
|
||||
.pull-right
|
||||
.controls
|
||||
= link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-sm"
|
||||
= link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm"
|
||||
.monospace= hook.url
|
||||
%div
|
||||
- %w(push_events tag_push_events issues_events note_events merge_requests_events build_events).each do |trigger|
|
||||
- if hook.send(trigger)
|
||||
%span.label.label-gray= trigger.titleize
|
||||
%span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
|
||||
|
||||
|
||||
@@ -6,18 +6,17 @@
|
||||
.login-heading
|
||||
%h3 Create an account
|
||||
.login-body
|
||||
- user = params[:user].present? ? params[:user] : {}
|
||||
= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
|
||||
.devise-errors
|
||||
= devise_error_messages!
|
||||
%div
|
||||
= f.text_field :name, class: "form-control top", value: user[:name], placeholder: "Name", required: true
|
||||
= f.text_field :name, class: "form-control top", placeholder: "Name", required: true
|
||||
%div
|
||||
= f.text_field :username, class: "form-control middle", value: user[:username], placeholder: "Username", required: true
|
||||
= f.text_field :username, class: "form-control middle", placeholder: "Username", required: true
|
||||
%div
|
||||
= f.email_field :email, class: "form-control middle", value: user[:email], placeholder: "Email", required: true
|
||||
= f.email_field :email, class: "form-control middle", placeholder: "Email", required: true
|
||||
.form-group.append-bottom-20#password-strength
|
||||
= f.password_field :password, class: "form-control bottom", value: user[:password], id: "user_password_sign_up", placeholder: "Password", required: true
|
||||
= f.password_field :password, class: "form-control bottom", id: "user_password_sign_up", placeholder: "Password", required: true
|
||||
%div
|
||||
- if current_application_settings.recaptcha_enabled
|
||||
= recaptcha_tags
|
||||
|
||||
@@ -345,11 +345,11 @@
|
||||
%ul
|
||||
%li
|
||||
%a.dropdown-menu-user-link.is-active{href: "#"}
|
||||
= link_to_member_avatar(current_user, size: 30)
|
||||
= link_to_member_avatar(@user, size: 30)
|
||||
%strong.dropdown-menu-user-full-name
|
||||
= current_user.name
|
||||
= @user.name
|
||||
.dropdown-menu-user-username
|
||||
= current_user.to_reference
|
||||
= @user.to_reference
|
||||
|
||||
.example
|
||||
%div
|
||||
@@ -372,11 +372,11 @@
|
||||
%ul
|
||||
%li
|
||||
%a.dropdown-menu-user-link.is-active{href: "#"}
|
||||
= link_to_member_avatar(current_user, size: 30)
|
||||
= link_to_member_avatar(@user, size: 30)
|
||||
%strong.dropdown-menu-user-full-name
|
||||
= current_user.name
|
||||
= @user.name
|
||||
.dropdown-menu-user-username
|
||||
= current_user.to_reference
|
||||
= @user.to_reference
|
||||
.dropdown-page-two
|
||||
.dropdown-title
|
||||
%button.dropdown-title-button.dropdown-menu-back{aria: {label: "Go back"}}
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
job.attr("id", "project_#{@project.id}")
|
||||
target_field = job.find(".import-target")
|
||||
target_field.empty()
|
||||
target_field.append('<strong>#{link_to @project.path_with_namespace, namespace_project_path(@project.namespace, @project)}</strong>')
|
||||
target_field.append('#{link_to @project.path_with_namespace, namespace_project_path(@project.namespace, @project)}')
|
||||
$("table.import-jobs tbody").prepend(job)
|
||||
job.addClass("active").find(".import-actions").html("<i class='fa fa-spinner fa-spin'></i> started")
|
||||
- else
|
||||
:plain
|
||||
job = $("tr#repo_#{@repo_id}")
|
||||
job.find(".import-actions").html("<i class='fa fa-exclamation-circle'> Error saving project: #{escape_javascript(@project.errors.full_messages.join(','))}</i>")
|
||||
job.find(".import-actions").html("<i class='fa fa-exclamation-circle'></i> Error saving project: #{escape_javascript(@project.errors.full_messages.join(','))}")
|
||||
|
||||
@@ -10,13 +10,19 @@
|
||||
%hr
|
||||
%p
|
||||
- if @incompatible_repos.any?
|
||||
= button_tag 'Import all compatible projects', class: "btn btn-success js-import-all"
|
||||
= button_tag class: "btn btn-import btn-success js-import-all" do
|
||||
Import all compatible projects
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
- else
|
||||
= button_tag 'Import all projects', class: "btn btn-success js-import-all"
|
||||
= button_tag class: "btn btn-success js-import-all" do
|
||||
Import all projects
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
|
||||
.table-holder
|
||||
.table-responsive
|
||||
%table.table.import-jobs
|
||||
%colgroup.import-jobs-from-col
|
||||
%colgroup.import-jobs-to-col
|
||||
%colgroup.import-jobs-status-col
|
||||
%thead
|
||||
%tr
|
||||
%th From Bitbucket
|
||||
@@ -28,7 +34,7 @@
|
||||
%td
|
||||
= link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: "_blank"
|
||||
%td
|
||||
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
%td.job-status
|
||||
- if project.import_status == 'finished'
|
||||
%span
|
||||
@@ -47,7 +53,9 @@
|
||||
%td.import-target
|
||||
= "#{repo["owner"]}/#{repo["slug"]}"
|
||||
%td.import-actions.job-status
|
||||
= button_tag "Import", class: "btn js-add-to-import"
|
||||
= button_tag class: "btn btn-import js-add-to-import" do
|
||||
Import
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
- @incompatible_repos.each do |repo|
|
||||
%tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"}
|
||||
%td
|
||||
|
||||
@@ -13,10 +13,15 @@
|
||||
how FogBugz email addresses and usernames are imported into GitLab.
|
||||
%hr
|
||||
%p
|
||||
= button_tag 'Import all projects', class: 'btn btn-success js-import-all'
|
||||
= button_tag class: 'btn btn-import btn-success js-import-all' do
|
||||
Import all projects
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
.table-holder
|
||||
.table-responsive
|
||||
%table.table.import-jobs
|
||||
%colgroup.import-jobs-from-col
|
||||
%colgroup.import-jobs-to-col
|
||||
%colgroup.import-jobs-status-col
|
||||
%thead
|
||||
%tr
|
||||
%th From FogBugz
|
||||
@@ -28,7 +33,7 @@
|
||||
%td
|
||||
= project.import_source
|
||||
%td
|
||||
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
%td.job-status
|
||||
- if project.import_status == 'finished'
|
||||
%span
|
||||
@@ -47,7 +52,9 @@
|
||||
%td.import-target
|
||||
= "#{current_user.username}/#{repo.name}"
|
||||
%td.import-actions.job-status
|
||||
= button_tag "Import", class: "btn js-add-to-import"
|
||||
= button_tag class: "btn btn-import js-add-to-import" do
|
||||
Import
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
:javascript
|
||||
new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}");
|
||||
|
||||
@@ -8,10 +8,15 @@
|
||||
Select projects you want to import.
|
||||
%hr
|
||||
%p
|
||||
= button_tag 'Import all projects', class: "btn btn-success js-import-all"
|
||||
= button_tag class: "btn btn-import btn-success js-import-all" do
|
||||
Import all projects
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
.table-holder
|
||||
.table-responsive
|
||||
%table.table.import-jobs
|
||||
%colgroup.import-jobs-from-col
|
||||
%colgroup.import-jobs-to-col
|
||||
%colgroup.import-jobs-status-col
|
||||
%thead
|
||||
%tr
|
||||
%th From GitHub
|
||||
@@ -23,7 +28,7 @@
|
||||
%td
|
||||
= link_to project.import_source, "https://github.com/#{project.import_source}", target: "_blank"
|
||||
%td
|
||||
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
%td.job-status
|
||||
- if project.import_status == 'finished'
|
||||
%span
|
||||
@@ -42,7 +47,9 @@
|
||||
%td.import-target
|
||||
= repo.full_name
|
||||
%td.import-actions.job-status
|
||||
= button_tag "Import", class: "btn js-add-to-import"
|
||||
= button_tag class: "btn btn-import js-add-to-import" do
|
||||
Import
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
:javascript
|
||||
new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}");
|
||||
|
||||
@@ -8,10 +8,15 @@
|
||||
Select projects you want to import.
|
||||
%hr
|
||||
%p
|
||||
= button_tag 'Import all projects', class: "btn btn-success js-import-all"
|
||||
= button_tag class: "btn btn-import btn-success js-import-all" do
|
||||
Import all projects
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
.table-holder
|
||||
.table-responsive
|
||||
%table.table.import-jobs
|
||||
%colgroup.import-jobs-from-col
|
||||
%colgroup.import-jobs-to-col
|
||||
%colgroup.import-jobs-status-col
|
||||
%thead
|
||||
%tr
|
||||
%th From GitLab.com
|
||||
@@ -23,7 +28,7 @@
|
||||
%td
|
||||
= link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank"
|
||||
%td
|
||||
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
%td.job-status
|
||||
- if project.import_status == 'finished'
|
||||
%span
|
||||
@@ -42,7 +47,9 @@
|
||||
%td.import-target
|
||||
= repo["path_with_namespace"]
|
||||
%td.import-actions.job-status
|
||||
= button_tag "Import", class: "btn js-add-to-import"
|
||||
= button_tag class: "btn js-add-to-import" do
|
||||
Import
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
:javascript
|
||||
new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}");
|
||||
|
||||
@@ -8,10 +8,15 @@
|
||||
Select projects you want to import.
|
||||
%hr
|
||||
%p
|
||||
= button_tag 'Import all projects', class: "btn btn-success js-import-all"
|
||||
= button_tag class: "btn btn-import btn-success js-import-all" do
|
||||
Import all projects
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
.table-holder
|
||||
.table-responsive
|
||||
%table.table.import-jobs
|
||||
%colgroup.import-jobs-from-col
|
||||
%colgroup.import-jobs-to-col
|
||||
%colgroup.import-jobs-status-col
|
||||
%thead
|
||||
%tr
|
||||
%th From Gitorious.org
|
||||
@@ -23,7 +28,7 @@
|
||||
%td
|
||||
= link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank"
|
||||
%td
|
||||
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
%td.job-status
|
||||
- if project.import_status == 'finished'
|
||||
%span
|
||||
@@ -42,7 +47,9 @@
|
||||
%td.import-target
|
||||
= repo.full_name
|
||||
%td.import-actions.job-status
|
||||
= button_tag "Import", class: "btn js-add-to-import"
|
||||
= button_tag class: "btn btn-import js-add-to-import" do
|
||||
Import
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
:javascript
|
||||
new ImporterStatus("#{jobs_import_gitorious_path}", "#{import_gitorious_path}");
|
||||
|
||||
@@ -14,12 +14,19 @@
|
||||
%hr
|
||||
%p
|
||||
- if @incompatible_repos.any?
|
||||
= button_tag 'Import all compatible projects', class: "btn btn-success js-import-all"
|
||||
= button_tag class: "btn btn-import btn-success js-import-all" do
|
||||
Import all compatible projects
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
- else
|
||||
= button_tag 'Import all projects', class: "btn btn-success js-import-all"
|
||||
= button_tag class: "btn btn-import btn-success js-import-all" do
|
||||
Import all projects
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
|
||||
.table-holder
|
||||
.table-responsive
|
||||
%table.table.import-jobs
|
||||
%colgroup.import-jobs-from-col
|
||||
%colgroup.import-jobs-to-col
|
||||
%colgroup.import-jobs-status-col
|
||||
%thead
|
||||
%tr
|
||||
%th From Google Code
|
||||
@@ -31,7 +38,7 @@
|
||||
%td
|
||||
= link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank"
|
||||
%td
|
||||
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
|
||||
%td.job-status
|
||||
- if project.import_status == 'finished'
|
||||
%span
|
||||
@@ -50,7 +57,9 @@
|
||||
%td.import-target
|
||||
= "#{current_user.username}/#{repo.name}"
|
||||
%td.import-actions.job-status
|
||||
= button_tag "Import", class: "btn js-add-to-import"
|
||||
= button_tag class: "btn btn-import js-add-to-import" do
|
||||
Import
|
||||
= icon("spinner spin", class: "loading-icon")
|
||||
- @incompatible_repos.each do |repo|
|
||||
%tr{id: "repo_#{repo.id}"}
|
||||
%td
|
||||
|
||||
@@ -52,6 +52,9 @@
|
||||
%li
|
||||
phpunit --coverage-text --colors=never (PHP) -
|
||||
%code ^\s*Lines:\s*\d+.\d+\%
|
||||
%li
|
||||
gcovr (C/C++) -
|
||||
%code ^TOTAL.*\s+(\d+\%)$
|
||||
|
||||
.form-group
|
||||
.col-sm-offset-2.col-sm-10
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.md-area
|
||||
.md-header
|
||||
%ul.nav-links
|
||||
%ul.nav-links.clearfix
|
||||
%li.active
|
||||
%a.js-md-write-button{ href: "#md-write-holder", tabindex: -1 }
|
||||
Write
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
= cache(cache_key) do
|
||||
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
|
||||
.commit-row-title
|
||||
%span.item-title.str-truncated
|
||||
%span.item-title
|
||||
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
|
||||
- if commit.description?
|
||||
%a.text-expander.js-toggle-button ...
|
||||
|
||||
@@ -74,16 +74,15 @@
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
Webhooks (#{@hooks.count})
|
||||
%ul.well-list
|
||||
%ul.content-list
|
||||
- @hooks.each do |hook|
|
||||
%li
|
||||
.pull-right
|
||||
.controls
|
||||
= link_to 'Test Hook', test_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm btn-grouped"
|
||||
= link_to 'Remove', namespace_project_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
|
||||
.clearfix
|
||||
%span.monospace= hook.url
|
||||
%p
|
||||
.monospace= hook.url
|
||||
%div
|
||||
- %w(push_events tag_push_events issues_events note_events merge_requests_events build_events).each do |trigger|
|
||||
- if hook.send(trigger)
|
||||
%span.label.label-gray= trigger.titleize
|
||||
SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
|
||||
%span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
|
||||
|
||||
@@ -1,82 +1,79 @@
|
||||
- page_title "#{@issue.title} (##{@issue.iid})", "Issues"
|
||||
- page_description @issue.description
|
||||
- page_card_attributes @issue.card_attributes
|
||||
- header_title project_title(@project, "Issues", namespace_project_issues_path(@project.namespace, @project))
|
||||
|
||||
= render "header_title"
|
||||
.clearfix.detail-page-header
|
||||
.issuable-header
|
||||
.issuable-status-box.status-box.status-box-closed{ class: issue_button_visibility(@issue, false) }
|
||||
= icon('check', class: "hidden-sm hidden-md hidden-lg")
|
||||
%span.hidden-xs
|
||||
Closed
|
||||
.issuable-status-box.status-box.status-box-open{ class: issue_button_visibility(@issue, true) }
|
||||
= icon('circle-o', class: "hidden-sm hidden-md hidden-lg")
|
||||
%span.hidden-xs Open
|
||||
|
||||
.issue
|
||||
.detail-page-header.issuable-header
|
||||
.pull-left
|
||||
.status-box{ class: "status-box-closed #{issue_button_visibility(@issue, false)}"}
|
||||
%span.hidden-xs
|
||||
Closed
|
||||
%span.hidden-sm.hidden-md.hidden-lg
|
||||
= icon('check')
|
||||
.status-box{ class: "status-box-open #{issue_button_visibility(@issue, true)}"}
|
||||
%span.hidden-xs
|
||||
Open
|
||||
%span.hidden-sm.hidden-md.hidden-lg
|
||||
= icon('circle-o')
|
||||
|
||||
%a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.js-sidebar-toggle{ href: "#" }
|
||||
%a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
|
||||
= icon('angle-double-left')
|
||||
|
||||
.issue-meta
|
||||
.issuable-meta
|
||||
= confidential_icon(@issue)
|
||||
%strong.identifier
|
||||
Issue ##{@issue.iid}
|
||||
%span.creator
|
||||
opened
|
||||
.editor-details
|
||||
.editor-details
|
||||
= time_ago_with_tooltip(@issue.created_at)
|
||||
by
|
||||
%strong
|
||||
= link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-xs")
|
||||
%strong
|
||||
= link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg",
|
||||
by_username: true, avatar: false)
|
||||
= issuable_meta(@issue, @project, "Issue")
|
||||
|
||||
.pull-right.issue-btn-group
|
||||
- if can?(current_user, :create_issue, @project)
|
||||
= link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do
|
||||
= icon('plus')
|
||||
New issue
|
||||
- if can?(current_user, :update_issue, @issue)
|
||||
= link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
|
||||
= link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
|
||||
= link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-nr btn-grouped issuable-edit' do
|
||||
= icon('pencil-square-o')
|
||||
Edit
|
||||
- if can?(current_user, :create_issue, @project) || can?(current_user, :update_issue, @issue)
|
||||
.issuable-actions
|
||||
.clearfix.issue-btn-group.dropdown
|
||||
%button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } }
|
||||
%span.caret
|
||||
Options
|
||||
.dropdown-menu.dropdown-menu-align-right.hidden-lg
|
||||
%ul
|
||||
- if can?(current_user, :create_issue, @project)
|
||||
%li
|
||||
= link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link'
|
||||
- if can?(current_user, :update_issue, @issue)
|
||||
%li
|
||||
= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
|
||||
%li
|
||||
= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
|
||||
%li
|
||||
= link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue)
|
||||
- if can?(current_user, :create_issue, @project)
|
||||
= link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do
|
||||
= icon('plus')
|
||||
New issue
|
||||
- if can?(current_user, :update_issue, @issue)
|
||||
= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
|
||||
= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
|
||||
= link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit' do
|
||||
= icon('pencil-square-o')
|
||||
Edit
|
||||
|
||||
|
||||
.issue-details.issuable-details
|
||||
.detail-page-description.content-block
|
||||
%h2.title
|
||||
= markdown escape_once(@issue.title), pipeline: :single_line
|
||||
%div
|
||||
- if @issue.description.present?
|
||||
.description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''}
|
||||
.wiki
|
||||
= preserve do
|
||||
= markdown(@issue.description, cache_key: [@issue, "description"])
|
||||
%textarea.hidden.js-task-list-field
|
||||
= @issue.description
|
||||
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
|
||||
.issue-details.issuable-details
|
||||
.detail-page-description.content-block
|
||||
%h2.title
|
||||
= markdown escape_once(@issue.title), pipeline: :single_line
|
||||
- if @issue.description.present?
|
||||
.description{ class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : '' }
|
||||
.wiki
|
||||
= preserve do
|
||||
= markdown(@issue.description, cache_key: [@issue, "description"])
|
||||
%textarea.hidden.js-task-list-field
|
||||
= @issue.description
|
||||
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
|
||||
|
||||
#merge-requests{'data-url' => referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue)}
|
||||
// This element is filled in using JavaScript.
|
||||
#merge-requests{ data: { url: referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue) } }
|
||||
// This element is filled in using JavaScript.
|
||||
|
||||
#related-branches{'data-url' => related_branches_namespace_project_issue_url(@project.namespace, @project, @issue)}
|
||||
// This element is filled in using JavaScript.
|
||||
#related-branches{ data: { url: related_branches_namespace_project_issue_url(@project.namespace, @project, @issue) } }
|
||||
// This element is filled in using JavaScript.
|
||||
|
||||
.content-block.content-block-small
|
||||
= render 'new_branch'
|
||||
= render 'votes/votes_block', votable: @issue
|
||||
.content-block.content-block-small
|
||||
= render 'new_branch'
|
||||
= render 'votes/votes_block', votable: @issue
|
||||
|
||||
.row
|
||||
%section.col-md-12
|
||||
.issuable-discussion
|
||||
= render 'projects/issues/discussion'
|
||||
%section.issuable-discussion
|
||||
= render 'projects/issues/discussion'
|
||||
|
||||
= render 'shared/issuable/sidebar', issuable: @issue
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
$('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}";
|
||||
$('aside.right-sidebar').effect('highlight');
|
||||
new IssuableContext();
|
||||
|
||||
@@ -16,11 +16,9 @@
|
||||
= dropdown_title("Select source project")
|
||||
= dropdown_filter("Search projects")
|
||||
= dropdown_content do
|
||||
- is_active = f.object.source_project_id == @merge_request.source_project.id
|
||||
%ul
|
||||
%li
|
||||
%a{ href: "#", class: "#{("is-active" if is_active)}", data: { id: @merge_request.source_project.id } }
|
||||
= @merge_request.source_project_path
|
||||
= render 'projects/merge_requests/dropdowns/project',
|
||||
projects: [@merge_request.source_project],
|
||||
selected: f.object.source_project_id
|
||||
.merge-request-select.dropdown
|
||||
= f.hidden_field :source_branch
|
||||
= dropdown_toggle "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" }
|
||||
@@ -28,11 +26,9 @@
|
||||
= dropdown_title("Select source branch")
|
||||
= dropdown_filter("Search branches")
|
||||
= dropdown_content do
|
||||
%ul
|
||||
- @merge_request.source_branches.each do |branch|
|
||||
%li
|
||||
%a{ href: "#", class: "#{("is-active" if f.object.source_branch == branch)}", data: { id: branch } }
|
||||
= branch
|
||||
= render 'projects/merge_requests/dropdowns/branch',
|
||||
branches: @merge_request.source_branches,
|
||||
selected: f.object.source_branch
|
||||
.panel-footer
|
||||
= icon('spinner spin', class: 'js-source-loading')
|
||||
%ul.list-unstyled.mr_source_commit
|
||||
@@ -50,11 +46,9 @@
|
||||
= dropdown_title("Select target project")
|
||||
= dropdown_filter("Search projects")
|
||||
= dropdown_content do
|
||||
%ul
|
||||
- projects.each do |project|
|
||||
%li
|
||||
%a{ href: "#", class: "#{("is-active" if f.object.target_project_id == project.id)}", data: { id: project.id } }
|
||||
= project.path_with_namespace
|
||||
= render 'projects/merge_requests/dropdowns/project',
|
||||
projects: projects,
|
||||
selected: f.object.target_project_id
|
||||
.merge-request-select.dropdown
|
||||
= f.hidden_field :target_branch
|
||||
= dropdown_toggle f.object.target_branch, { toggle: "dropdown", field_name: "#{f.object_name}[target_branch]" }, { toggle_class: "js-compare-dropdown js-target-branch" }
|
||||
@@ -62,11 +56,9 @@
|
||||
= dropdown_title("Select target branch")
|
||||
= dropdown_filter("Search branches")
|
||||
= dropdown_content do
|
||||
%ul
|
||||
- @merge_request.target_branches.each do |branch|
|
||||
%li
|
||||
%a{ href: "#", class: "#{("is-active" if f.object.target_branch == branch)}", data: { id: branch } }
|
||||
= branch
|
||||
= render 'projects/merge_requests/dropdowns/branch',
|
||||
branches: @merge_request.target_branches,
|
||||
selected: f.object.target_branch
|
||||
.panel-footer
|
||||
= icon('spinner spin', class: "js-target-loading")
|
||||
%ul.list-unstyled.mr_target_commit
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
|
||||
- page_description @merge_request.description
|
||||
- page_card_attributes @merge_request.card_attributes
|
||||
|
||||
= render "header_title"
|
||||
- header_title project_title(@project, "Merge Requests", namespace_project_merge_requests_path(@project.namespace, @project))
|
||||
|
||||
- if params[:view] == 'parallel'
|
||||
- fluid_layout true
|
||||
@@ -32,8 +31,8 @@
|
||||
%span Request to merge
|
||||
%span.label-branch= source_branch_with_namespace(@merge_request)
|
||||
%span into
|
||||
= link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
|
||||
= @merge_request.target_branch
|
||||
%span.label-branch
|
||||
= link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch)
|
||||
- if @merge_request.open? && @merge_request.diverged_from_target_branch?
|
||||
%span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind)
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
%ul
|
||||
- branches.each do |branch|
|
||||
%li
|
||||
%a{ href: '#', class: "#{('is-active' if selected == branch)}", data: { id: branch } }
|
||||
= branch
|
||||
@@ -0,0 +1,5 @@
|
||||
%ul
|
||||
- projects.each do |project|
|
||||
%li
|
||||
%a{ href: "#", class: "#{('is-active' if selected == project.id)}", data: { id: project.id } }
|
||||
= project.path_with_namespace
|
||||
@@ -1,35 +1,32 @@
|
||||
.detail-page-header
|
||||
.status-box{ class: status_box_class(@merge_request) }
|
||||
%span.hidden-xs
|
||||
= @merge_request.state_human_name
|
||||
%span.hidden-sm.hidden-md.hidden-lg
|
||||
= icon(@merge_request.state_icon_name)
|
||||
%a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.js-sidebar-toggle{ href: "#" }
|
||||
= icon('angle-double-left')
|
||||
.issue-meta
|
||||
%strong.identifier
|
||||
%span.hidden-sm.hidden-md.hidden-lg
|
||||
MR
|
||||
.clearfix.detail-page-header
|
||||
.issuable-header
|
||||
.issuable-status-box.status-box{ class: status_box_class(@merge_request) }
|
||||
= icon(@merge_request.state_icon_name, class: "hidden-sm hidden-md hidden-lg")
|
||||
%span.hidden-xs
|
||||
Merge Request
|
||||
!#{@merge_request.iid}
|
||||
%span.creator
|
||||
opened
|
||||
.editor-details
|
||||
= time_ago_with_tooltip(@merge_request.created_at)
|
||||
by
|
||||
%strong
|
||||
= link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-xs")
|
||||
%strong
|
||||
= link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg",
|
||||
by_username: true, avatar: false)
|
||||
= @merge_request.state_human_name
|
||||
|
||||
.issue-btn-group.pull-right
|
||||
- if can?(current_user, :update_merge_request, @merge_request)
|
||||
- if @merge_request.open?
|
||||
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'btn btn-nr btn-grouped btn-close', title: 'Close merge request'
|
||||
= link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-nr btn-grouped issuable-edit', id: 'edit_merge_request' do
|
||||
%a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
|
||||
= icon('angle-double-left')
|
||||
|
||||
.issuable-meta
|
||||
= issuable_meta(@merge_request, @project, "Merge Request")
|
||||
|
||||
- if can?(current_user, :update_merge_request, @merge_request)
|
||||
.issuable-actions
|
||||
.clearfix.issue-btn-group.dropdown
|
||||
%button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } }
|
||||
%span.caret
|
||||
Options
|
||||
.dropdown-menu.dropdown-menu-align-right.hidden-lg
|
||||
%ul
|
||||
%li{ class: issue_button_visibility(@merge_request, true) }
|
||||
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request'
|
||||
%li{ class: issue_button_visibility(@merge_request, false) }
|
||||
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
|
||||
%li
|
||||
= link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit', id: 'edit_merge_request'
|
||||
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-close #{issue_button_visibility(@merge_request, true)}", title: 'Close merge request'
|
||||
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen reopen-mr-link #{issue_button_visibility(@merge_request, false)}", title: 'Reopen merge request'
|
||||
= link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit", id: 'edit_merge_request' do
|
||||
= icon('pencil-square-o')
|
||||
Edit
|
||||
- if @merge_request.closed?
|
||||
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'btn btn-nr btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request'
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
$('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}";
|
||||
$('aside.right-sidebar').effect('highlight');
|
||||
new IssuableContext();
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
%ul
|
||||
- @target_branches.each do |branch|
|
||||
%li
|
||||
%a{ href: "#", class: "#{("is-active" if "a" == branch)}", data: { id: branch } }
|
||||
= branch
|
||||
= render 'projects/merge_requests/dropdowns/branch',
|
||||
branches: @target_branches,
|
||||
selected: nil
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
- note = discussion_notes.first
|
||||
.timeline-entry
|
||||
%li.note.note-discussion.timeline-entry
|
||||
.timeline-entry-inner
|
||||
.timeline-icon
|
||||
= link_to user_path(note.author) do
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)] }
|
||||
- note_editable = note_editable?(note)
|
||||
%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable} }
|
||||
.timeline-entry-inner
|
||||
.timeline-icon
|
||||
%a{href: user_path(note.author)}
|
||||
@@ -15,16 +16,16 @@
|
||||
- if access
|
||||
%span.note-role
|
||||
= access
|
||||
- if note_editable?(note)
|
||||
- if note_editable
|
||||
= link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
|
||||
= icon('pencil')
|
||||
= link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do
|
||||
= icon('trash-o')
|
||||
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
|
||||
.note-body{class: note_editable ? 'js-task-list-container' : ''}
|
||||
.note-text
|
||||
= preserve do
|
||||
= markdown(note.note, pipeline: :note, cache_key: [note, "note"])
|
||||
- if note_editable?(note)
|
||||
- if note_editable
|
||||
= render 'projects/notes/edit_form', note: note
|
||||
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%ul#notes-list.notes.main-notes-list.timeline
|
||||
= render "projects/notes/notes"
|
||||
%ul.notes.timeline
|
||||
%ul.notes.notes-form.timeline
|
||||
%li.timeline-entry
|
||||
- if can? current_user, :create_note, @project
|
||||
.timeline-icon.hidden-xs.hidden-sm
|
||||
|
||||
@@ -6,15 +6,11 @@
|
||||
= "#{note.author.to_reference} started a discussion"
|
||||
= link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do
|
||||
on the diff
|
||||
= time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
|
||||
.discussion-actions
|
||||
= link_to "#", class: "discussion-action-button discussion-toggle-button js-toggle-button" do
|
||||
%i.fa.fa-chevron-up
|
||||
Show/hide discussion
|
||||
.last-update.hide.js-toggle-content
|
||||
- last_note = discussion_notes.last
|
||||
last updated by
|
||||
= link_to_member(@project, last_note.author, avatar: false)
|
||||
#{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
|
||||
|
||||
.discussion-body.js-toggle-content
|
||||
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
|
||||
|
||||
@@ -8,21 +8,18 @@
|
||||
= "#{note.author.to_reference} started a discussion on #{commit_description}"
|
||||
- if commit
|
||||
= link_to(commit.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable), class: 'monospace')
|
||||
= time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
|
||||
.discussion-actions
|
||||
= link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
|
||||
%i.fa.fa-chevron-up
|
||||
Show/hide discussion
|
||||
.last-update.hide.js-toggle-content
|
||||
- last_note = discussion_notes.last
|
||||
last updated by
|
||||
= link_to_member(@project, last_note.author, avatar: false)
|
||||
#{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
|
||||
.discussion-body.js-toggle-content
|
||||
- if note.for_diff_line?
|
||||
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
|
||||
- else
|
||||
.panel.panel-default
|
||||
.notes{ data: { discussion_id: discussion_notes.first.discussion_id } }
|
||||
= render discussion_notes
|
||||
%ul.notes.timeline
|
||||
= render discussion_notes
|
||||
.discussion-reply-holder
|
||||
= link_to_reply_diff(discussion_notes.first)
|
||||
|
||||
@@ -5,14 +5,10 @@
|
||||
.inline.discussion-headline-light
|
||||
= "#{note.author.to_reference} started a discussion"
|
||||
on the outdated diff
|
||||
= time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
|
||||
.discussion-actions
|
||||
= link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
|
||||
%i.fa.fa-chevron-down
|
||||
Show/hide discussion
|
||||
.last-update.hide.js-toggle-content
|
||||
- last_note = discussion_notes.last
|
||||
last updated by
|
||||
= link_to_member(@project, last_note.author, avatar: false)
|
||||
#{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
|
||||
.discussion-body.js-toggle-content.hide
|
||||
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
%h3 Shared runners
|
||||
|
||||
.bs-callout.bs-callout-warning
|
||||
GitLab Runners do not offer secure isolation between projects that they do builds for. You are TRUSTING all GitLab users who can push code to project A, B or C to run shell scripts on the machine hosting runner X.
|
||||
.bs-callout.bs-callout-warning.shared-runners-description
|
||||
- if shared_runners_text.present?
|
||||
= markdown(shared_runners_text, pipeline: 'plain_markdown')
|
||||
- else
|
||||
GitLab Runners do not offer secure isolation between projects that they do builds for. You are TRUSTING all GitLab users who can push code to project A, B or C to run shell scripts on the machine hosting runner X.
|
||||
%hr
|
||||
- if @project.shared_runners_enabled?
|
||||
= link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-warning', method: :post do
|
||||
|
||||
@@ -3,3 +3,6 @@
|
||||
|
||||
%p
|
||||
= link_to "See the affected projects in the GitLab admin panel", admin_namespaces_projects_url(last_repository_check_failed: 1)
|
||||
|
||||
%p
|
||||
You are receiving this message because you are a GitLab administrator for #{Gitlab.config.gitlab.url}.
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#{@message}.
|
||||
\
|
||||
View details: #{admin_namespaces_projects_url(last_repository_check_failed: 1)}
|
||||
|
||||
You are receiving this message because you are a GitLab administrator
|
||||
for #{Gitlab.config.gitlab.url}.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
= f.label :title, class: 'control-label'
|
||||
.col-sm-10
|
||||
= f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off',
|
||||
class: 'form-control pad js-gfm-input', required: true
|
||||
class: 'form-control pad', required: true
|
||||
|
||||
- if issuable.is_a?(MergeRequest)
|
||||
%p.help-block
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
|
||||
.selectbox.hide-collapsed
|
||||
= f.hidden_field 'assignee_id', value: issuable.assignee_id, id: 'issue_assignee_id'
|
||||
= dropdown_tag('Select assignee', options: { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), field_name: "#{issuable.to_ability_name}[assignee_id]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } })
|
||||
= dropdown_tag('Select assignee', options: { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_id]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } })
|
||||
|
||||
.block.milestone
|
||||
.sidebar-collapsed-icon
|
||||
@@ -118,6 +118,7 @@
|
||||
Manage labels
|
||||
- else
|
||||
View labels
|
||||
= dropdown_loading
|
||||
|
||||
= render "shared/issuable/participants", participants: issuable.participants(current_user)
|
||||
- if current_user
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
|
||||
- if assignee
|
||||
= link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: issuable.assignee_id, state: 'all' }),
|
||||
class: 'has-tooltip', data: { 'original-title' => "Assigned to #{sanitize(assignee.name)}", container: 'body' } do
|
||||
class: 'has-tooltip', title: "Assigned to #{assignee.name}", data: { container: 'body' } do
|
||||
- image_tag(avatar_icon(issuable.assignee, 16), class: "avatar s16", alt: '')
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
= content_for :meta_tags do
|
||||
= auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity")
|
||||
|
||||
= render 'shared/show_aside'
|
||||
|
||||
.user-profile
|
||||
.cover-block
|
||||
.cover-controls
|
||||
|
||||
@@ -39,7 +39,7 @@ class PostReceive
|
||||
end
|
||||
|
||||
if Gitlab::Git.tag_ref?(ref)
|
||||
GitTagPushService.new.execute(post_received.project, @user, oldrev, newrev, ref)
|
||||
GitTagPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute
|
||||
elsif Gitlab::Git.branch_ref?(ref)
|
||||
GitPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute
|
||||
end
|
||||
@@ -47,7 +47,7 @@ class PostReceive
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
|
||||
def log(message)
|
||||
Gitlab::GitLogger.error("POST-RECEIVE: #{message}")
|
||||
end
|
||||
|
||||
@@ -15,10 +15,10 @@ module RepositoryCheck
|
||||
private
|
||||
|
||||
def check(project)
|
||||
repositories = [project.repository]
|
||||
repositories << project.wiki.repository if project.wiki_enabled?
|
||||
# Use 'map do', not 'all? do', to prevent short-circuiting
|
||||
[project.repository, project.wiki.repository].map do |repository|
|
||||
git_fsck(repository.path_to_repo)
|
||||
end.all?
|
||||
repositories.map { |repository| git_fsck(repository.path_to_repo) }.all?
|
||||
end
|
||||
|
||||
def git_fsck(path)
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
class MigrateRepoSize < ActiveRecord::Migration
|
||||
def up
|
||||
Project.reset_column_information
|
||||
Project.find_each(batch_size: 500) do |project|
|
||||
project_data = execute('SELECT projects.id, namespaces.path AS namespace_path, projects.path AS project_path FROM projects LEFT JOIN namespaces ON projects.namespace_id = namespaces.id')
|
||||
|
||||
project_data.each do |project|
|
||||
id = project['id']
|
||||
namespace_path = project['namespace_path'] || ''
|
||||
path = File.join(Gitlab.config.gitlab_shell.repos_path, namespace_path, project['project_path'] + '.git')
|
||||
|
||||
begin
|
||||
if project.empty_repo?
|
||||
repo = Gitlab::Git::Repository.new(path)
|
||||
if repo.empty?
|
||||
print '-'
|
||||
else
|
||||
project.update_repository_size
|
||||
size = repo.size
|
||||
print '.'
|
||||
execute("UPDATE projects SET repository_size = #{size} WHERE id = #{id}")
|
||||
end
|
||||
rescue
|
||||
print 'F'
|
||||
rescue => e
|
||||
puts "\nFailed to update project #{id}: #{e}"
|
||||
end
|
||||
end
|
||||
puts 'Done'
|
||||
puts "\nDone"
|
||||
end
|
||||
|
||||
def down
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
class AddImportCredentialsToProjectImportData < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :project_import_data, :encrypted_credentials, :text
|
||||
add_column :project_import_data, :encrypted_credentials_iv, :string
|
||||
add_column :project_import_data, :encrypted_credentials_salt, :string
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,131 @@
|
||||
# Loops through old importer projects that kept a token/password in the import URL
|
||||
# and encrypts the credentials into a separate field in project#import_data
|
||||
# #down method not supported
|
||||
class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration
|
||||
|
||||
class ProjectImportDataFake
|
||||
extend AttrEncrypted
|
||||
attr_accessor :credentials
|
||||
attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base, marshal: true, encode: true, :mode => :per_attribute_iv_and_salt
|
||||
end
|
||||
|
||||
def up
|
||||
say("Encrypting and migrating project import credentials...")
|
||||
|
||||
# This should cover GitHub, GitLab, Bitbucket user:password, token@domain, and other similar URLs.
|
||||
in_transaction(message: "Projects including GitHub and GitLab projects with an unsecured URL.") { process_projects_with_wrong_url }
|
||||
|
||||
in_transaction(message: "Migrating Bitbucket credentials...") { process_project(import_type: 'bitbucket', credentials_keys: ['bb_session']) }
|
||||
|
||||
in_transaction(message: "Migrating FogBugz credentials...") { process_project(import_type: 'fogbugz', credentials_keys: ['fb_session']) }
|
||||
|
||||
end
|
||||
|
||||
def process_projects_with_wrong_url
|
||||
projects_with_wrong_import_url.each do |project|
|
||||
begin
|
||||
import_url = Gitlab::ImportUrl.new(project["import_url"])
|
||||
|
||||
update_import_url(import_url, project)
|
||||
update_import_data(import_url, project)
|
||||
rescue URI::InvalidURIError
|
||||
nullify_import_url(project)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def process_project(import_type:, credentials_keys: [])
|
||||
unencrypted_import_data(import_type: import_type).each do |data|
|
||||
replace_data_credentials(data, credentials_keys)
|
||||
end
|
||||
end
|
||||
|
||||
def replace_data_credentials(data, credentials_keys)
|
||||
data_hash = JSON.load(data['data']) if data['data']
|
||||
unless data_hash.blank?
|
||||
encrypted_data_hash = encrypt_data(data_hash, credentials_keys)
|
||||
unencrypted_data = data_hash.empty? ? ' NULL ' : quote(data_hash.to_json)
|
||||
update_with_encrypted_data(encrypted_data_hash, data['id'], unencrypted_data)
|
||||
end
|
||||
end
|
||||
|
||||
def encrypt_data(data_hash, credentials_keys)
|
||||
new_data_hash = {}
|
||||
credentials_keys.each do |key|
|
||||
new_data_hash[key.to_sym] = data_hash.delete(key) if data_hash[key]
|
||||
end
|
||||
new_data_hash.deep_symbolize_keys
|
||||
end
|
||||
|
||||
def in_transaction(message:)
|
||||
say_with_time(message) do
|
||||
ActiveRecord::Base.transaction do
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_import_data(import_url, project)
|
||||
fake_import_data = ProjectImportDataFake.new
|
||||
fake_import_data.credentials = import_url.credentials
|
||||
import_data_id = project['import_data_id']
|
||||
if import_data_id
|
||||
execute(update_import_data_sql(import_data_id, fake_import_data))
|
||||
else
|
||||
execute(insert_import_data_sql(project['id'], fake_import_data))
|
||||
end
|
||||
end
|
||||
|
||||
def update_with_encrypted_data(data_hash, import_data_id, unencrypted_data = ' NULL ')
|
||||
fake_import_data = ProjectImportDataFake.new
|
||||
fake_import_data.credentials = data_hash
|
||||
execute(update_import_data_sql(import_data_id, fake_import_data, unencrypted_data))
|
||||
end
|
||||
|
||||
def update_import_url(import_url, project)
|
||||
execute("UPDATE projects SET import_url = #{quote(import_url.sanitized_url)} WHERE id = #{project['id']}")
|
||||
end
|
||||
|
||||
def nullify_import_url(project)
|
||||
execute("UPDATE projects SET import_url = NULL WHERE id = #{project['id']}")
|
||||
end
|
||||
|
||||
def insert_import_data_sql(project_id, fake_import_data)
|
||||
%(
|
||||
INSERT INTO project_import_data
|
||||
(encrypted_credentials,
|
||||
project_id,
|
||||
encrypted_credentials_iv,
|
||||
encrypted_credentials_salt)
|
||||
VALUES ( #{quote(fake_import_data.encrypted_credentials)},
|
||||
'#{project_id}',
|
||||
#{quote(fake_import_data.encrypted_credentials_iv)},
|
||||
#{quote(fake_import_data.encrypted_credentials_salt)})
|
||||
).squish
|
||||
end
|
||||
|
||||
def update_import_data_sql(id, fake_import_data, data = 'NULL')
|
||||
%(
|
||||
UPDATE project_import_data
|
||||
SET encrypted_credentials = #{quote(fake_import_data.encrypted_credentials)},
|
||||
encrypted_credentials_iv = #{quote(fake_import_data.encrypted_credentials_iv)},
|
||||
encrypted_credentials_salt = #{quote(fake_import_data.encrypted_credentials_salt)},
|
||||
data = #{data}
|
||||
WHERE id = '#{id}'
|
||||
).squish
|
||||
end
|
||||
|
||||
#GitHub projects with token, and any user:password@ based URL
|
||||
def projects_with_wrong_import_url
|
||||
select_all("SELECT p.id, p.import_url, i.id as import_data_id FROM projects p LEFT JOIN project_import_data i on p.id = i.project_id WHERE p.import_url <> '' AND p.import_url LIKE '%//%@%'")
|
||||
end
|
||||
|
||||
# All imports with data for import_type
|
||||
def unencrypted_import_data(import_type: )
|
||||
select_all("SELECT i.id, p.import_url, i.data FROM projects p INNER JOIN project_import_data i ON p.id = i.project_id WHERE p.import_url <> '' AND p.import_type = '#{import_type}' ")
|
||||
end
|
||||
|
||||
def quote(value)
|
||||
ActiveRecord::Base.connection.quote(value)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddSharedRunnersTextToApplicationSettings < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :application_settings, :shared_runners_text, :text
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddMetricsPacketSize < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :application_settings, :metrics_packet_size, :integer, default: 1
|
||||
end
|
||||
end
|
||||
+24
-19
@@ -11,7 +11,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20160412140240) do
|
||||
ActiveRecord::Schema.define(version: 20160419120017) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
@@ -78,6 +78,8 @@ ActiveRecord::Schema.define(version: 20160412140240) do
|
||||
t.boolean "email_author_in_body", default: false
|
||||
t.integer "default_group_visibility"
|
||||
t.boolean "repository_checks_enabled", default: true
|
||||
t.integer "metrics_packet_size", default: 1
|
||||
t.text "shared_runners_text"
|
||||
end
|
||||
|
||||
create_table "audit_events", force: :cascade do |t|
|
||||
@@ -704,6 +706,9 @@ ActiveRecord::Schema.define(version: 20160412140240) do
|
||||
create_table "project_import_data", force: :cascade do |t|
|
||||
t.integer "project_id"
|
||||
t.text "data"
|
||||
t.text "encrypted_credentials"
|
||||
t.text "encrypted_credentials_iv"
|
||||
t.text "encrypted_credentials_salt"
|
||||
end
|
||||
|
||||
create_table "projects", force: :cascade do |t|
|
||||
@@ -713,37 +718,37 @@ ActiveRecord::Schema.define(version: 20160412140240) do
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.integer "creator_id"
|
||||
t.boolean "issues_enabled", default: true, null: false
|
||||
t.boolean "wall_enabled", default: true, null: false
|
||||
t.boolean "merge_requests_enabled", default: true, null: false
|
||||
t.boolean "wiki_enabled", default: true, null: false
|
||||
t.boolean "issues_enabled", default: true, null: false
|
||||
t.boolean "wall_enabled", default: true, null: false
|
||||
t.boolean "merge_requests_enabled", default: true, null: false
|
||||
t.boolean "wiki_enabled", default: true, null: false
|
||||
t.integer "namespace_id"
|
||||
t.string "issues_tracker", default: "gitlab", null: false
|
||||
t.string "issues_tracker", default: "gitlab", null: false
|
||||
t.string "issues_tracker_id"
|
||||
t.boolean "snippets_enabled", default: true, null: false
|
||||
t.boolean "snippets_enabled", default: true, null: false
|
||||
t.datetime "last_activity_at"
|
||||
t.string "import_url"
|
||||
t.integer "visibility_level", default: 0, null: false
|
||||
t.boolean "archived", default: false, null: false
|
||||
t.integer "visibility_level", default: 0, null: false
|
||||
t.boolean "archived", default: false, null: false
|
||||
t.string "avatar"
|
||||
t.string "import_status"
|
||||
t.float "repository_size", default: 0.0
|
||||
t.integer "star_count", default: 0, null: false
|
||||
t.float "repository_size", default: 0.0
|
||||
t.integer "star_count", default: 0, null: false
|
||||
t.string "import_type"
|
||||
t.string "import_source"
|
||||
t.integer "commit_count", default: 0
|
||||
t.integer "commit_count", default: 0
|
||||
t.text "import_error"
|
||||
t.integer "ci_id"
|
||||
t.boolean "builds_enabled", default: true, null: false
|
||||
t.boolean "shared_runners_enabled", default: true, null: false
|
||||
t.boolean "builds_enabled", default: true, null: false
|
||||
t.boolean "shared_runners_enabled", default: true, null: false
|
||||
t.string "runners_token"
|
||||
t.string "build_coverage_regex"
|
||||
t.boolean "build_allow_git_fetch", default: true, null: false
|
||||
t.integer "build_timeout", default: 3600, null: false
|
||||
t.boolean "pending_delete", default: false
|
||||
t.boolean "public_builds", default: true, null: false
|
||||
t.boolean "build_allow_git_fetch", default: true, null: false
|
||||
t.integer "build_timeout", default: 3600, null: false
|
||||
t.boolean "pending_delete", default: false
|
||||
t.boolean "public_builds", default: true, null: false
|
||||
t.string "main_language"
|
||||
t.integer "pushes_since_gc", default: 0
|
||||
t.integer "pushes_since_gc", default: 0
|
||||
t.boolean "last_repository_check_failed"
|
||||
t.datetime "last_repository_check_at"
|
||||
end
|
||||
|
||||
@@ -13,7 +13,7 @@ GitLab offers a [continuous integration][ci] service. If you
|
||||
and configure your GitLab project to use a [Runner], then each merge request or
|
||||
push triggers a build.
|
||||
|
||||
The `.gitlab-ci.yml` file tells the GitLab runner what do to. By default it
|
||||
The `.gitlab-ci.yml` file tells the GitLab runner what to do. By default it
|
||||
runs three [stages]: `build`, `test`, and `deploy`.
|
||||
|
||||
If everything runs OK (no non-zero return values), you'll get a nice green
|
||||
|
||||
@@ -7,6 +7,10 @@ through the coordinator API of GitLab CI.
|
||||
A runner can be specific to a certain project or serve any project
|
||||
in GitLab CI. A runner that serves all projects is called a shared runner.
|
||||
|
||||
Ideally, GitLab Runner should not be installed on the same machine as GitLab.
|
||||
Read the [requirements documentation](../../install/requirements.md#gitlab-runner)
|
||||
for more information.
|
||||
|
||||
## Shared vs. Specific Runners
|
||||
|
||||
A runner that is specific only runs for the specified project. A shared runner
|
||||
@@ -140,7 +144,7 @@ to it. This means that if you have shared runners setup for a project and
|
||||
someone forks that project, the shared runners will also serve jobs of this
|
||||
project.
|
||||
|
||||
# Attack vectors in runners
|
||||
## Attack vectors in Runners
|
||||
|
||||
Mentioned briefly earlier, but the following things of runners can be exploited.
|
||||
We're always looking for contributions that can mitigate these [Security Considerations](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/security/index.md).
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user