Merge remote-tracking branch 'origin/master' into label-dropdown-fix
# Conflicts: # app/views/shared/issuable/_sidebar.html.haml
This commit is contained in:
commit
7b6fe0e259
57
CHANGELOG
57
CHANGELOG
|
@ -1,26 +1,38 @@
|
|||
Please view this file on the master branch, on stable branches it's out of date.
|
||||
|
||||
v 8.8.0 (unreleased)
|
||||
|
||||
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)
|
||||
- Add support to cherry-pick any commit into any branch in the web interface (Minqi Pan)
|
||||
- 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)
|
||||
|
@ -28,6 +40,8 @@ v 8.7.0 (unreleased)
|
|||
- Fix a bug whith trailing slash in teamcity_url (Charles May)
|
||||
- Allow back dating on issues when created or updated through the API
|
||||
- Allow back dating on issue notes when created through the API
|
||||
- Propose license template when creating a new LICENSE file
|
||||
- API: Expose /licenses and /licenses/:key
|
||||
- Fix avatar stretching by providing a cropping feature
|
||||
- API: Expose `subscribed` for issues and merge requests (Robert Schilling)
|
||||
- Allow SAML to handle external users based on user's information !3530
|
||||
|
@ -45,6 +59,7 @@ v 8.7.0 (unreleased)
|
|||
- 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
|
||||
|
@ -56,6 +71,7 @@ v 8.7.0 (unreleased)
|
|||
- 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)
|
||||
|
@ -64,19 +80,46 @@ 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)
|
||||
- Add Slack notifications when Wiki is edited (Sebastian Klier)
|
||||
- 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
|
||||
- Properly format all merge request references with ! rather than # !3740 (Ben Bodenmiller)
|
||||
- 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
|
||||
- Fix GitHub project's link in the import page when provider has a custom URL
|
||||
- Add RAW build trace output and button on build page
|
||||
- Add incremental build trace update into CI API
|
||||
|
||||
v 8.6.7 (unreleased)
|
||||
- Fix persistent XSS vulnerability in `commit_person_link` helper
|
||||
- Fix persistent XSS vulnerability in Label and Milestone dropdowns
|
||||
- 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
|
||||
|
@ -276,7 +319,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)
|
||||
|
|
5
Gemfile
5
Gemfile
|
@ -190,6 +190,9 @@ gem 'babosa', '~> 1.0.2'
|
|||
# Sanitizes SVG input
|
||||
gem "loofah", "~> 2.0.3"
|
||||
|
||||
# Working with license
|
||||
gem 'licensee', '~> 8.0.0'
|
||||
|
||||
# Protect against bruteforcing
|
||||
gem "rack-attack", '~> 4.3.1'
|
||||
|
||||
|
@ -315,7 +318,7 @@ end
|
|||
|
||||
gem "newrelic_rpm", '~> 3.14'
|
||||
|
||||
gem 'octokit', '~> 3.8.0'
|
||||
gem 'octokit', '~> 4.3.0'
|
||||
|
||||
gem "mail_room", "~> 0.6.1"
|
||||
|
||||
|
|
13
Gemfile.lock
13
Gemfile.lock
|
@ -452,6 +452,8 @@ GEM
|
|||
addressable (~> 2.3)
|
||||
letter_opener (1.1.2)
|
||||
launchy (~> 2.2)
|
||||
licensee (8.0.0)
|
||||
rugged (>= 0.24b)
|
||||
listen (3.0.5)
|
||||
rb-fsevent (>= 0.9.3)
|
||||
rb-inotify (>= 0.9)
|
||||
|
@ -485,8 +487,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 +714,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)
|
||||
|
@ -957,6 +959,7 @@ DEPENDENCIES
|
|||
jquery-ui-rails (~> 5.0.0)
|
||||
kaminari (~> 0.16.3)
|
||||
letter_opener (~> 1.1.2)
|
||||
licensee (~> 8.0.0)
|
||||
loofah (~> 2.0.3)
|
||||
mail_room (~> 0.6.1)
|
||||
method_source (~> 0.8)
|
||||
|
@ -968,7 +971,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)
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
group_projects_path: "/api/:version/groups/:id/projects.json"
|
||||
projects_path: "/api/:version/projects.json"
|
||||
labels_path: "/api/:version/projects/:id/labels"
|
||||
license_path: "/api/:version/licenses/:key"
|
||||
|
||||
group: (group_id, callback) ->
|
||||
url = Api.buildUrl(Api.group_path)
|
||||
|
@ -92,6 +93,16 @@
|
|||
).done (projects) ->
|
||||
callback(projects)
|
||||
|
||||
# Return text for a specific license
|
||||
licenseText: (key, data, callback) ->
|
||||
url = Api.buildUrl(Api.license_path).replace(':key', key)
|
||||
|
||||
$.ajax(
|
||||
url: url
|
||||
data: data
|
||||
).done (license) ->
|
||||
callback(license)
|
||||
|
||||
buildUrl: (url) ->
|
||||
url = gon.relative_url_root + url if gon.relative_url_root?
|
||||
return url.replace(':version', gon.api_version)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
class @BlobLicenseSelector
|
||||
licenseRegex: /^(.+\/)?(licen[sc]e|copying)($|\.)/i
|
||||
|
||||
constructor: (editor) ->
|
||||
@$licenseSelector = $('.js-license-selector')
|
||||
$fileNameInput = $('#file_name')
|
||||
|
||||
initialFileNameValue = if $fileNameInput.length
|
||||
$fileNameInput.val()
|
||||
else if $('.editor-file-name').length
|
||||
$('.editor-file-name').text().trim()
|
||||
|
||||
@toggleLicenseSelector(initialFileNameValue)
|
||||
|
||||
if $fileNameInput
|
||||
$fileNameInput.on 'keyup blur', (e) =>
|
||||
@toggleLicenseSelector($(e.target).val())
|
||||
|
||||
$('select.license-select').on 'change', (e) ->
|
||||
data =
|
||||
project: $(this).data('project')
|
||||
fullname: $(this).data('fullname')
|
||||
Api.licenseText $(this).val(), data, (license) ->
|
||||
editor.setValue(license.content, -1)
|
||||
|
||||
toggleLicenseSelector: (fileName) =>
|
||||
if @licenseRegex.test(fileName)
|
||||
@$licenseSelector.show()
|
||||
else
|
||||
@$licenseSelector.hide()
|
|
@ -1,44 +1,39 @@
|
|||
class @EditBlob
|
||||
constructor: (assets_path, mode)->
|
||||
ace.config.set "modePath", assets_path + '/ace'
|
||||
constructor: (assets_path, ace_mode = null) ->
|
||||
ace.config.set "modePath", "#{assets_path}/ace"
|
||||
ace.config.loadModule "ace/ext/searchbox"
|
||||
if mode
|
||||
ace_mode = mode
|
||||
editor = ace.edit("editor")
|
||||
editor.focus()
|
||||
@editor = editor
|
||||
|
||||
if ace_mode
|
||||
editor.getSession().setMode "ace/mode/" + ace_mode
|
||||
@editor = ace.edit("editor")
|
||||
@editor.focus()
|
||||
@editor.getSession().setMode "ace/mode/#{ace_mode}" if ace_mode
|
||||
|
||||
# Before a form submission, move the content from the Ace editor into the
|
||||
# submitted textarea
|
||||
$('form').submit ->
|
||||
$("#file-content").val(editor.getValue())
|
||||
$('form').submit =>
|
||||
$("#file-content").val(@editor.getValue())
|
||||
|
||||
editModePanes = $(".js-edit-mode-pane")
|
||||
editModeLinks = $(".js-edit-mode a")
|
||||
editModeLinks.click (event) ->
|
||||
event.preventDefault()
|
||||
currentLink = $(this)
|
||||
paneId = currentLink.attr("href")
|
||||
currentPane = editModePanes.filter(paneId)
|
||||
editModeLinks.parent().removeClass "active hover"
|
||||
currentLink.parent().addClass "active hover"
|
||||
editModePanes.hide()
|
||||
if paneId is "#preview"
|
||||
currentPane.fadeIn 200
|
||||
$.post currentLink.data("preview-url"),
|
||||
content: editor.getValue()
|
||||
, (response) ->
|
||||
currentPane.empty().append response
|
||||
currentPane.syntaxHighlight()
|
||||
return
|
||||
@initModePanesAndLinks()
|
||||
new BlobLicenseSelector(@editor)
|
||||
|
||||
else
|
||||
currentPane.fadeIn 200
|
||||
editor.focus()
|
||||
return
|
||||
initModePanesAndLinks: ->
|
||||
@$editModePanes = $(".js-edit-mode-pane")
|
||||
@$editModeLinks = $(".js-edit-mode a")
|
||||
@$editModeLinks.click @editModeLinkClickHandler
|
||||
|
||||
editor: ->
|
||||
return @editor
|
||||
editModeLinkClickHandler: (event) =>
|
||||
event.preventDefault()
|
||||
currentLink = $(event.target)
|
||||
paneId = currentLink.attr("href")
|
||||
currentPane = @$editModePanes.filter(paneId)
|
||||
@$editModeLinks.parent().removeClass "active hover"
|
||||
currentLink.parent().addClass "active hover"
|
||||
@$editModePanes.hide()
|
||||
currentPane.fadeIn 200
|
||||
if paneId is "#preview"
|
||||
$.post currentLink.data("preview-url"),
|
||||
content: @editor.getValue()
|
||||
, (response) ->
|
||||
currentPane.empty().append response
|
||||
currentPane.syntaxHighlight()
|
||||
|
||||
else
|
||||
@editor.focus()
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
class @NewBlob
|
||||
constructor: (assets_path, mode)->
|
||||
ace.config.set "modePath", assets_path + '/ace'
|
||||
ace.config.loadModule "ace/ext/searchbox"
|
||||
if mode
|
||||
ace_mode = mode
|
||||
editor = ace.edit("editor")
|
||||
editor.focus()
|
||||
@editor = editor
|
||||
|
||||
if ace_mode
|
||||
editor.getSession().setMode "ace/mode/" + ace_mode
|
||||
|
||||
# Before a form submission, move the content from the Ace editor into the
|
||||
# submitted textarea
|
||||
$('form').submit ->
|
||||
$("#file-content").val(editor.getValue())
|
||||
|
||||
editor: ->
|
||||
return @editor
|
|
@ -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 (=>
|
||||
|
|
|
@ -9,21 +9,30 @@ class @IssuableContext
|
|||
$(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
|
||||
$(this).submit()
|
||||
|
||||
$(document).off("click", ".edit-link").on "click",".edit-link", (e) ->
|
||||
$block = $(@).parents('.block')
|
||||
$selectbox = $block.find('.selectbox')
|
||||
if $selectbox.is(':visible')
|
||||
$selectbox.hide()
|
||||
$block.find('.value').show()
|
||||
else
|
||||
$selectbox.show()
|
||||
$block.find('.value').hide()
|
||||
$(document)
|
||||
.off 'click', '.dropdown-content a'
|
||||
.on 'click', '.dropdown-content a', (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
$(document)
|
||||
.off 'click', '.edit-link'
|
||||
.on 'click', '.edit-link', (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
$block = $(@).parents('.block')
|
||||
$selectbox = $block.find('.selectbox')
|
||||
if $selectbox.is(':visible')
|
||||
$selectbox.hide()
|
||||
$block.find('.value').show()
|
||||
else
|
||||
$selectbox.show()
|
||||
$block.find('.value').hide()
|
||||
|
||||
if $selectbox.is(':visible')
|
||||
setTimeout ->
|
||||
$block.find('.dropdown-menu-toggle').trigger 'click'
|
||||
, 0
|
||||
|
||||
if $selectbox.is(':visible')
|
||||
setTimeout (->
|
||||
$block.find('.dropdown-menu-toggle').trigger 'click'
|
||||
), 0
|
||||
|
||||
|
||||
$(".right-sidebar").niceScroll()
|
||||
|
||||
|
|
|
@ -29,13 +29,13 @@ class @LabelsSelect
|
|||
if issueUpdateURL
|
||||
labelHTMLTemplate = _.template(
|
||||
'<% _.each(labels, function(label){ %>
|
||||
<a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name=<%= label.title %>">
|
||||
<span class="label has-tooltip color-label" title="<%= label.description %>" style="background-color: <%= label.color %>;">
|
||||
<%= label.title %>
|
||||
<a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name=<%= _.escape(label.title) %>">
|
||||
<span class="label has-tooltip color-label" title="<%= _.escape(label.description) %>" style="background-color: <%= label.color %>;">
|
||||
<%= _.escape(label.title) %>
|
||||
</span>
|
||||
</a>
|
||||
<% }); %>'
|
||||
);
|
||||
)
|
||||
labelNoneHTMLTemplate = _.template('<div class="light">None</div>')
|
||||
|
||||
if newLabelField.length
|
||||
|
@ -191,7 +191,7 @@ class @LabelsSelect
|
|||
"<li>
|
||||
<a href='#' class='#{selectedClass}'>
|
||||
#{color}
|
||||
#{label.title}
|
||||
#{_.escape(label.title)}
|
||||
</a>
|
||||
</li>"
|
||||
filterable: true
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -24,7 +24,7 @@ class @MilestoneSelect
|
|||
|
||||
if issueUpdateURL
|
||||
milestoneLinkTemplate = _.template(
|
||||
'<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>"><%= title %></a>'
|
||||
'<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>"><%= _.escape(title) %></a>'
|
||||
)
|
||||
|
||||
milestoneLinkNoneTemplate = '<div class="light">None</div>'
|
||||
|
@ -71,7 +71,7 @@ class @MilestoneSelect
|
|||
defaultLabel
|
||||
fieldName: $dropdown.data('field-name')
|
||||
text: (milestone) ->
|
||||
milestone.title
|
||||
_.escape(milestone.title)
|
||||
id: (milestone) ->
|
||||
if !useId
|
||||
milestone.name
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -26,6 +26,10 @@
|
|||
line-height: 42px;
|
||||
padding-top: 7px;
|
||||
padding-bottom: 7px;
|
||||
|
||||
.pull-right {
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-ref {
|
||||
|
@ -53,4 +57,9 @@
|
|||
.select2 {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.encoding-selector,
|
||||
.license-selector {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -277,10 +277,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.btn-default.gutter-toggle {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.detail-page-description {
|
||||
small {
|
||||
color: $gray-darkest;
|
||||
|
@ -326,3 +322,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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
.note-textarea {
|
||||
display: block;
|
||||
padding: 10px 0;
|
||||
color: $gl-gray;
|
||||
font-family: $regular_font;
|
||||
border: 0;
|
||||
|
||||
|
@ -61,11 +62,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
|
||||
|
|
|
@ -51,6 +51,7 @@ class HelpController < ApplicationController
|
|||
end
|
||||
|
||||
def ui
|
||||
@user = User.new(id: 0, name: 'John Doe', username: '@johndoe')
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
class Projects::BuildsController < Projects::ApplicationController
|
||||
before_action :build, except: [:index, :cancel_all]
|
||||
before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry]
|
||||
before_action :authorize_update_build!, except: [:index, :show, :status]
|
||||
before_action :authorize_update_build!, except: [:index, :show, :status, :raw]
|
||||
layout 'project'
|
||||
|
||||
def index
|
||||
|
@ -62,6 +62,14 @@ class Projects::BuildsController < Projects::ApplicationController
|
|||
notice: "Build has been sucessfully erased!"
|
||||
end
|
||||
|
||||
def raw
|
||||
if @build.has_trace?
|
||||
send_file @build.path_to_trace, type: 'text/plain; charset=utf-8', disposition: 'inline'
|
||||
else
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build
|
||||
|
|
|
@ -12,7 +12,7 @@ class Projects::CommitController < Projects::ApplicationController
|
|||
before_action :authorize_read_commit_status!, only: [:builds]
|
||||
before_action :commit
|
||||
before_action :define_show_vars, only: [:show, :builds]
|
||||
before_action :authorize_edit_tree!, only: [:revert]
|
||||
before_action :authorize_edit_tree!, only: [:revert, :cherry_pick]
|
||||
|
||||
def show
|
||||
apply_diff_view_cookie!
|
||||
|
@ -60,27 +60,32 @@ class Projects::CommitController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def revert
|
||||
assign_revert_commit_vars
|
||||
assign_change_commit_vars(@commit.revert_branch_name)
|
||||
|
||||
return render_404 if @target_branch.blank?
|
||||
|
||||
create_commit(Commits::RevertService, success_notice: "The #{revert_type_title} has been successfully reverted.",
|
||||
success_path: successful_revert_path, failure_path: failed_revert_path)
|
||||
create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title} has been successfully reverted.",
|
||||
success_path: successful_change_path, failure_path: failed_change_path)
|
||||
end
|
||||
|
||||
def cherry_pick
|
||||
assign_change_commit_vars(@commit.cherry_pick_branch_name)
|
||||
|
||||
return render_404 if @target_branch.blank?
|
||||
|
||||
create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title} has been successfully cherry-picked.",
|
||||
success_path: successful_change_path, failure_path: failed_change_path)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def revert_type_title
|
||||
@commit.merged_merge_request ? 'merge request' : 'commit'
|
||||
end
|
||||
|
||||
def successful_revert_path
|
||||
def successful_change_path
|
||||
return referenced_merge_request_url if @commit.merged_merge_request
|
||||
|
||||
namespace_project_commits_url(@project.namespace, @project, @target_branch)
|
||||
end
|
||||
|
||||
def failed_revert_path
|
||||
def failed_change_path
|
||||
return referenced_merge_request_url if @commit.merged_merge_request
|
||||
|
||||
namespace_project_commit_url(@project.namespace, @project, params[:id])
|
||||
|
@ -111,14 +116,13 @@ class Projects::CommitController < Projects::ApplicationController
|
|||
@statuses = ci_commit.statuses if ci_commit
|
||||
end
|
||||
|
||||
def assign_revert_commit_vars
|
||||
def assign_change_commit_vars(mr_source_branch)
|
||||
@commit = project.commit(params[:id])
|
||||
@target_branch = params[:target_branch]
|
||||
@mr_source_branch = @commit.revert_branch_name
|
||||
@mr_source_branch = mr_source_branch
|
||||
@mr_target_branch = @target_branch
|
||||
@commit_params = {
|
||||
commit: @commit,
|
||||
revert_type_title: revert_type_title,
|
||||
create_merge_request: params[:create_merge_request].present? || different_project?
|
||||
}
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -6,7 +6,7 @@ class Projects::ServicesController < Projects::ApplicationController
|
|||
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
|
||||
:colorize_messages, :channels,
|
||||
:push_events, :issues_events, :merge_requests_events, :tag_push_events,
|
||||
:note_events, :build_events,
|
||||
:note_events, :build_events, :wiki_page_events,
|
||||
:notify_only_broken_builds, :add_pusher,
|
||||
:send_from_committer_email, :disable_diffs, :external_wiki_url,
|
||||
:notify, :color,
|
||||
|
|
|
@ -44,7 +44,7 @@ class Projects::WikisController < Projects::ApplicationController
|
|||
|
||||
return render('empty') unless can?(current_user, :create_wiki, @project)
|
||||
|
||||
if @page.update(content, format, message)
|
||||
if @page = WikiPages::UpdateService.new(@project, current_user, wiki_params).execute(@page)
|
||||
redirect_to(
|
||||
namespace_project_wiki_path(@project.namespace, @project, @page),
|
||||
notice: 'Wiki was successfully updated.'
|
||||
|
@ -55,9 +55,9 @@ class Projects::WikisController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def create
|
||||
@page = WikiPage.new(@project_wiki)
|
||||
@page = WikiPages::CreateService.new(@project, current_user, wiki_params).execute
|
||||
|
||||
if @page.create(wiki_params)
|
||||
if @page.persisted?
|
||||
redirect_to(
|
||||
namespace_project_wiki_path(@project.namespace, @project, @page),
|
||||
notice: 'Wiki was successfully updated.'
|
||||
|
@ -122,15 +122,4 @@ class Projects::WikisController < Projects::ApplicationController
|
|||
params[:wiki].slice(:title, :content, :format, :message)
|
||||
end
|
||||
|
||||
def content
|
||||
params[:wiki][:content]
|
||||
end
|
||||
|
||||
def format
|
||||
params[:wiki][:format]
|
||||
end
|
||||
|
||||
def message
|
||||
params[:wiki][:message]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -173,4 +173,15 @@ module BlobHelper
|
|||
response.etag = @blob.id
|
||||
!stale
|
||||
end
|
||||
|
||||
def licenses_for_select
|
||||
return @licenses_for_select if defined?(@licenses_for_select)
|
||||
|
||||
licenses = Licensee::License.all
|
||||
|
||||
@licenses_for_select = {
|
||||
Popular: licenses.select(&:featured).map { |license| [license.name, license.key] },
|
||||
Other: licenses.reject(&:featured).map { |license| [license.name, license.key] }
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -126,12 +126,10 @@ module CommitsHelper
|
|||
def revert_commit_link(commit, continue_to_path, btn_class: nil)
|
||||
return unless current_user
|
||||
|
||||
tooltip = "Revert this #{revert_commit_type(commit)} in a new merge request"
|
||||
tooltip = "Revert this #{commit.change_type_title} in a new merge request"
|
||||
|
||||
if can_collaborate_with_project?
|
||||
content_tag :span, 'data-toggle' => 'modal', 'data-target' => '#modal-revert-commit' do
|
||||
link_to 'Revert', '#modal-revert-commit', 'data-toggle' => 'tooltip', 'data-container' => 'body', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class}"
|
||||
end
|
||||
link_to 'Revert', '#modal-revert-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class} has-tooltip"
|
||||
elsif can?(current_user, :fork_project, @project)
|
||||
continue_params = {
|
||||
to: continue_to_path,
|
||||
|
@ -146,11 +144,24 @@ module CommitsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def revert_commit_type(commit)
|
||||
if commit.merged_merge_request
|
||||
'merge request'
|
||||
else
|
||||
'commit'
|
||||
def cherry_pick_commit_link(commit, continue_to_path, btn_class: nil)
|
||||
return unless current_user
|
||||
|
||||
tooltip = "Cherry-pick this #{commit.change_type_title} in a new merge request"
|
||||
|
||||
if can_collaborate_with_project?
|
||||
link_to 'Cherry-pick', '#modal-cherry-pick-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class} has-tooltip"
|
||||
elsif can?(current_user, :fork_project, @project)
|
||||
continue_params = {
|
||||
to: continue_to_path,
|
||||
notice: edit_in_new_fork_notice + ' Try to cherry-pick this commit again.',
|
||||
notice_now: edit_in_new_fork_notice_now
|
||||
}
|
||||
fork_path = namespace_project_forks_path(@project.namespace, @project,
|
||||
namespace_key: current_user.namespace.id,
|
||||
continue: continue_params)
|
||||
|
||||
link_to 'Cherry-pick', fork_path, class: 'btn btn-grouped btn-close', method: :post, 'data-toggle' => 'tooltip', 'data-container' => 'body', title: tooltip
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -183,7 +194,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?
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
module ImportHelper
|
||||
def github_project_link(path_with_namespace)
|
||||
link_to path_with_namespace, github_project_url(path_with_namespace), target: '_blank'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def github_project_url(path_with_namespace)
|
||||
"#{github_root_url}/#{path_with_namespace}"
|
||||
end
|
||||
|
||||
def github_root_url
|
||||
return @github_url if defined?(@github_url)
|
||||
|
||||
provider = Gitlab.config.omniauth.providers.find { |p| p.name == 'github' }
|
||||
@github_url = provider.fetch('url', 'https://github.com') if provider
|
||||
end
|
||||
end
|
|
@ -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
|
||||
|
||||
|
@ -216,40 +216,14 @@ module ProjectsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def add_contribution_guide_path(project)
|
||||
if project && !project.repository.contribution_guide
|
||||
namespace_project_new_blob_path(
|
||||
project.namespace,
|
||||
project,
|
||||
project.default_branch,
|
||||
file_name: "CONTRIBUTING.md",
|
||||
commit_message: "Add contribution guide"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def add_changelog_path(project)
|
||||
if project && !project.repository.changelog
|
||||
namespace_project_new_blob_path(
|
||||
project.namespace,
|
||||
project,
|
||||
project.default_branch,
|
||||
file_name: "CHANGELOG",
|
||||
commit_message: "Add changelog"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def add_license_path(project)
|
||||
if project && !project.repository.license
|
||||
namespace_project_new_blob_path(
|
||||
project.namespace,
|
||||
project,
|
||||
project.default_branch,
|
||||
file_name: "LICENSE",
|
||||
commit_message: "Add license"
|
||||
)
|
||||
end
|
||||
def add_special_file_path(project, file_name:, commit_message: nil)
|
||||
namespace_project_new_blob_path(
|
||||
project.namespace,
|
||||
project,
|
||||
project.default_branch || 'master',
|
||||
file_name: file_name,
|
||||
commit_message: commit_message || "Add #{file_name.downcase}"
|
||||
)
|
||||
end
|
||||
|
||||
def contribution_guide_path(project)
|
||||
|
@ -272,7 +246,7 @@ module ProjectsHelper
|
|||
end
|
||||
|
||||
def license_path(project)
|
||||
filename_path(project, :license)
|
||||
filename_path(project, :license_blob)
|
||||
end
|
||||
|
||||
def version_path(project)
|
||||
|
@ -306,6 +280,13 @@ module ProjectsHelper
|
|||
namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'README.md')
|
||||
end
|
||||
|
||||
def new_license_path
|
||||
ref = @repository.root_ref if @repository
|
||||
ref ||= 'master'
|
||||
|
||||
namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'LICENSE')
|
||||
end
|
||||
|
||||
def last_push_event
|
||||
if current_user
|
||||
current_user.recent_push(@project.id)
|
||||
|
@ -335,6 +316,12 @@ module ProjectsHelper
|
|||
@ref || @repository.try(:root_ref)
|
||||
end
|
||||
|
||||
def license_short_name(project)
|
||||
license = Licensee::License.new(project.repository.license_key)
|
||||
|
||||
license.nickname || license.name
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filename_path(project, filename)
|
||||
|
|
|
@ -66,7 +66,7 @@ module TreeHelper
|
|||
ref
|
||||
else
|
||||
project = tree_edit_project(project)
|
||||
project.repository.next_patch_branch
|
||||
project.repository.next_branch('patch')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ module Emails
|
|||
{
|
||||
from: sender(sender_id),
|
||||
to: recipient(recipient_id),
|
||||
subject: subject("#{@merge_request.title} (##{@merge_request.iid})")
|
||||
subject: subject("#{@merge_request.title} (#{@merge_request.to_reference})")
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -38,7 +38,7 @@ module Emails
|
|||
{
|
||||
from: sender(@note.author_id),
|
||||
to: recipient(recipient_id),
|
||||
subject: subject("#{@note.noteable.title} (##{@note.noteable.iid})")
|
||||
subject: subject("#{@note.noteable.title} (#{@note.noteable.to_reference})")
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ class RepositoryCheckMailer < BaseMailer
|
|||
|
||||
mail(
|
||||
to: User.admins.pluck(:email),
|
||||
subject: @message
|
||||
subject: "GitLab Admin | #{@message}"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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}",
|
||||
|
|
|
@ -230,12 +230,33 @@ module Ci
|
|||
end
|
||||
end
|
||||
|
||||
def trace_length
|
||||
if raw_trace
|
||||
raw_trace.length
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def trace=(trace)
|
||||
recreate_trace_dir
|
||||
File.write(path_to_trace, trace)
|
||||
end
|
||||
|
||||
def recreate_trace_dir
|
||||
unless Dir.exists?(dir_to_trace)
|
||||
FileUtils.mkdir_p(dir_to_trace)
|
||||
end
|
||||
end
|
||||
private :recreate_trace_dir
|
||||
|
||||
File.write(path_to_trace, trace)
|
||||
def append_trace(trace_part, offset)
|
||||
recreate_trace_dir
|
||||
|
||||
File.truncate(path_to_trace, offset) if File.exist?(path_to_trace)
|
||||
File.open(path_to_trace, 'a') do |f|
|
||||
f.write(trace_part)
|
||||
end
|
||||
end
|
||||
|
||||
def dir_to_trace
|
||||
|
@ -365,11 +386,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
|
||||
|
|
|
@ -15,8 +15,8 @@ class Commit
|
|||
DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines]
|
||||
|
||||
# Commits above this size will not be rendered in HTML
|
||||
DIFF_HARD_LIMIT_FILES = 1000 unless defined?(DIFF_HARD_LIMIT_FILES)
|
||||
DIFF_HARD_LIMIT_LINES = 50000 unless defined?(DIFF_HARD_LIMIT_LINES)
|
||||
DIFF_HARD_LIMIT_FILES = 1000
|
||||
DIFF_HARD_LIMIT_LINES = 50000
|
||||
|
||||
class << self
|
||||
def decorate(commits, project)
|
||||
|
@ -218,6 +218,10 @@ class Commit
|
|||
def revert_branch_name
|
||||
"revert-#{short_id}"
|
||||
end
|
||||
|
||||
def cherry_pick_branch_name
|
||||
project.repository.next_branch("cherry-pick-#{short_id}", mild: true)
|
||||
end
|
||||
|
||||
def revert_description
|
||||
if merged_merge_request
|
||||
|
@ -253,6 +257,10 @@ class Commit
|
|||
end.any? { |commit_ref| commit_ref.reverts_commit?(self) }
|
||||
end
|
||||
|
||||
def change_type_title
|
||||
merged_merge_request ? 'merge request' : 'commit'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def repo_changes
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
#
|
||||
|
||||
require 'carrierwave/orm/activerecord'
|
||||
require 'file_size_validator'
|
||||
|
||||
class Group < Namespace
|
||||
include Gitlab::ConfigHelper
|
||||
|
|
|
@ -21,10 +21,9 @@
|
|||
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) }
|
||||
scope :build_hooks, -> { where(build_events: true) }
|
||||
scope :wiki_page_hooks, -> { where(wiki_page_events: true) }
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
#
|
||||
|
||||
require 'carrierwave/orm/activerecord'
|
||||
require 'file_size_validator'
|
||||
|
||||
class Issue < ActiveRecord::Base
|
||||
include InternalId
|
||||
|
@ -104,10 +103,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 +156,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
|
||||
|
|
|
@ -27,9 +27,6 @@
|
|||
# merge_commit_sha :string
|
||||
#
|
||||
|
||||
require Rails.root.join("app/models/commit")
|
||||
require Rails.root.join("lib/static_model")
|
||||
|
||||
class MergeRequest < ActiveRecord::Base
|
||||
include InternalId
|
||||
include Issuable
|
||||
|
@ -605,4 +602,8 @@ class MergeRequest < ActiveRecord::Base
|
|||
def can_be_reverted?(current_user = nil)
|
||||
merge_commit && !merge_commit.has_been_reverted?(current_user, self)
|
||||
end
|
||||
|
||||
def can_be_cherry_picked?
|
||||
merge_commit
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,8 +11,6 @@
|
|||
# updated_at :datetime
|
||||
#
|
||||
|
||||
require Rails.root.join("app/models/commit")
|
||||
|
||||
class MergeRequestDiff < ActiveRecord::Base
|
||||
include Sortable
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
#
|
||||
|
||||
require 'carrierwave/orm/activerecord'
|
||||
require 'file_size_validator'
|
||||
|
||||
class Note < ActiveRecord::Base
|
||||
include Gitlab::CurrentSettings
|
||||
|
|
|
@ -40,7 +40,6 @@
|
|||
#
|
||||
|
||||
require 'carrierwave/orm/activerecord'
|
||||
require 'file_size_validator'
|
||||
|
||||
class Project < ActiveRecord::Base
|
||||
include Gitlab::ConfigHelper
|
||||
|
@ -409,6 +408,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 +830,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 +842,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
|
||||
|
|
|
@ -8,12 +8,23 @@
|
|||
#
|
||||
|
||||
require 'carrierwave/orm/activerecord'
|
||||
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
|
||||
|
|
|
@ -183,7 +183,7 @@ class HipchatService < Service
|
|||
title = obj_attr[:title]
|
||||
|
||||
merge_request_url = "#{project_url}/merge_requests/#{merge_request_id}"
|
||||
merge_request_link = "<a href=\"#{merge_request_url}\">merge request ##{merge_request_id}</a>"
|
||||
merge_request_link = "<a href=\"#{merge_request_url}\">merge request !#{merge_request_id}</a>"
|
||||
message = "#{user_name} #{state} #{merge_request_link} in " \
|
||||
"#{project_link}: <b>#{title}</b>"
|
||||
|
||||
|
@ -224,7 +224,7 @@ class HipchatService < Service
|
|||
when "MergeRequest"
|
||||
subj_attr = HashWithIndifferentAccess.new(data[:merge_request])
|
||||
subject_id = subj_attr[:iid]
|
||||
subject_desc = "##{subject_id}"
|
||||
subject_desc = "!#{subject_id}"
|
||||
subject_type = "merge request"
|
||||
title = format_title(subj_attr[:title])
|
||||
when "Snippet"
|
||||
|
|
|
@ -60,7 +60,7 @@ class SlackService < Service
|
|||
end
|
||||
|
||||
def supported_events
|
||||
%w(push issue merge_request note tag_push build)
|
||||
%w(push issue merge_request note tag_push build wiki_page)
|
||||
end
|
||||
|
||||
def execute(data)
|
||||
|
@ -90,6 +90,8 @@ class SlackService < Service
|
|||
NoteMessage.new(data)
|
||||
when "build"
|
||||
BuildMessage.new(data) if should_build_be_notified?(data)
|
||||
when "wiki_page"
|
||||
WikiPageMessage.new(data)
|
||||
end
|
||||
|
||||
opt = {}
|
||||
|
@ -133,3 +135,4 @@ require "slack_service/push_message"
|
|||
require "slack_service/merge_message"
|
||||
require "slack_service/note_message"
|
||||
require "slack_service/build_message"
|
||||
require "slack_service/wiki_page_message"
|
||||
|
|
|
@ -50,7 +50,7 @@ class SlackService
|
|||
end
|
||||
|
||||
def merge_request_link
|
||||
"[merge request ##{merge_request_id}](#{merge_request_url})"
|
||||
"[merge request !#{merge_request_id}](#{merge_request_url})"
|
||||
end
|
||||
|
||||
def merge_request_url
|
||||
|
|
|
@ -58,7 +58,7 @@ class SlackService
|
|||
|
||||
def create_merge_note(merge_request)
|
||||
commented_on_message(
|
||||
"[merge request ##{merge_request[:iid]}](#{@note_url})",
|
||||
"[merge request !#{merge_request[:iid]}](#{@note_url})",
|
||||
format_title(merge_request[:title]))
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
class SlackService
|
||||
class WikiPageMessage < BaseMessage
|
||||
attr_reader :user_name
|
||||
attr_reader :title
|
||||
attr_reader :project_name
|
||||
attr_reader :project_url
|
||||
attr_reader :wiki_page_url
|
||||
attr_reader :action
|
||||
attr_reader :description
|
||||
|
||||
def initialize(params)
|
||||
@user_name = params[:user][:name]
|
||||
@project_name = params[:project_name]
|
||||
@project_url = params[:project_url]
|
||||
|
||||
obj_attr = params[:object_attributes]
|
||||
obj_attr = HashWithIndifferentAccess.new(obj_attr)
|
||||
@title = obj_attr[:title]
|
||||
@wiki_page_url = obj_attr[:url]
|
||||
@description = obj_attr[:content]
|
||||
|
||||
@action =
|
||||
case obj_attr[:action]
|
||||
when "create"
|
||||
"created"
|
||||
when "update"
|
||||
"edited"
|
||||
end
|
||||
end
|
||||
|
||||
def attachments
|
||||
description_message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def message
|
||||
"#{user_name} #{action} #{wiki_page_link} in #{project_link}: *#{title}*"
|
||||
end
|
||||
|
||||
def description_message
|
||||
[{ text: format(@description), color: attachment_color }]
|
||||
end
|
||||
|
||||
def project_link
|
||||
"[#{project_name}](#{project_url})"
|
||||
end
|
||||
|
||||
def wiki_page_link
|
||||
"[wiki page](#{wiki_page_url})"
|
||||
end
|
||||
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)
|
||||
|
@ -226,7 +228,8 @@ class Repository
|
|||
|
||||
def cache_keys
|
||||
%i(size branch_names tag_names commit_count
|
||||
readme version contribution_guide changelog license)
|
||||
readme version contribution_guide changelog
|
||||
license_blob license_key)
|
||||
end
|
||||
|
||||
def build_cache
|
||||
|
@ -459,27 +462,21 @@ class Repository
|
|||
end
|
||||
end
|
||||
|
||||
def license
|
||||
cache.fetch(:license) do
|
||||
licenses = tree(:head).blobs.find_all do |file|
|
||||
file.name =~ /\A(copying|license|licence)/i
|
||||
end
|
||||
def license_blob
|
||||
return nil if !exists? || empty?
|
||||
|
||||
preferences = [
|
||||
/\Alicen[sc]e\z/i, # LICENSE, LICENCE
|
||||
/\Alicen[sc]e\./i, # LICENSE.md, LICENSE.txt
|
||||
/\Acopying\z/i, # COPYING
|
||||
/\Acopying\.(?!lesser)/i, # COPYING.txt
|
||||
/Acopying.lesser/i # COPYING.LESSER
|
||||
]
|
||||
|
||||
license = nil
|
||||
preferences.each do |r|
|
||||
license = licenses.find { |l| l.name =~ r }
|
||||
break if license
|
||||
cache.fetch(:license_blob) do
|
||||
if licensee_project.license
|
||||
blob_at_branch(root_ref, licensee_project.matched_file.filename)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
license
|
||||
def license_key
|
||||
return nil if !exists? || empty?
|
||||
|
||||
cache.fetch(:license_key) do
|
||||
licensee_project.license.try(:key) || 'no-license'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -547,15 +544,18 @@ class Repository
|
|||
commit(sha)
|
||||
end
|
||||
|
||||
def next_patch_branch
|
||||
patch_branch_ids = self.branch_names.map do |n|
|
||||
result = n.match(/\Apatch-([0-9]+)\z/)
|
||||
def next_branch(name, opts={})
|
||||
branch_ids = self.branch_names.map do |n|
|
||||
next 1 if n == name
|
||||
result = n.match(/\A#{name}-([0-9]+)\z/)
|
||||
result[1].to_i if result
|
||||
end.compact
|
||||
|
||||
highest_patch_branch_id = patch_branch_ids.max || 0
|
||||
highest_branch_id = branch_ids.max || 0
|
||||
|
||||
"patch-#{highest_patch_branch_id + 1}"
|
||||
return name if opts[:mild] && 0 == highest_branch_id
|
||||
|
||||
"#{name}-#{highest_branch_id + 1}"
|
||||
end
|
||||
|
||||
# Remove archives older than 2 hours
|
||||
|
@ -758,6 +758,28 @@ class Repository
|
|||
end
|
||||
end
|
||||
|
||||
def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil)
|
||||
source_sha = find_branch(base_branch).target
|
||||
cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch)
|
||||
|
||||
return false unless cherry_pick_tree_id
|
||||
|
||||
commit_with_hooks(user, base_branch) do |ref|
|
||||
committer = user_to_committer(user)
|
||||
source_sha = Rugged::Commit.create(rugged,
|
||||
message: commit.message,
|
||||
author: {
|
||||
email: commit.author_email,
|
||||
name: commit.author_name,
|
||||
time: commit.authored_date
|
||||
},
|
||||
committer: committer,
|
||||
tree: cherry_pick_tree_id,
|
||||
parents: [rugged.lookup(source_sha)],
|
||||
update_ref: ref)
|
||||
end
|
||||
end
|
||||
|
||||
def check_revert_content(commit, base_branch)
|
||||
source_sha = find_branch(base_branch).target
|
||||
args = [commit.id, source_sha]
|
||||
|
@ -772,6 +794,20 @@ class Repository
|
|||
tree_id
|
||||
end
|
||||
|
||||
def check_cherry_pick_content(commit, base_branch)
|
||||
source_sha = find_branch(base_branch).target
|
||||
args = [commit.id, source_sha]
|
||||
args << 1 if commit.merge_commit?
|
||||
|
||||
cherry_pick_index = rugged.cherrypick_commit(*args)
|
||||
return false if cherry_pick_index.conflicts?
|
||||
|
||||
tree_id = cherry_pick_index.write_tree(rugged)
|
||||
return false unless diff_exists?(source_sha, tree_id)
|
||||
|
||||
tree_id
|
||||
end
|
||||
|
||||
def diff_exists?(sha1, sha2)
|
||||
rugged.diff(sha1, sha2).size > 0
|
||||
end
|
||||
|
@ -923,4 +959,8 @@ class Repository
|
|||
def cache
|
||||
@cache ||= RepositoryCache.new(path_with_namespace)
|
||||
end
|
||||
|
||||
def licensee_project
|
||||
@licensee_project ||= Licensee.project(path)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,6 +32,7 @@ class Service < ActiveRecord::Base
|
|||
default_value_for :tag_push_events, true
|
||||
default_value_for :note_events, true
|
||||
default_value_for :build_events, true
|
||||
default_value_for :wiki_page_events, true
|
||||
|
||||
after_initialize :initialize_properties
|
||||
|
||||
|
@ -53,6 +54,7 @@ class Service < ActiveRecord::Base
|
|||
scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) }
|
||||
scope :note_hooks, -> { where(note_events: true, active: true) }
|
||||
scope :build_hooks, -> { where(build_events: true, active: true) }
|
||||
scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) }
|
||||
|
||||
default_value_for :category, 'common'
|
||||
|
||||
|
@ -94,7 +96,7 @@ class Service < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def supported_events
|
||||
%w(push tag_push issue merge_request)
|
||||
%w(push tag_push issue merge_request wiki_page)
|
||||
end
|
||||
|
||||
def execute(data)
|
||||
|
|
|
@ -63,7 +63,6 @@
|
|||
#
|
||||
|
||||
require 'carrierwave/orm/activerecord'
|
||||
require 'file_size_validator'
|
||||
|
||||
class User < ActiveRecord::Base
|
||||
extend Gitlab::ConfigHelper
|
||||
|
|
|
@ -29,6 +29,10 @@ class WikiPage
|
|||
# new Page values before writing to the Gollum repository.
|
||||
attr_accessor :attributes
|
||||
|
||||
def hook_attrs
|
||||
attributes
|
||||
end
|
||||
|
||||
def initialize(wiki, page = nil, persisted = false)
|
||||
@wiki = wiki
|
||||
@page = page
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
module Commits
|
||||
class ChangeService < ::BaseService
|
||||
class ValidationError < StandardError; end
|
||||
class ChangeError < StandardError; end
|
||||
|
||||
def execute
|
||||
@source_project = params[:source_project] || @project
|
||||
@target_branch = params[:target_branch]
|
||||
@commit = params[:commit]
|
||||
@create_merge_request = params[:create_merge_request].present?
|
||||
|
||||
check_push_permissions unless @create_merge_request
|
||||
commit
|
||||
rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError,
|
||||
ValidationError, ChangeError => ex
|
||||
error(ex.message)
|
||||
end
|
||||
|
||||
def commit
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_push_permissions
|
||||
allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch)
|
||||
|
||||
unless allowed
|
||||
raise ValidationError.new('You are not allowed to push into this branch')
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def create_target_branch(new_branch)
|
||||
# Temporary branch exists and contains the change commit
|
||||
return success if repository.find_branch(new_branch)
|
||||
|
||||
result = CreateBranchService.new(@project, current_user)
|
||||
.execute(new_branch, @target_branch, source_project: @source_project)
|
||||
|
||||
if result[:status] == :error
|
||||
raise ChangeError, "There was an error creating the source branch: #{result[:message]}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
module Commits
|
||||
class CherryPickService < ChangeService
|
||||
def commit
|
||||
cherry_pick_into = @create_merge_request ? @commit.cherry_pick_branch_name : @target_branch
|
||||
cherry_pick_tree_id = repository.check_cherry_pick_content(@commit, @target_branch)
|
||||
|
||||
if cherry_pick_tree_id
|
||||
create_target_branch(cherry_pick_into) if @create_merge_request
|
||||
|
||||
repository.cherry_pick(current_user, @commit, cherry_pick_into, cherry_pick_tree_id)
|
||||
success
|
||||
else
|
||||
error_msg = "Sorry, we cannot cherry-pick this #{@commit.change_type_title} automatically.
|
||||
It may have already been cherry-picked, or a more recent commit may have updated some of its content."
|
||||
raise ChangeError, error_msg
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,21 +1,5 @@
|
|||
module Commits
|
||||
class RevertService < ::BaseService
|
||||
class ValidationError < StandardError; end
|
||||
class ReversionError < StandardError; end
|
||||
|
||||
def execute
|
||||
@source_project = params[:source_project] || @project
|
||||
@target_branch = params[:target_branch]
|
||||
@commit = params[:commit]
|
||||
@create_merge_request = params[:create_merge_request].present?
|
||||
|
||||
check_push_permissions unless @create_merge_request
|
||||
commit
|
||||
rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError,
|
||||
ValidationError, ReversionError => ex
|
||||
error(ex.message)
|
||||
end
|
||||
|
||||
class RevertService < ChangeService
|
||||
def commit
|
||||
revert_into = @create_merge_request ? @commit.revert_branch_name : @target_branch
|
||||
revert_tree_id = repository.check_revert_content(@commit, @target_branch)
|
||||
|
@ -26,34 +10,10 @@ module Commits
|
|||
repository.revert(current_user, @commit, revert_into, revert_tree_id)
|
||||
success
|
||||
else
|
||||
error_msg = "Sorry, we cannot revert this #{params[:revert_type_title]} automatically.
|
||||
error_msg = "Sorry, we cannot revert this #{@commit.change_type_title} automatically.
|
||||
It may have already been reverted, or a more recent commit may have updated some of its content."
|
||||
raise ReversionError, error_msg
|
||||
raise ChangeError, error_msg
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_target_branch(new_branch)
|
||||
# Temporary branch exists and contains the revert commit
|
||||
return success if repository.find_branch(new_branch)
|
||||
|
||||
result = CreateBranchService.new(@project, current_user)
|
||||
.execute(new_branch, @target_branch, source_project: @source_project)
|
||||
|
||||
if result[:status] == :error
|
||||
raise ReversionError, "There was an error creating the source branch: #{result[:message]}"
|
||||
end
|
||||
end
|
||||
|
||||
def check_push_permissions
|
||||
allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch)
|
||||
|
||||
unless allowed
|
||||
raise ValidationError.new('You are not allowed to push into this branch')
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
module WikiPages
|
||||
class BaseService < ::BaseService
|
||||
|
||||
def hook_data(page, action)
|
||||
hook_data = {
|
||||
object_kind: page.class.name.underscore,
|
||||
user: current_user.hook_attrs,
|
||||
project: @project.hook_attrs,
|
||||
object_attributes: page.hook_attrs,
|
||||
# DEPRECATED
|
||||
repository: @project.hook_attrs.slice(:name, :url, :description, :homepage)
|
||||
}
|
||||
|
||||
page_url = Gitlab::UrlBuilder.build(page)
|
||||
hook_data[:object_attributes].merge!(url: page_url, action: action)
|
||||
hook_data
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def execute_hooks(page, action = 'create')
|
||||
page_data = hook_data(page, action)
|
||||
@project.execute_hooks(page_data, :wiki_page_hooks)
|
||||
@project.execute_services(page_data, :wiki_page_hooks)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
module WikiPages
|
||||
class CreateService < WikiPages::BaseService
|
||||
def execute
|
||||
page = WikiPage.new(@project.wiki)
|
||||
|
||||
if page.create(@params)
|
||||
execute_hooks(page, 'create')
|
||||
end
|
||||
|
||||
page
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,11 @@
|
|||
module WikiPages
|
||||
class UpdateService < WikiPages::BaseService
|
||||
def execute(page)
|
||||
if page.update(@params[:content], @params[:format], @params[:message])
|
||||
execute_hooks(page, 'update')
|
||||
end
|
||||
|
||||
page
|
||||
end
|
||||
end
|
||||
end
|
|
@ -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
|
||||
|
@ -21,9 +26,9 @@
|
|||
- @already_added_projects.each do |project|
|
||||
%tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
|
||||
%td
|
||||
= link_to project.import_source, "https://github.com/#{project.import_source}", target: "_blank"
|
||||
= github_project_link(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
|
||||
|
@ -38,11 +43,13 @@
|
|||
- @repos.each do |repo|
|
||||
%tr{id: "repo_#{repo.id}"}
|
||||
%td
|
||||
= link_to repo.full_name, "https://github.com/#{repo.full_name}", target: "_blank"
|
||||
= github_project_link(repo.full_name)
|
||||
%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
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
%p
|
||||
= "Merge Request ##{@merge_request.iid} was closed by #{@updated_by.name}"
|
||||
= "Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
= "Merge Request ##{@merge_request.iid} was closed by #{@updated_by.name}"
|
||||
= "Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}"
|
||||
|
||||
Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
%p
|
||||
= "Merge Request ##{@merge_request.iid} was #{@mr_status} by #{@updated_by.name}"
|
||||
= "Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}"
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue