Commit 0d035441 authored by Valery Sizov's avatar Valery Sizov

Merge branch 'ce-upstream' into 'master'

Sync CE master to EE master



See merge request !346
parents 90cb7c64 ca2abf8c
...@@ -728,7 +728,7 @@ Metrics/ParameterLists: ...@@ -728,7 +728,7 @@ Metrics/ParameterLists:
# A complexity metric geared towards measuring complexity for a human reader. # A complexity metric geared towards measuring complexity for a human reader.
Metrics/PerceivedComplexity: Metrics/PerceivedComplexity:
Enabled: true Enabled: true
Max: 17 Max: 18
#################### Lint ################################ #################### Lint ################################
......
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.7.0 (unreleased) v 8.7.0 (unreleased)
- The Projects::HousekeepingService class has extra instrumentation (Yorick Peterse) - Method instrumentation now uses Module#prepend instead of aliasing methods
- Fix revoking of authorized OAuth applications (Connor Shea) - Repository.clean_old_archives is now instrumented
- All service classes (those residing in app/services) are now instrumented (Yorick Peterse) - Add support for environment variables on a job level in CI configuration file
- Developers can now add custom tags to transactions (Yorick Peterse) - SQL query counts are now tracked per transaction
- Loading of an issue's referenced merge requests and related branches is now done asynchronously (Yorick Peterse) - The Projects::HousekeepingService class has extra instrumentation
- All service classes (those residing in app/services) are now instrumented
- Developers can now add custom tags to transactions
- Loading of an issue's referenced merge requests and related branches is now done asynchronously
- Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea) - Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea)
- Project switcher uses new dropdown styling
- Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea) - Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea)
- Do not include award_emojis in issue and merge_request comment_count !3610 (Lucas Charles) - Do not include award_emojis in issue and merge_request comment_count !3610 (Lucas Charles)
- All images in discussions and wikis now link to their source files !3464 (Connor Shea). - All images in discussions and wikis now link to their source files !3464 (Connor Shea).
...@@ -14,7 +18,7 @@ v 8.7.0 (unreleased) ...@@ -14,7 +18,7 @@ v 8.7.0 (unreleased)
- Add setting for customizing the list of trusted proxies !3524 - Add setting for customizing the list of trusted proxies !3524
- Allow projects to be transfered to a lower visibility level group - Allow projects to be transfered to a lower visibility level group
- Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524 - Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524
- Improved Markdown rendering performance !3389 (Yorick Peterse) - Improved Markdown rendering performance !3389
- Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu) - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu)
- API: Ability to subscribe and unsubscribe from issues and merge requests (Robert Schilling) - API: Ability to subscribe and unsubscribe from issues and merge requests (Robert Schilling)
- Expose project badges in project settings - Expose project badges in project settings
...@@ -42,6 +46,7 @@ v 8.7.0 (unreleased) ...@@ -42,6 +46,7 @@ v 8.7.0 (unreleased)
- Add default scope to projects to exclude projects pending deletion - Add default scope to projects to exclude projects pending deletion
- Allow to close merge requests which source projects(forks) are deleted. - Allow to close merge requests which source projects(forks) are deleted.
- Ensure empty recipients are rejected in BuildsEmailService - Ensure empty recipients are rejected in BuildsEmailService
- Use rugged to change HEAD in Project#change_head (P.S.V.R)
- API: Ability to filter milestones by state `active` and `closed` (Robert Schilling) - API: Ability to filter milestones by state `active` and `closed` (Robert Schilling)
- API: Fix milestone filtering by `iid` (Robert Schilling) - API: Fix milestone filtering by `iid` (Robert Schilling)
- API: Delete notes of issues, snippets, and merge requests (Robert Schilling) - API: Delete notes of issues, snippets, and merge requests (Robert Schilling)
...@@ -50,10 +55,12 @@ v 8.7.0 (unreleased) ...@@ -50,10 +55,12 @@ v 8.7.0 (unreleased)
- Fix high CPU usage when PostReceive receives refs/merge-requests/<id> - Fix high CPU usage when PostReceive receives refs/merge-requests/<id>
- Hide `Create a group` help block when creating a new project in a group - Hide `Create a group` help block when creating a new project in a group
- Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.)
- Allow issues and merge requests to be assigned to the author !2765
- Gracefully handle notes on deleted commits in merge requests (Stan Hu) - Gracefully handle notes on deleted commits in merge requests (Stan Hu)
- Decouple membership and notifications - Decouple membership and notifications
- Fix creation of merge requests for orphaned branches (Stan Hu) - Fix creation of merge requests for orphaned branches (Stan Hu)
- API: Ability to retrieve a single tag (Robert Schilling) - 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) - 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) - Remove "Congratulations!" tweet button on newly-created project. (Connor Shea)
- Fix admin/projects when using visibility levels on search (PotHix) - Fix admin/projects when using visibility levels on search (PotHix)
...@@ -69,13 +76,23 @@ v 8.7.0 (unreleased) ...@@ -69,13 +76,23 @@ v 8.7.0 (unreleased)
- Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu) - Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu)
- Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld) - Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld)
- Improved markdown forms - Improved markdown forms
- Delete tags using Rugged for performance reasons (Robert Schilling)
- Diffs load at the correct point when linking from from number - Diffs load at the correct point when linking from from number
- Selected diff rows highlight - Selected diff rows highlight
- Fix emoji catgories in the emoji picker - Fix emoji categories in the emoji picker
- Add encrypted credentials for imported projects and migrate old ones
- Author and participants are displayed first on users autocompletion
- Show number sign on external issue reference text (Florent Baldino)
- Updated print style for issues
v 8.6.6 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) - Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk)
- Project switcher uses new dropdown styling - 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 v 8.6.5
- Fix importing from GitHub Enterprise. !3529 - Fix importing from GitHub Enterprise. !3529
...@@ -293,7 +310,7 @@ v 8.5.1 ...@@ -293,7 +310,7 @@ v 8.5.1
v 8.5.0 v 8.5.0
- Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu) - Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu)
- Cache various Repository methods to improve performance (Yorick Peterse) - Cache various Repository methods to improve performance
- Fix duplicated branch creation/deletion Webhooks/service notifications when using Web UI (Stan Hu) - Fix duplicated branch creation/deletion Webhooks/service notifications when using Web UI (Stan Hu)
- Ensure rake tasks that don't need a DB connection can be run without one - Ensure rake tasks that don't need a DB connection can be run without one
- Update New Relic gem to 3.14.1.311 (Stan Hu) - Update New Relic gem to 3.14.1.311 (Stan Hu)
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
window.GitLab ?= {} window.GitLab ?= {}
GitLab.GfmAutoComplete = GitLab.GfmAutoComplete =
dataLoading: false
dataSource: '' dataSource: ''
# Emoji # Emoji
...@@ -17,17 +19,41 @@ GitLab.GfmAutoComplete = ...@@ -17,17 +19,41 @@ GitLab.GfmAutoComplete =
template: '<li><small>${id}</small> ${title}</li>' template: '<li><small>${id}</small> ${title}</li>'
# Add GFM auto-completion to all input fields, that accept GFM input. # Add GFM auto-completion to all input fields, that accept GFM input.
setup: -> setup: (wrap) ->
input = $('.js-gfm-input') @input = $('.js-gfm-input')
# destroy previous instances
@destroyAtWho()
# set up instances
@setupAtWho()
if @dataSource
if !@dataLoading
@dataLoading = true
# We should wait until initializations are done
# and only trigger the last .setup since
# The previous .dataSource belongs to the previous issuable
# and the last one will have the **proper** .dataSource property
# TODO: Make this a singleton and turn off events when moving to another page
setTimeout( =>
fetch = @fetchData(@dataSource)
fetch.done (data) =>
@dataLoading = false
@loadData(data)
, 1000)
setupAtWho: ->
# Emoji # Emoji
input.atwho @input.atwho
at: ':' at: ':'
displayTpl: @Emoji.template displayTpl: @Emoji.template
insertTpl: ':${name}:' insertTpl: ':${name}:'
# Team Members # Team Members
input.atwho @input.atwho
at: '@' at: '@'
displayTpl: @Members.template displayTpl: @Members.template
insertTpl: '${atwho-at}${username}' insertTpl: '${atwho-at}${username}'
...@@ -42,7 +68,7 @@ GitLab.GfmAutoComplete = ...@@ -42,7 +68,7 @@ GitLab.GfmAutoComplete =
title: sanitize(title) title: sanitize(title)
search: sanitize("#{m.username} #{m.name}") search: sanitize("#{m.username} #{m.name}")
input.atwho @input.atwho
at: '#' at: '#'
alias: 'issues' alias: 'issues'
searchKey: 'search' searchKey: 'search'
...@@ -55,7 +81,7 @@ GitLab.GfmAutoComplete = ...@@ -55,7 +81,7 @@ GitLab.GfmAutoComplete =
title: sanitize(i.title) title: sanitize(i.title)
search: "#{i.iid} #{i.title}" search: "#{i.iid} #{i.title}"
input.atwho @input.atwho
at: '!' at: '!'
alias: 'mergerequests' alias: 'mergerequests'
searchKey: 'search' searchKey: 'search'
...@@ -68,13 +94,18 @@ GitLab.GfmAutoComplete = ...@@ -68,13 +94,18 @@ GitLab.GfmAutoComplete =
title: sanitize(m.title) title: sanitize(m.title)
search: "#{m.iid} #{m.title}" search: "#{m.iid} #{m.title}"
if @dataSource destroyAtWho: ->
$.getJSON(@dataSource).done (data) -> @input.atwho('destroy')
# load members
input.atwho 'load', '@', data.members fetchData: (dataSource) ->
# load issues $.getJSON(dataSource)
input.atwho 'load', 'issues', data.issues
# load merge requests loadData: (data) ->
input.atwho 'load', 'mergerequests', data.mergerequests # load members
# load emojis @input.atwho 'load', '@', data.members
input.atwho 'load', ':', data.emojis # load issues
@input.atwho 'load', 'issues', data.issues
# load merge requests
@input.atwho 'load', 'mergerequests', data.mergerequests
# load emojis
@input.atwho 'load', ':', data.emojis
...@@ -4,18 +4,33 @@ class @ImporterStatus ...@@ -4,18 +4,33 @@ class @ImporterStatus
this.setAutoUpdate() this.setAutoUpdate()
initStatusPage: -> initStatusPage: ->
$(".js-add-to-import").click (event) => $('.js-add-to-import')
new_namespace = null .off 'click'
tr = $(event.currentTarget).closest("tr") .on 'click', (e) =>
id = tr.attr("id").replace("repo_", "") new_namespace = null
if tr.find(".import-target input").length > 0 $btn = $(e.currentTarget)
new_namespace = tr.find(".import-target input").prop("value") $tr = $btn.closest('tr')
tr.find(".import-target").empty().append(new_namespace + "/" + tr.find(".import-target").data("project_name")) id = $tr.attr('id').replace('repo_', '')
$.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script' if $tr.find('.import-target input').length > 0
new_namespace = $tr.find('.import-target input').prop('value')
$(".js-import-all").click (event) => $tr.find('.import-target').empty().append("#{new_namespace} / #{$tr.find('.import-target').data('project_name')}")
$(".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: -> setAutoUpdate: ->
setInterval (=> setInterval (=>
......
...@@ -75,6 +75,9 @@ class @Notes ...@@ -75,6 +75,9 @@ class @Notes
# when issue status changes, we need to refresh data # when issue status changes, we need to refresh data
$(document).on "issuable:change", @refresh $(document).on "issuable:change", @refresh
# when a key is clicked on the notes
$(document).on "keydown", ".js-note-text", @keydownNoteText
cleanBinding: -> cleanBinding: ->
$(document).off "ajax:success", ".js-main-target-form" $(document).off "ajax:success", ".js-main-target-form"
$(document).off "ajax:success", ".js-discussion-note-form" $(document).off "ajax:success", ".js-discussion-note-form"
...@@ -92,10 +95,19 @@ class @Notes ...@@ -92,10 +95,19 @@ class @Notes
$(document).off "click", ".js-note-target-reopen" $(document).off "click", ".js-note-target-reopen"
$(document).off "click", ".js-note-target-close" $(document).off "click", ".js-note-target-close"
$(document).off "click", ".js-note-discard" $(document).off "click", ".js-note-discard"
$(document).off "keydown", ".js-note-text"
$('.note .js-task-list-container').taskList('disable') $('.note .js-task-list-container').taskList('disable')
$(document).off 'tasklist:changed', '.note .js-task-list-container' $(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: -> initRefresh: ->
clearInterval(Notes.interval) clearInterval(Notes.interval)
Notes.interval = setInterval => Notes.interval = setInterval =>
...@@ -343,7 +355,7 @@ class @Notes ...@@ -343,7 +355,7 @@ class @Notes
Adds a hidden div with the original content of the note to fill the edit note form with Adds a hidden div with the original content of the note to fill the edit note form with
if the user cancels if the user cancels
### ###
showEditForm: (e) -> showEditForm: (e, scrollTo, myLastNote) ->
e.preventDefault() e.preventDefault()
note = $(this).closest(".note") note = $(this).closest(".note")
note.addClass "is-editting" note.addClass "is-editting"
...@@ -354,9 +366,27 @@ class @Notes ...@@ -354,9 +366,27 @@ class @Notes
# Show the attachment delete link # Show the attachment delete link
note.find(".js-note-attachment-delete").show() 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 Called in response to clicking the edit note link
......
...@@ -12,6 +12,7 @@ class @UsersSelect ...@@ -12,6 +12,7 @@ class @UsersSelect
showNullUser = $dropdown.data('null-user') showNullUser = $dropdown.data('null-user')
showAnyUser = $dropdown.data('any-user') showAnyUser = $dropdown.data('any-user')
firstUser = $dropdown.data('first-user') firstUser = $dropdown.data('first-user')
@authorId = $dropdown.data('author-id')
selectedId = $dropdown.data('selected') selectedId = $dropdown.data('selected')
defaultLabel = $dropdown.data('default-label') defaultLabel = $dropdown.data('default-label')
issueURL = $dropdown.data('issueUpdate') issueURL = $dropdown.data('issueUpdate')
...@@ -209,6 +210,7 @@ class @UsersSelect ...@@ -209,6 +210,7 @@ class @UsersSelect
@groupId = $(select).data('group-id') @groupId = $(select).data('group-id')
@showCurrentUser = $(select).data('current-user') @showCurrentUser = $(select).data('current-user')
@pushCodeToProtectedBranches = $(select).data('push-code-to-protected-branches') @pushCodeToProtectedBranches = $(select).data('push-code-to-protected-branches')
@authorId = $(select).data('author-id')
showNullUser = $(select).data('null-user') showNullUser = $(select).data('null-user')
showAnyUser = $(select).data('any-user') showAnyUser = $(select).data('any-user')
showEmailUser = $(select).data('email-user') showEmailUser = $(select).data('email-user')
...@@ -316,6 +318,7 @@ class @UsersSelect ...@@ -316,6 +318,7 @@ class @UsersSelect
skip_ldap: @skipLdap skip_ldap: @skipLdap
current_user: @showCurrentUser current_user: @showCurrentUser
push_code_to_protected_branches: @pushCodeToProtectedBranches push_code_to_protected_branches: @pushCodeToProtectedBranches
author_id: @authorId
dataType: "json" dataType: "json"
).done (users) -> ).done (users) ->
callback(users) callback(users)
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
*/ */
.status-box { .status-box {
/* Extra small devices (phones, less than 768px) */ /* Extra small devices (phones, less than 768px) */
/* No media query since this is the default in Bootstrap */ /* No media query since this is the default in Bootstrap */
padding: 5px 11px; padding: 5px 11px;
......
...@@ -70,13 +70,6 @@ ...@@ -70,13 +70,6 @@
display: none; display: none;
} }
.issue-details {
.creator,
.page-title .btn-close {
display: none;
}
}
%ul.notes .note-role, .note-actions { %ul.notes .note-role, .note-actions {
display: none; display: none;
} }
......
...@@ -39,8 +39,7 @@ ...@@ -39,8 +39,7 @@
.diff-file { .diff-file {
border: 1px solid $border-color; border: 1px solid $border-color;
border-bottom: none; border-bottom: none;
margin-left: 0; margin: 0;
margin-right: 0;
} }
} }
......
...@@ -75,6 +75,11 @@ li.commit { ...@@ -75,6 +75,11 @@ li.commit {
} }
} }
.item-title {
display: inline-block;
max-width: 70%;
}
.commit-row-description { .commit-row-description {
font-size: 14px; font-size: 14px;
border-left: 1px solid #eee; border-left: 1px solid #eee;
......
.detail-page-header { .detail-page-header {
padding: 11px 0; padding: $gl-padding-top 0;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
color: #5c5d5e; color: #5c5d5e;
font-size: 16px; font-size: 16px;
...@@ -16,11 +16,6 @@ ...@@ -16,11 +16,6 @@
.issue_created_ago, .author_link { .issue_created_ago, .author_link {
white-space: nowrap; white-space: nowrap;
} }
.issue-meta {
display: inline-block;
line-height: 20px;
}
} }
.detail-page-description { .detail-page-description {
......
...@@ -16,3 +16,24 @@ i.icon-gitorious-big { ...@@ -16,3 +16,24 @@ i.icon-gitorious-big {
width: 18px; width: 18px;
height: 18px; height: 18px;
} }
.import-jobs-from-col,
.import-jobs-to-col {
width: 40%;
}
.import-jobs-status-col {
width: 20%;
}
.btn-import {
.loading-icon {
display: none;
}
&.is-loading {
.loading-icon {
display: inline-block;
}
}
}
...@@ -273,10 +273,6 @@ ...@@ -273,10 +273,6 @@
} }
} }
.btn-default.gutter-toggle {
margin-top: 4px;
}
.detail-page-description { .detail-page-description {
small { small {
color: $gray-darkest; color: $gray-darkest;
...@@ -322,3 +318,50 @@ ...@@ -322,3 +318,50 @@
padding-top: 7px; 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 { ...@@ -86,41 +86,9 @@ form.edit-issue {
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
.issue-btn-group { .issue-btn-group {
width: 100%; width: 100%;
margin-top: 5px;
.btn-group {
width: 100%;
ul {
width: 100%;
text-align: center;
}
}
.btn { .btn {
width: 100%; 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 { ...@@ -133,11 +101,3 @@ form.edit-issue {
color: $gl-text-color; color: $gl-text-color;
margin-left: 52px; margin-left: 52px;
} }
.editor-details {
display: block;
@media (min-width: $screen-sm-min) {
display: inline-block;
}
}
...@@ -198,6 +198,12 @@ ul.notes { ...@@ -198,6 +198,12 @@ ul.notes {
color: $notes-light-color; color: $notes-light-color;
} }
.discussion-headline-light {
a {
color: $gl-link-color;
}
}
/** /**
* Actions for Discussions/Notes * Actions for Discussions/Notes
*/ */
...@@ -209,6 +215,17 @@ ul.notes { ...@@ -209,6 +215,17 @@ ul.notes {
color: $notes-action-color; 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, .note-action-button,
.discussion-action-button { .discussion-action-button {
display: inline-block; display: inline-block;
......
/* 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, .wiki h2, .wiki h3, .wiki h4, .wiki h5, .wiki h6 {margin-top: 17px; }
.wiki h1 {font-size: 30px;} .wiki h1 {font-size: 30px;}
.wiki h2 {font-size: 22px;} .wiki h2 {font-size: 22px;}
.wiki h3 {font-size: 18px; font-weight: bold; } .wiki h3 {font-size: 18px; font-weight: bold; }
.sidebar-wrapper { display: none; } header,
.nav { display: none; } nav,
.btn { display: none; } 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;
}
...@@ -20,8 +20,15 @@ class AutocompleteController < ApplicationController ...@@ -20,8 +20,15 @@ class AutocompleteController < ApplicationController
if params[:search].blank? if params[:search].blank?
# Include current user if available to filter by "Me" # Include current user if available to filter by "Me"
if params[:current_user] && current_user if params[:current_user] && current_user
@users = [*@users, current_user].uniq @users = [*@users, current_user]
end end
if params[:author_id].present?
author = User.find_by_id(params[:author_id])
@users = [author, *@users] if author
end
@users.uniq!
end end
render json: @users, only: [:name, :username, :id], methods: [:avatar_url] render json: @users, only: [:name, :username, :id], methods: [:avatar_url]
......
...@@ -51,6 +51,7 @@ class HelpController < ApplicationController ...@@ -51,6 +51,7 @@ class HelpController < ApplicationController
end end
def ui def ui
@user = User.new(id: 0, name: 'John Doe', username: '@johndoe')
end end
private private
......
...@@ -66,8 +66,8 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -66,8 +66,8 @@ class Projects::IssuesController < Projects::ApplicationController
end end
def show def show
@note = @project.notes.new(noteable: @issue) @note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.nonawards.with_associations.fresh @notes = @issue.notes.nonawards.with_associations.fresh
@noteable = @issue @noteable = @issue
respond_to do |format| respond_to do |format|
......
...@@ -55,6 +55,15 @@ module IssuablesHelper ...@@ -55,6 +55,15 @@ module IssuablesHelper
h(milestone_title.presence || default_label) h(milestone_title.presence || default_label)
end 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 private
def sidebar_gutter_collapsed? def sidebar_gutter_collapsed?
......
...@@ -5,30 +5,26 @@ module SelectsHelper ...@@ -5,30 +5,26 @@ module SelectsHelper
css_class << "skip_ldap " if opts[:skip_ldap] css_class << "skip_ldap " if opts[:skip_ldap]
css_class << (opts[:class] || '') css_class << (opts[:class] || '')
value = opts[:selected] || '' value = opts[:selected] || ''
placeholder = opts[:placeholder] || 'Search for a user'
null_user = opts[:null_user] || false
any_user = opts[:any_user] || false
email_user = opts[:email_user] || false
first_user = opts[:first_user] && current_user ? current_user.username : false first_user = opts[:first_user] && current_user ? current_user.username : false
current_user = opts[:current_user] || false
project = opts[:project] || @project
push_code_to_protected_branches = opts[:push_code_to_protected_branches]
html = { html = {
class: css_class, class: css_class,
data: { data: {
placeholder: placeholder, placeholder: opts[:placeholder] || 'Search for a user',
null_user: null_user, null_user: opts[:null_user] || false,
any_user: any_user, any_user: opts[:any_user] || false,
email_user: email_user, email_user: opts[:email_user] || false,
first_user: first_user, first_user: first_user,
current_user: current_user, current_user: opts[:current_user] || false,
"push-code-to-protected-branches" => push_code_to_protected_branches "push-code-to-protected-branches" => opts[:push_code_to_protected_branches],
author_id: opts[:author_id] || ''
} }
} }
unless opts[:scope] == :all unless opts[:scope] == :all
project = opts[:project] || @project
if project if project
html['data-project-id'] = project.id html['data-project-id'] = project.id
elsif @group elsif @group
......
...@@ -366,11 +366,23 @@ module Ci ...@@ -366,11 +366,23 @@ module Ci
self.update(erased_by: user, erased_at: Time.now) self.update(erased_by: user, erased_at: Time.now)
end end
private
def yaml_variables def yaml_variables
global_yaml_variables + job_yaml_variables
end
def global_yaml_variables
if commit.config_processor
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 if commit.config_processor
commit.config_processor.variables.map do |key, value| commit.config_processor.job_variables(name).map do |key, value|
{ key: key, value: value, public: true } { key: key, value: value, public: true }
end end
else else
......
...@@ -37,4 +37,10 @@ class ExternalIssue ...@@ -37,4 +37,10 @@ class ExternalIssue
def to_reference(_from_project = nil) def to_reference(_from_project = nil)
id id
end end
def reference_link_text(from_project = nil)
return "##{id}" if /^\d+$/.match(id)
id
end
end end
...@@ -90,7 +90,8 @@ class Project < ActiveRecord::Base ...@@ -90,7 +90,8 @@ class Project < ActiveRecord::Base
after_destroy :remove_pages after_destroy :remove_pages
after_update :update_forks_visibility_level after_update :update_forks_visibility_level
after_update :remove_mirror_repository_reference, if: :import_url_changed? after_update :remove_mirror_repository_reference,
if: ->(project) { project.mirror? && project.import_url_updated? }
ActsAsTaggableOn.strict_case_match = true ActsAsTaggableOn.strict_case_match = true
acts_as_taggable_on :tags acts_as_taggable_on :tags
...@@ -461,6 +462,35 @@ class Project < ActiveRecord::Base ...@@ -461,6 +462,35 @@ class Project < ActiveRecord::Base
self.import_data.destroy if self.import_data self.import_data.destroy if self.import_data
end end
def import_url=(value)
import_url = Gitlab::ImportUrl.new(value)
create_or_update_import_data(credentials: import_url.credentials)
super(import_url.sanitized_url)
end
def import_url
if import_data && super
import_url = Gitlab::ImportUrl.new(super, credentials: import_data.credentials)
import_url.full_url
else
super
end
end
def create_or_update_import_data(data: nil, credentials: nil)
project_import_data = import_data || build_import_data
if data
project_import_data.data ||= {}
project_import_data.data = project_import_data.data.merge(data)
end
if credentials
project_import_data.credentials ||= {}
project_import_data.credentials = project_import_data.credentials.merge(credentials)
end
project_import_data.save
end
def import? def import?
external_import? || forked? external_import? || forked?
end end
...@@ -997,7 +1027,9 @@ class Project < ActiveRecord::Base ...@@ -997,7 +1027,9 @@ class Project < ActiveRecord::Base
def change_head(branch) def change_head(branch)
repository.before_change_head repository.before_change_head
gitlab_shell.update_repository_head(self.path_with_namespace, branch) repository.rugged.references.create('HEAD',
"refs/heads/#{branch}",
force: true)
reload_default_branch reload_default_branch
end end
...@@ -1218,6 +1250,11 @@ class Project < ActiveRecord::Base ...@@ -1218,6 +1250,11 @@ class Project < ActiveRecord::Base
self.merge_requests_ff_only_enabled || self.merge_requests_rebase_enabled self.merge_requests_ff_only_enabled || self.merge_requests_rebase_enabled
end end
def import_url_updated?
# check if import_url has been updated and it's not just the first assignment
import_url_changed? && changes['import_url'].first
end
private private
def update_forks_visibility_level def update_forks_visibility_level
......
...@@ -12,8 +12,20 @@ require 'file_size_validator' ...@@ -12,8 +12,20 @@ require 'file_size_validator'
class ProjectImportData < ActiveRecord::Base class ProjectImportData < ActiveRecord::Base
belongs_to :project belongs_to :project
attr_encrypted :credentials,
key: Gitlab::Application.secrets.db_key_base,
marshal: true,
encode: true,
mode: :per_attribute_iv_and_salt
serialize :data, JSON serialize :data, JSON
validates :project, presence: true validates :project, presence: true
before_validation :symbolize_credentials
def symbolize_credentials
# bang doesn't work here - attr_encrypted makes it not to work
self.credentials = self.credentials.deep_symbolize_keys unless self.credentials.blank?
end
end end
...@@ -20,11 +20,13 @@ class Repository ...@@ -20,11 +20,13 @@ class Repository
delegate :push_remote_branches, :delete_remote_branches, to: :gitlab_shell delegate :push_remote_branches, :delete_remote_branches, to: :gitlab_shell
def self.clean_old_archives 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 end
def initialize(path_with_namespace, project) def initialize(path_with_namespace, project)
...@@ -183,7 +185,12 @@ class Repository ...@@ -183,7 +185,12 @@ class Repository
def rm_tag(tag_name) def rm_tag(tag_name)
before_remove_tag before_remove_tag
gitlab_shell.rm_tag(path_with_namespace, tag_name) begin
rugged.tags.delete(tag_name)
true
rescue Rugged::ReferenceError
false
end
end end
def add_remote(name, url) def add_remote(name, url)
......
module Projects module Projects
class ParticipantsService < BaseService class ParticipantsService < BaseService
def execute(note_type, note_id) def execute(noteable_type, noteable_id)
participating = @noteable_type = noteable_type
if note_type && note_id @noteable_id = noteable_id
participants_in(note_type, note_id)
else
[]
end
project_members = sorted(project.team.members) project_members = sorted(project.team.members)
participants = all_members + groups + project_members + participating participants = target_owner + participants_in_target + all_members + groups + project_members
participants.uniq participants.uniq
end end
def participants_in(type, id) def target
target = @target ||=
case type case @noteable_type
when "Issue" when "Issue"
project.issues.find_by_iid(id) project.issues.find_by_iid(@noteable_id)
when "MergeRequest" when "MergeRequest"
project.merge_requests.find_by_iid(id) project.merge_requests.find_by_iid(@noteable_id)
when "Commit" when "Commit"
project.commit(id) project.commit(@noteable_id)
else
nil
end end
end
def target_owner
return [] unless target && target.author.present?
[{
name: target.author.name,
username: target.author.username
}]
end
def participants_in_target
return [] unless target return [] unless target
users = target.participants(current_user) users = target.participants(current_user)
...@@ -30,13 +39,13 @@ module Projects ...@@ -30,13 +39,13 @@ module Projects
end end
def sorted(users) def sorted(users)
users.uniq.to_a.compact.sort_by(&:username).map do |user| users.uniq.to_a.compact.sort_by(&:username).map do |user|
{ username: user.username, name: user.name } { username: user.username, name: user.name }
end end
end end
def groups def groups
current_user.authorized_groups.sort_by(&:path).map do |group| current_user.authorized_groups.sort_by(&:path).map do |group|
count = group.users.count count = group.users.count
{ username: group.path, name: group.name, count: count } { username: group.path, name: group.name, count: count }
end end
......
...@@ -6,18 +6,17 @@ ...@@ -6,18 +6,17 @@
.login-heading .login-heading
%h3 Create an account %h3 Create an account
.login-body .login-body
- user = params[:user].present? ? params[:user] : {}
= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
.devise-errors .devise-errors
= devise_error_messages! = devise_error_messages!
%div %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 %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 %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 .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 %div
- if current_application_settings.recaptcha_enabled - if current_application_settings.recaptcha_enabled
= recaptcha_tags = recaptcha_tags
......
...@@ -345,11 +345,11 @@ ...@@ -345,11 +345,11 @@
%ul %ul
%li %li
%a.dropdown-menu-user-link.is-active{href: "#"} %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 %strong.dropdown-menu-user-full-name
= current_user.name = @user.name
.dropdown-menu-user-username .dropdown-menu-user-username
= current_user.to_reference = @user.to_reference
.example .example
%div %div
...@@ -372,11 +372,11 @@ ...@@ -372,11 +372,11 @@
%ul %ul
%li %li
%a.dropdown-menu-user-link.is-active{href: "#"} %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 %strong.dropdown-menu-user-full-name
= current_user.name = @user.name
.dropdown-menu-user-username .dropdown-menu-user-username
= current_user.to_reference = @user.to_reference
.dropdown-page-two .dropdown-page-two
.dropdown-title .dropdown-title
%button.dropdown-title-button.dropdown-menu-back{aria: {label: "Go back"}} %button.dropdown-title-button.dropdown-menu-back{aria: {label: "Go back"}}
......
...@@ -20,10 +20,10 @@ ...@@ -20,10 +20,10 @@
job.attr("id", "project_#{@project.id}") job.attr("id", "project_#{@project.id}")
target_field = job.find(".import-target") target_field = job.find(".import-target")
target_field.empty() 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) $("table.import-jobs tbody").prepend(job)
job.addClass("active").find(".import-actions").html("<i class='fa fa-spinner fa-spin'></i> started") job.addClass("active").find(".import-actions").html("<i class='fa fa-spinner fa-spin'></i> started")
- else - else
:plain :plain
job = $("tr#repo_#{@repo_id}") 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 @@ ...@@ -10,13 +10,19 @@
%hr %hr
%p %p
- if @incompatible_repos.any? - 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 - 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-responsive
.table-holder
%table.table.import-jobs %table.table.import-jobs
%colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col
%colgroup.import-jobs-status-col
%thead %thead
%tr %tr
%th From Bitbucket %th From Bitbucket
...@@ -28,7 +34,7 @@ ...@@ -28,7 +34,7 @@
%td %td
= link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: "_blank" = link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: "_blank"
%td %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 %td.job-status
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span %span
...@@ -47,7 +53,9 @@ ...@@ -47,7 +53,9 @@
%td.import-target %td.import-target
= "#{repo["owner"]}/#{repo["slug"]}" = "#{repo["owner"]}/#{repo["slug"]}"
%td.import-actions.job-status %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| - @incompatible_repos.each do |repo|
%tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"} %tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"}
%td %td
......
...@@ -13,10 +13,15 @@ ...@@ -13,10 +13,15 @@
how FogBugz email addresses and usernames are imported into GitLab. how FogBugz email addresses and usernames are imported into GitLab.
%hr %hr
%p %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 %table.table.import-jobs
%colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col
%colgroup.import-jobs-status-col
%thead %thead
%tr %tr
%th From FogBugz %th From FogBugz
...@@ -28,7 +33,7 @@ ...@@ -28,7 +33,7 @@
%td %td
= project.import_source = project.import_source
%td %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 %td.job-status
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span %span
...@@ -47,7 +52,9 @@ ...@@ -47,7 +52,9 @@
%td.import-target %td.import-target
= "#{current_user.username}/#{repo.name}" = "#{current_user.username}/#{repo.name}"
%td.import-actions.job-status %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 :javascript
new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}"); new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}");
...@@ -8,10 +8,15 @@ ...@@ -8,10 +8,15 @@
Select projects you want to import. Select projects you want to import.
%hr %hr
%p %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 %table.table.import-jobs
%colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col
%colgroup.import-jobs-status-col
%thead %thead
%tr %tr
%th From GitHub %th From GitHub
...@@ -23,7 +28,7 @@ ...@@ -23,7 +28,7 @@
%td %td
= link_to project.import_source, "https://github.com/#{project.import_source}", target: "_blank" = link_to project.import_source, "https://github.com/#{project.import_source}", target: "_blank"
%td %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 %td.job-status
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span %span
...@@ -42,7 +47,9 @@ ...@@ -42,7 +47,9 @@
%td.import-target %td.import-target
= repo.full_name = repo.full_name
%td.import-actions.job-status %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 :javascript
new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}"); new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}");
...@@ -8,10 +8,15 @@ ...@@ -8,10 +8,15 @@
Select projects you want to import. Select projects you want to import.
%hr %hr
%p %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 %table.table.import-jobs
%colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col
%colgroup.import-jobs-status-col
%thead %thead
%tr %tr
%th From GitLab.com %th From GitLab.com
...@@ -23,7 +28,7 @@ ...@@ -23,7 +28,7 @@
%td %td
= link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank" = link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank"
%td %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 %td.job-status
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span %span
...@@ -42,7 +47,9 @@ ...@@ -42,7 +47,9 @@
%td.import-target %td.import-target
= repo["path_with_namespace"] = repo["path_with_namespace"]
%td.import-actions.job-status %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 :javascript
new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}"); new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}");
...@@ -8,10 +8,15 @@ ...@@ -8,10 +8,15 @@
Select projects you want to import. Select projects you want to import.
%hr %hr
%p %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 %table.table.import-jobs
%colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col
%colgroup.import-jobs-status-col
%thead %thead
%tr %tr
%th From Gitorious.org %th From Gitorious.org
...@@ -23,7 +28,7 @@ ...@@ -23,7 +28,7 @@
%td %td
= link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank" = link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank"
%td %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 %td.job-status
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span %span
...@@ -42,7 +47,9 @@ ...@@ -42,7 +47,9 @@
%td.import-target %td.import-target
= repo.full_name = repo.full_name
%td.import-actions.job-status %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 :javascript
new ImporterStatus("#{jobs_import_gitorious_path}", "#{import_gitorious_path}"); new ImporterStatus("#{jobs_import_gitorious_path}", "#{import_gitorious_path}");
...@@ -14,12 +14,19 @@ ...@@ -14,12 +14,19 @@
%hr %hr
%p %p
- if @incompatible_repos.any? - 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 - 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 %table.table.import-jobs
%colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col
%colgroup.import-jobs-status-col
%thead %thead
%tr %tr
%th From Google Code %th From Google Code
...@@ -31,7 +38,7 @@ ...@@ -31,7 +38,7 @@
%td %td
= link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank" = link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank"
%td %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 %td.job-status
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span %span
...@@ -50,7 +57,9 @@ ...@@ -50,7 +57,9 @@
%td.import-target %td.import-target
= "#{current_user.username}/#{repo.name}" = "#{current_user.username}/#{repo.name}"
%td.import-actions.job-status %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| - @incompatible_repos.each do |repo|
%tr{id: "repo_#{repo.id}"} %tr{id: "repo_#{repo.id}"}
%td %td
......
...@@ -52,6 +52,9 @@ ...@@ -52,6 +52,9 @@
%li %li
phpunit --coverage-text --colors=never (PHP) - phpunit --coverage-text --colors=never (PHP) -
%code ^\s*Lines:\s*\d+.\d+\% %code ^\s*Lines:\s*\d+.\d+\%
%li
gcovr (C/C++) -
%code ^TOTAL.*\s+(\d+\%)$
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
......
.md-area .md-area
.md-header .md-header
%ul.nav-links %ul.nav-links.clearfix
%li.active %li.active
%a.js-md-write-button{ href: "#md-write-holder", tabindex: -1 } %a.js-md-write-button{ href: "#md-write-holder", tabindex: -1 }
Write Write
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
= cache(cache_key) do = cache(cache_key) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" } %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
.commit-row-title .commit-row-title
%span.item-title.str-truncated %span.item-title
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
- if commit.description? - if commit.description?
%a.text-expander.js-toggle-button ... %a.text-expander.js-toggle-button ...
......
- page_title "#{@issue.title} (##{@issue.iid})", "Issues" - page_title "#{@issue.title} (##{@issue.iid})", "Issues"
- page_description @issue.description - page_description @issue.description
- page_card_attributes @issue.card_attributes - page_card_attributes @issue.card_attributes
- header_title project_title(@project, "Issues", namespace_project_issues_path(@project.namespace, @project))
= render "header_title" .clearfix.detail-page-header
.issuable-header
.issuable-status-box.status-box.status-box-closed{ class: issue_button_visibility(@issue, false) }
= icon('check', class: "hidden-sm hidden-md hidden-lg")
%span.hidden-xs
Closed
.issuable-status-box.status-box.status-box-open{ class: issue_button_visibility(@issue, true) }
= icon('circle-o', class: "hidden-sm hidden-md hidden-lg")
%span.hidden-xs Open
.issue %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
.detail-page-header.issuable-header
.pull-left
.status-box{ class: "status-box-closed #{issue_button_visibility(@issue, false)}"}
%span.hidden-xs
Closed
%span.hidden-sm.hidden-md.hidden-lg
= icon('check')
.status-box{ class: "status-box-open #{issue_button_visibility(@issue, true)}"}
%span.hidden-xs
Open
%span.hidden-sm.hidden-md.hidden-lg
= icon('circle-o')
%a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.js-sidebar-toggle{ href: "#" }
= icon('angle-double-left') = icon('angle-double-left')
.issue-meta .issuable-meta
= confidential_icon(@issue) = confidential_icon(@issue)
%strong.identifier = issuable_meta(@issue, @project, "Issue")
Issue ##{@issue.iid}
%span.creator - if can?(current_user, :create_issue, @project) || can?(current_user, :update_issue, @issue)
opened .issuable-actions
.editor-details .clearfix.issue-btn-group.dropdown
.editor-details %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } }
= time_ago_with_tooltip(@issue.created_at) %span.caret
by Options
%strong .dropdown-menu.dropdown-menu-align-right.hidden-lg
= link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-xs") %ul
%strong - if can?(current_user, :create_issue, @project)
= link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg", %li
by_username: true, avatar: false) = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link'
- if can?(current_user, :update_issue, @issue)
%li
= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
%li
= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
%li
= link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue)
- if can?(current_user, :create_issue, @project)
= link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do
= icon('plus')
New issue
- if can?(current_user, :update_issue, @issue)
= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
= link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit' do
= icon('pencil-square-o')
Edit
.pull-right.issue-btn-group
- if can?(current_user, :create_issue, @project)
= link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do
= icon('plus')
New issue
- if can?(current_user, :update_issue, @issue)
= link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
= link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
= link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-nr btn-grouped issuable-edit' do
= icon('pencil-square-o')
Edit
.issue-details.issuable-details .issue-details.issuable-details
.detail-page-description.content-block .detail-page-description.content-block
%h2.title %h2.title
= markdown escape_once(@issue.title), pipeline: :single_line = markdown escape_once(@issue.title), pipeline: :single_line
%div - if @issue.description.present?
- if @issue.description.present? .description{ class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : '' }
.description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''} .wiki
.wiki = preserve do
= preserve do = markdown(@issue.description, cache_key: [@issue, "description"])
= markdown(@issue.description, cache_key: [@issue, "description"]) %textarea.hidden.js-task-list-field
%textarea.hidden.js-task-list-field = @issue.description
= @issue.description = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
#merge-requests{'data-url' => referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue)} #merge-requests{ data: { url: referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue) } }
// This element is filled in using JavaScript. // This element is filled in using JavaScript.
#related-branches{'data-url' => related_branches_namespace_project_issue_url(@project.namespace, @project, @issue)} #related-branches{ data: { url: related_branches_namespace_project_issue_url(@project.namespace, @project, @issue) } }
// This element is filled in using JavaScript. // This element is filled in using JavaScript.
.content-block.content-block-small .content-block.content-block-small
= render 'new_branch' = render 'new_branch'
= render 'votes/votes_block', votable: @issue = render 'votes/votes_block', votable: @issue
.row %section.issuable-discussion
%section.col-md-12 = render 'projects/issues/discussion'
.issuable-discussion
= render 'projects/issues/discussion'
= render 'shared/issuable/sidebar', issuable: @issue = render 'shared/issuable/sidebar', issuable: @issue
$('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}";
$('aside.right-sidebar').effect('highlight');
new IssuableContext();
...@@ -16,11 +16,9 @@ ...@@ -16,11 +16,9 @@
= dropdown_title("Select source project") = dropdown_title("Select source project")
= dropdown_filter("Search projects") = dropdown_filter("Search projects")
= dropdown_content do = dropdown_content do
- is_active = f.object.source_project_id == @merge_request.source_project.id = render 'projects/merge_requests/dropdowns/project',
%ul projects: [@merge_request.source_project],
%li selected: f.object.source_project_id
%a{ href: "#", class: "#{("is-active" if is_active)}", data: { id: @merge_request.source_project.id } }
= @merge_request.source_project_path
.merge-request-select.dropdown .merge-request-select.dropdown
= f.hidden_field :source_branch = f.hidden_field :source_branch
= dropdown_toggle "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" } = dropdown_toggle "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" }
...@@ -28,11 +26,9 @@ ...@@ -28,11 +26,9 @@
= dropdown_title("Select source branch") = dropdown_title("Select source branch")
= dropdown_filter("Search branches") = dropdown_filter("Search branches")
= dropdown_content do = dropdown_content do
%ul = render 'projects/merge_requests/dropdowns/branch',
- @merge_request.source_branches.each do |branch| branches: @merge_request.source_branches,
%li selected: f.object.source_branch
%a{ href: "#", class: "#{("is-active" if f.object.source_branch == branch)}", data: { id: branch } }
= branch
.panel-footer .panel-footer
= icon('spinner spin', class: 'js-source-loading') = icon('spinner spin', class: 'js-source-loading')
%ul.list-unstyled.mr_source_commit %ul.list-unstyled.mr_source_commit
...@@ -50,11 +46,9 @@ ...@@ -50,11 +46,9 @@
= dropdown_title("Select target project") = dropdown_title("Select target project")
= dropdown_filter("Search projects") = dropdown_filter("Search projects")
= dropdown_content do = dropdown_content do
%ul = render 'projects/merge_requests/dropdowns/project',
- projects.each do |project| projects: projects,
%li selected: f.object.target_project_id
%a{ href: "#", class: "#{("is-active" if f.object.target_project_id == project.id)}", data: { id: project.id } }
= project.path_with_namespace
.merge-request-select.dropdown .merge-request-select.dropdown
= f.hidden_field :target_branch = f.hidden_field :target_branch
= dropdown_toggle f.object.target_branch, { toggle: "dropdown", field_name: "#{f.object_name}[target_branch]" }, { toggle_class: "js-compare-dropdown js-target-branch" } = dropdown_toggle f.object.target_branch, { toggle: "dropdown", field_name: "#{f.object_name}[target_branch]" }, { toggle_class: "js-compare-dropdown js-target-branch" }
...@@ -62,11 +56,9 @@ ...@@ -62,11 +56,9 @@
= dropdown_title("Select target branch") = dropdown_title("Select target branch")
= dropdown_filter("Search branches") = dropdown_filter("Search branches")
= dropdown_content do = dropdown_content do
%ul = render 'projects/merge_requests/dropdowns/branch',
- @merge_request.target_branches.each do |branch| branches: @merge_request.target_branches,
%li selected: f.object.target_branch
%a{ href: "#", class: "#{("is-active" if f.object.target_branch == branch)}", data: { id: branch } }
= branch
.panel-footer .panel-footer
= icon('spinner spin', class: "js-target-loading") = icon('spinner spin', class: "js-target-loading")
%ul.list-unstyled.mr_target_commit %ul.list-unstyled.mr_target_commit
......
- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests" - page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
- page_description @merge_request.description - page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes - page_card_attributes @merge_request.card_attributes
- header_title project_title(@project, "Merge Requests", namespace_project_merge_requests_path(@project.namespace, @project))
= render "header_title"
- if params[:view] == 'parallel' - if params[:view] == 'parallel'
- fluid_layout true - fluid_layout true
...@@ -32,8 +31,7 @@ ...@@ -32,8 +31,7 @@
%span Request to merge %span Request to merge
%span.label-branch= source_branch_with_namespace(@merge_request) %span.label-branch= source_branch_with_namespace(@merge_request)
%span into %span into
= link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do = link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"
= @merge_request.target_branch
- if @merge_request.open? && @merge_request.diverged_from_target_branch? - if @merge_request.open? && @merge_request.diverged_from_target_branch?
%span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind) %span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind)
......
%ul
- branches.each do |branch|
%li
%a{ href: '#', class: "#{('is-active' if selected == branch)}", data: { id: branch } }
= branch
%ul
- projects.each do |project|
%li
%a{ href: "#", class: "#{('is-active' if selected == project.id)}", data: { id: project.id } }
= project.path_with_namespace
.detail-page-header .clearfix.detail-page-header
.status-box{ class: status_box_class(@merge_request) } .issuable-header
%span.hidden-xs .issuable-status-box.status-box{ class: status_box_class(@merge_request) }
= @merge_request.state_human_name = icon(@merge_request.state_icon_name, class: "hidden-sm hidden-md hidden-lg")
%span.hidden-sm.hidden-md.hidden-lg
= icon(@merge_request.state_icon_name)
%a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.js-sidebar-toggle{ href: "#" }
= icon('angle-double-left')
.issue-meta
%strong.identifier
%span.hidden-sm.hidden-md.hidden-lg
MR
%span.hidden-xs %span.hidden-xs
Merge Request = @merge_request.state_human_name
!#{@merge_request.iid}
%span.creator
opened
.editor-details
= time_ago_with_tooltip(@merge_request.created_at)
by
%strong
= link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-xs")
%strong
= link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg",
by_username: true, avatar: false)
.issue-btn-group.pull-right %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
- if can?(current_user, :update_merge_request, @merge_request) = icon('angle-double-left')
- if @merge_request.open?
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'btn btn-nr btn-grouped btn-close', title: 'Close merge request' .issuable-meta
= link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-nr btn-grouped issuable-edit', id: 'edit_merge_request' do = issuable_meta(@merge_request, @project, "Merge Request")
- if can?(current_user, :update_merge_request, @merge_request)
.issuable-actions
.clearfix.issue-btn-group.dropdown
%button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } }
%span.caret
Options
.dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul
%li{ class: issue_button_visibility(@merge_request, true) }
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request'
%li{ class: issue_button_visibility(@merge_request, false) }
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
%li
= link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit'
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-close #{issue_button_visibility(@merge_request, true)}", title: 'Close merge request'
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen reopen-mr-link #{issue_button_visibility(@merge_request, false)}", title: 'Reopen merge request'
= link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit" do
= icon('pencil-square-o') = icon('pencil-square-o')
Edit Edit
- if @merge_request.closed?
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'btn btn-nr btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request'
$('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}";
$('aside.right-sidebar').effect('highlight');
new IssuableContext();
%ul = render 'projects/merge_requests/dropdowns/branch',
- @target_branches.each do |branch| branches: @target_branches,
%li selected: nil
%a{ href: "#", class: "#{("is-active" if "a" == branch)}", data: { id: branch } }
= branch
- note = discussion_notes.first - note = discussion_notes.first
.timeline-entry %li.note.note-discussion.timeline-entry
.timeline-entry-inner .timeline-entry-inner
.timeline-icon .timeline-icon
= link_to user_path(note.author) do = link_to user_path(note.author) do
......
%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)] } - note_editable = note_editable?(note)
%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable} }
.timeline-entry-inner .timeline-entry-inner
.timeline-icon .timeline-icon
%a{href: user_path(note.author)} %a{href: user_path(note.author)}
...@@ -15,16 +16,16 @@ ...@@ -15,16 +16,16 @@
- if access - if access
%span.note-role %span.note-role
= access = access
- if note_editable?(note) - if note_editable
= link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do = link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
= icon('pencil') = icon('pencil')
= link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do
= icon('trash-o') = icon('trash-o')
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''} .note-body{class: note_editable ? 'js-task-list-container' : ''}
.note-text .note-text
= preserve do = preserve do
= markdown(note.note, pipeline: :note, cache_key: [note, "note"]) = markdown(note.note, pipeline: :note, cache_key: [note, "note"])
- if note_editable?(note) - if note_editable
= render 'projects/notes/edit_form', note: note = render 'projects/notes/edit_form', note: note
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true) = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
......
%ul#notes-list.notes.main-notes-list.timeline %ul#notes-list.notes.main-notes-list.timeline
= render "projects/notes/notes" = render "projects/notes/notes"
%ul.notes.timeline %ul.notes.notes-form.timeline
%li.timeline-entry %li.timeline-entry
- if can? current_user, :create_note, @project - if can? current_user, :create_note, @project
.timeline-icon.hidden-xs.hidden-sm .timeline-icon.hidden-xs.hidden-sm
......
...@@ -6,15 +6,11 @@ ...@@ -6,15 +6,11 @@
= "#{note.author.to_reference} started a discussion" = "#{note.author.to_reference} started a discussion"
= link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do = link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do
on the diff on the diff
= time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
.discussion-actions .discussion-actions
= link_to "#", class: "discussion-action-button discussion-toggle-button js-toggle-button" do = link_to "#", class: "discussion-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-up %i.fa.fa-chevron-up
Show/hide discussion Show/hide discussion
.last-update.hide.js-toggle-content
- last_note = discussion_notes.last
last updated by
= link_to_member(@project, last_note.author, avatar: false)
#{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content .discussion-body.js-toggle-content
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
...@@ -8,21 +8,18 @@ ...@@ -8,21 +8,18 @@
= "#{note.author.to_reference} started a discussion on #{commit_description}" = "#{note.author.to_reference} started a discussion on #{commit_description}"
- if commit - if commit
= link_to(commit.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable), class: 'monospace') = link_to(commit.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable), class: 'monospace')
= time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
.discussion-actions .discussion-actions
= link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-up %i.fa.fa-chevron-up
Show/hide discussion Show/hide discussion
.last-update.hide.js-toggle-content
- last_note = discussion_notes.last
last updated by
= link_to_member(@project, last_note.author, avatar: false)
#{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content .discussion-body.js-toggle-content
- if note.for_diff_line? - if note.for_diff_line?
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
- else - else
.panel.panel-default .panel.panel-default
.notes{ data: { discussion_id: discussion_notes.first.discussion_id } } .notes{ data: { discussion_id: discussion_notes.first.discussion_id } }
= render discussion_notes %ul.notes.timeline
= render discussion_notes
.discussion-reply-holder .discussion-reply-holder
= link_to_reply_diff(discussion_notes.first) = link_to_reply_diff(discussion_notes.first)
...@@ -5,14 +5,10 @@ ...@@ -5,14 +5,10 @@
.inline.discussion-headline-light .inline.discussion-headline-light
= "#{note.author.to_reference} started a discussion" = "#{note.author.to_reference} started a discussion"
on the outdated diff on the outdated diff
= time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "discussion_updated_ago")
.discussion-actions .discussion-actions
= link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
%i.fa.fa-chevron-down %i.fa.fa-chevron-down
Show/hide discussion Show/hide discussion
.last-update.hide.js-toggle-content
- last_note = discussion_notes.last
last updated by
= link_to_member(@project, last_note.author, avatar: false)
#{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content.hide .discussion-body.js-toggle-content.hide
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
= f.label :title, class: 'control-label' = f.label :title, class: 'control-label'
.col-sm-10 .col-sm-10
= f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off', = f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off',
class: 'form-control pad js-gfm-input', required: true class: 'form-control pad', required: true
- if issuable.is_a?(MergeRequest) - if issuable.is_a?(MergeRequest)
%p.help-block %p.help-block
......
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
.selectbox.hide-collapsed .selectbox.hide-collapsed
= f.hidden_field 'assignee_id', value: issuable.assignee_id, id: 'issue_assignee_id' = f.hidden_field 'assignee_id', value: issuable.assignee_id, id: 'issue_assignee_id'
= dropdown_tag('Select assignee', options: { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), field_name: "#{issuable.to_ability_name}[assignee_id]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } }) = dropdown_tag('Select assignee', options: { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_id]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } })
.block.milestone .block.milestone
.sidebar-collapsed-icon .sidebar-collapsed-icon
......
class AddImportCredentialsToProjectImportData < ActiveRecord::Migration
def change
add_column :project_import_data, :encrypted_credentials, :text
add_column :project_import_data, :encrypted_credentials_iv, :string
add_column :project_import_data, :encrypted_credentials_salt, :string
end
end
# Loops through old importer projects that kept a token/password in the import URL
# and encrypts the credentials into a separate field in project#import_data
# #down method not supported
class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration
class ProjectImportDataFake
extend AttrEncrypted
attr_accessor :credentials
attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base, marshal: true, encode: true, :mode => :per_attribute_iv_and_salt
end
def up
say("Encrypting and migrating project import credentials...")
# This should cover GitHub, GitLab, Bitbucket user:password, token@domain, and other similar URLs.
in_transaction(message: "Projects including GitHub and GitLab projects with an unsecured URL.") { process_projects_with_wrong_url }
in_transaction(message: "Migrating Bitbucket credentials...") { process_project(import_type: 'bitbucket', credentials_keys: ['bb_session']) }
in_transaction(message: "Migrating FogBugz credentials...") { process_project(import_type: 'fogbugz', credentials_keys: ['fb_session']) }
end
def process_projects_with_wrong_url
projects_with_wrong_import_url.each do |project|
begin
import_url = Gitlab::ImportUrl.new(project["import_url"])
update_import_url(import_url, project)
update_import_data(import_url, project)
rescue URI::InvalidURIError
nullify_import_url(project)
end
end
end
def process_project(import_type:, credentials_keys: [])
unencrypted_import_data(import_type: import_type).each do |data|
replace_data_credentials(data, credentials_keys)
end
end
def replace_data_credentials(data, credentials_keys)
data_hash = JSON.load(data['data']) if data['data']
unless data_hash.blank?
encrypted_data_hash = encrypt_data(data_hash, credentials_keys)
unencrypted_data = data_hash.empty? ? ' NULL ' : quote(data_hash.to_json)
update_with_encrypted_data(encrypted_data_hash, data['id'], unencrypted_data)
end
end
def encrypt_data(data_hash, credentials_keys)
new_data_hash = {}
credentials_keys.each do |key|
new_data_hash[key.to_sym] = data_hash.delete(key) if data_hash[key]
end
new_data_hash.deep_symbolize_keys
end
def in_transaction(message:)
say_with_time(message) do
ActiveRecord::Base.transaction do
yield
end
end
end
def update_import_data(import_url, project)
fake_import_data = ProjectImportDataFake.new
fake_import_data.credentials = import_url.credentials
import_data_id = project['import_data_id']
if import_data_id
execute(update_import_data_sql(import_data_id, fake_import_data))
else
execute(insert_import_data_sql(project['id'], fake_import_data))
end
end
def update_with_encrypted_data(data_hash, import_data_id, unencrypted_data = ' NULL ')
fake_import_data = ProjectImportDataFake.new
fake_import_data.credentials = data_hash
execute(update_import_data_sql(import_data_id, fake_import_data, unencrypted_data))
end
def update_import_url(import_url, project)
execute("UPDATE projects SET import_url = #{quote(import_url.sanitized_url)} WHERE id = #{project['id']}")
end
def nullify_import_url(project)
execute("UPDATE projects SET import_url = NULL WHERE id = #{project['id']}")
end
def insert_import_data_sql(project_id, fake_import_data)
%(
INSERT INTO project_import_data
(encrypted_credentials,
project_id,
encrypted_credentials_iv,
encrypted_credentials_salt)
VALUES ( #{quote(fake_import_data.encrypted_credentials)},
'#{project_id}',
#{quote(fake_import_data.encrypted_credentials_iv)},
#{quote(fake_import_data.encrypted_credentials_salt)})
).squish
end
def update_import_data_sql(id, fake_import_data, data = 'NULL')
%(
UPDATE project_import_data
SET encrypted_credentials = #{quote(fake_import_data.encrypted_credentials)},
encrypted_credentials_iv = #{quote(fake_import_data.encrypted_credentials_iv)},
encrypted_credentials_salt = #{quote(fake_import_data.encrypted_credentials_salt)},
data = #{data}
WHERE id = '#{id}'
).squish
end
#GitHub projects with token, and any user:password@ based URL
def projects_with_wrong_import_url
select_all("SELECT p.id, p.import_url, i.id as import_data_id FROM projects p LEFT JOIN project_import_data i on p.id = i.project_id WHERE p.import_url <> '' AND p.import_url LIKE '%//%@%'")
end
# All imports with data for import_type
def unencrypted_import_data(import_type: )
select_all("SELECT i.id, p.import_url, i.data FROM projects p INNER JOIN project_import_data i ON p.id = i.project_id WHERE p.import_url <> '' AND p.import_type = '#{import_type}' ")
end
def quote(value)
ActiveRecord::Base.connection.quote(value)
end
end
...@@ -807,6 +807,9 @@ ActiveRecord::Schema.define(version: 20160414064845) do ...@@ -807,6 +807,9 @@ ActiveRecord::Schema.define(version: 20160414064845) do
create_table "project_import_data", force: :cascade do |t| create_table "project_import_data", force: :cascade do |t|
t.integer "project_id" t.integer "project_id"
t.text "data" t.text "data"
t.text "encrypted_credentials"
t.string "encrypted_credentials_iv"
t.string "encrypted_credentials_salt"
end end
create_table "projects", force: :cascade do |t| create_table "projects", force: :cascade do |t|
......
...@@ -13,7 +13,7 @@ GitLab offers a [continuous integration][ci] service. If you ...@@ -13,7 +13,7 @@ GitLab offers a [continuous integration][ci] service. If you
and configure your GitLab project to use a [Runner], then each merge request or and configure your GitLab project to use a [Runner], then each merge request or
push triggers a build. push triggers a build.
The `.gitlab-ci.yml` file tells the GitLab runner what do to. By default it The `.gitlab-ci.yml` file tells the GitLab runner what to do. By default it
runs three [stages]: `build`, `test`, and `deploy`. runs three [stages]: `build`, `test`, and `deploy`.
If everything runs OK (no non-zero return values), you'll get a nice green If everything runs OK (no non-zero return values), you'll get a nice green
......
## Variables ## Variables
When receiving a build from GitLab CI, the runner prepares the build environment. When receiving a build from GitLab CI, the runner prepares the build environment.
It starts by setting a list of **predefined variables** (Environment Variables) and a list of **user-defined variables** It starts by setting a list of **predefined variables** (Environment Variables) and a list of **user-defined variables**
The variables can be overwritten. They take precedence over each other in this order: The variables can be overwritten. They take precedence over each other in this order:
1. Trigger variables
1. Secure variables 1. Secure variables
1. YAML-defined variables 1. YAML-defined job-level variables
1. YAML-defined global variables
1. Predefined variables 1. Predefined variables
For example, if you define: For example, if you define:
1. API_TOKEN=SECURE as Secure Variable 1. `API_TOKEN=SECURE` as Secure Variable
1. API_TOKEN=YAML as YAML-defined variable 1. `API_TOKEN=YAML` as YAML-defined variable
The API_TOKEN will take the Secure Variable value: `SECURE`. The `API_TOKEN` will take the Secure Variable value: `SECURE`.
### Predefined variables (Environment Variables) ### Predefined variables (Environment Variables)
...@@ -70,15 +73,20 @@ These variables can be later used in all executed commands and scripts. ...@@ -70,15 +73,20 @@ These variables can be later used in all executed commands and scripts.
The YAML-defined variables are also set to all created service containers, thus allowing to fine tune them. The YAML-defined variables are also set to all created service containers, thus allowing to fine tune them.
Variables can be defined at a global level, but also at a job level.
More information about Docker integration can be found in [Using Docker Images](../docker/using_docker_images.md). More information about Docker integration can be found in [Using Docker Images](../docker/using_docker_images.md).
### User-defined variables (Secure Variables) ### User-defined variables (Secure Variables)
**This feature requires GitLab Runner 0.4.0 or higher** **This feature requires GitLab Runner 0.4.0 or higher**
GitLab CI allows you to define per-project **Secure Variables** that are set in build environment. GitLab CI allows you to define per-project **Secure Variables** that are set in
the build environment.
The secure variables are stored out of the repository (the `.gitlab-ci.yml`). The secure variables are stored out of the repository (the `.gitlab-ci.yml`).
The variables are securely passed to GitLab Runner and are available in build environment. The variables are securely passed to GitLab Runner and are available in the
It's desired method to use them for storing passwords, secret keys or whatever you want. build environment.
It's desired method to use them for storing passwords, secret keys or whatever
you want.
**The value of the variable can be shown in build log if explicitly asked to do so.** **The value of the variable can be shown in build log if explicitly asked to do so.**
If your project is public or internal you can make the builds private. If your project is public or internal you can make the builds private.
......
...@@ -23,6 +23,7 @@ If you want a quick introduction to GitLab CI, follow our ...@@ -23,6 +23,7 @@ If you want a quick introduction to GitLab CI, follow our
- [Jobs](#jobs) - [Jobs](#jobs)
- [script](#script) - [script](#script)
- [stage](#stage) - [stage](#stage)
- [job variables](#job-variables)
- [only and except](#only-and-except) - [only and except](#only-and-except)
- [tags](#tags) - [tags](#tags)
- [when](#when) - [when](#when)
...@@ -174,6 +175,8 @@ These variables can be later used in all executed commands and scripts. ...@@ -174,6 +175,8 @@ These variables can be later used in all executed commands and scripts.
The YAML-defined variables are also set to all created service containers, The YAML-defined variables are also set to all created service containers,
thus allowing to fine tune them. thus allowing to fine tune them.
Variables can be also defined on [job level](#job-variables).
### cache ### cache
>**Note:** >**Note:**
...@@ -324,6 +327,7 @@ job_name: ...@@ -324,6 +327,7 @@ job_name:
| services | no | Use docker services, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) | | services | no | Use docker services, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
| stage | no | Defines a build stage (default: `test`) | | stage | no | Defines a build stage (default: `test`) |
| type | no | Alias for `stage` | | type | no | Alias for `stage` |
| variables | no | Define build variables on a job level |
| only | no | Defines a list of git refs for which build is created | | only | no | Defines a list of git refs for which build is created |
| except | no | Defines a list of git refs for which build is not created | | except | no | Defines a list of git refs for which build is not created |
| tags | no | Defines a list of tags which are used to select Runner | | tags | no | Defines a list of tags which are used to select Runner |
...@@ -414,6 +418,18 @@ job: ...@@ -414,6 +418,18 @@ job:
The above example will run `job` for all branches on `gitlab-org/gitlab-ce`, The above example will run `job` for all branches on `gitlab-org/gitlab-ce`,
except master. except master.
### job variables
It is possible to define build variables using a `variables` keyword on a job
level. It works basically the same way as its global-level equivalent but
allows you to define job-specific build variables.
When the `variables` keyword is used on a job level, it overrides global YAML
build variables and predefined variables.
Build variables priority is defined in
[variables documentation](../variables/README.md).
### tags ### tags
`tags` is used to select specific Runners from the list of all Runners that are `tags` is used to select specific Runners from the list of all Runners that are
......
...@@ -556,8 +556,8 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -556,8 +556,8 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
click_button "Submit merge request" click_button "Submit merge request"
page.within '.detail-page-header' do page.within '.issuable-actions' do
click_link "Edit" first(:link, 'Edit', visible: true).click
end end
page.within 'ul.approver-list' do page.within 'ul.approver-list' do
......
...@@ -2,7 +2,7 @@ module SharedIssuable ...@@ -2,7 +2,7 @@ module SharedIssuable
include Spinach::DSL include Spinach::DSL
def edit_issuable def edit_issuable
find(:css, '.issuable-edit').click find('.issuable-edit', visible: true).click
end end
step 'project "Community" has "Community issue" open issue' do step 'project "Community" has "Community issue" open issue' do
......
...@@ -7,9 +7,9 @@ module Ci ...@@ -7,9 +7,9 @@ module Ci
ALLOWED_YAML_KEYS = [:before_script, :image, :services, :types, :stages, :variables, :cache] ALLOWED_YAML_KEYS = [:before_script, :image, :services, :types, :stages, :variables, :cache]
ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services,
:allow_failure, :type, :stage, :when, :artifacts, :cache, :allow_failure, :type, :stage, :when, :artifacts, :cache,
:dependencies] :dependencies, :variables]
attr_reader :before_script, :image, :services, :variables, :path, :cache attr_reader :before_script, :image, :services, :path, :cache
def initialize(config, path = nil) def initialize(config, path = nil)
@config = YAML.safe_load(config, [Symbol], [], true) @config = YAML.safe_load(config, [Symbol], [], true)
...@@ -40,6 +40,17 @@ module Ci ...@@ -40,6 +40,17 @@ module Ci
@stages || DEFAULT_STAGES @stages || DEFAULT_STAGES
end end
def global_variables
@variables
end
def job_variables(name)
job = @jobs[name.to_sym]
return [] unless job
job.fetch(:variables, [])
end
private private
def initial_parsing def initial_parsing
...@@ -115,7 +126,7 @@ module Ci ...@@ -115,7 +126,7 @@ module Ci
end end
unless @variables.nil? || validate_variables(@variables) unless @variables.nil? || validate_variables(@variables)
raise ValidationError, "variables should be a map of key-valued strings" raise ValidationError, "variables should be a map of key-value strings"
end end
if @cache if @cache
...@@ -145,6 +156,7 @@ module Ci ...@@ -145,6 +156,7 @@ module Ci
validate_job_types!(name, job) validate_job_types!(name, job)
validate_job_stage!(name, job) if job[:stage] validate_job_stage!(name, job) if job[:stage]
validate_job_variables!(name, job) if job[:variables]
validate_job_cache!(name, job) if job[:cache] validate_job_cache!(name, job) if job[:cache]
validate_job_artifacts!(name, job) if job[:artifacts] validate_job_artifacts!(name, job) if job[:artifacts]
validate_job_dependencies!(name, job) if job[:dependencies] validate_job_dependencies!(name, job) if job[:dependencies]
...@@ -206,6 +218,13 @@ module Ci ...@@ -206,6 +218,13 @@ module Ci
end end
end end
def validate_job_variables!(name, job)
unless validate_variables(job[:variables])
raise ValidationError,
"#{name} job: variables should be a map of key-value strings"
end
end
def validate_job_cache!(name, job) def validate_job_cache!(name, job)
if job[:cache][:key] && !validate_string(job[:cache][:key]) if job[:cache][:key] && !validate_string(job[:cache][:key])
raise ValidationError, "#{name} job: cache:key parameter should be a string" raise ValidationError, "#{name} job: cache:key parameter should be a string"
......
...@@ -98,19 +98,6 @@ module Gitlab ...@@ -98,19 +98,6 @@ module Gitlab
"#{path}.git", "#{new_path}.git"]) "#{path}.git", "#{new_path}.git"])
end end
# Update HEAD for repository
#
# path - project path with namespace
# branch - repository branch name
#
# Ex.
# update_repository_head("gitlab/gitlab-ci", "3-1-stable")
#
def update_repository_head(path, branch)
Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'update-head',
"#{path}.git", branch])
end
# Fork repository to new namespace # Fork repository to new namespace
# #
# path - project path with namespace # path - project path with namespace
...@@ -136,33 +123,6 @@ module Gitlab ...@@ -136,33 +123,6 @@ module Gitlab
'rm-project', "#{name}.git"]) 'rm-project', "#{name}.git"])
end end
# Add repository branch from passed ref
#
# path - project path with namespace
# branch_name - new branch name
# ref - HEAD for new branch
#
# Ex.
# add_branch("gitlab/gitlab-ci", "4-0-stable", "master")
#
def add_branch(path, branch_name, ref)
Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'create-branch',
"#{path}.git", branch_name, ref])
end
# Remove repository branch
#
# path - project path with namespace
# branch_name - branch name to remove
#
# Ex.
# rm_branch("gitlab/gitlab-ci", "4-0-stable")
#
def rm_branch(path, branch_name)
Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-branch',
"#{path}.git", branch_name])
end
# Add repository tag from passed ref # Add repository tag from passed ref
# #
# path - project path with namespace # path - project path with namespace
...@@ -181,19 +141,6 @@ module Gitlab ...@@ -181,19 +141,6 @@ module Gitlab
Gitlab::Utils.system_silent(cmd) Gitlab::Utils.system_silent(cmd)
end end
# Remove repository tag
#
# path - project path with namespace
# tag_name - tag name to remove
#
# Ex.
# rm_tag("gitlab/gitlab-ci", "v4.0")
#
def rm_tag(path, tag_name)
Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-tag',
"#{path}.git", tag_name])
end
# Gc repository # Gc repository
# #
# path - project path with namespace # path - project path with namespace
......
...@@ -5,6 +5,17 @@ module Gitlab ...@@ -5,6 +5,17 @@ module Gitlab
attr_reader :consumer, :api attr_reader :consumer, :api
def self.from_project(project)
import_data_credentials = project.import_data.credentials if project.import_data
if import_data_credentials && import_data_credentials[:bb_session]
token = import_data_credentials[:bb_session][:bitbucket_access_token]
token_secret = import_data_credentials[:bb_session][:bitbucket_access_token_secret]
new(token, token_secret)
else
raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}"
end
end
def initialize(access_token = nil, access_token_secret = nil) def initialize(access_token = nil, access_token_secret = nil)
@consumer = ::OAuth::Consumer.new( @consumer = ::OAuth::Consumer.new(
config.app_id, config.app_id,
...@@ -54,7 +65,7 @@ module Gitlab ...@@ -54,7 +65,7 @@ module Gitlab
def issues(project_identifier) def issues(project_identifier)
all_issues = [] all_issues = []
offset = 0 offset = 0
per_page = 50 # Maximum number allowed by Bitbucket per_page = 50 # Maximum number allowed by Bitbucket
index = 0 index = 0
begin begin
...@@ -120,7 +131,7 @@ module Gitlab ...@@ -120,7 +131,7 @@ module Gitlab
end end
def config def config
Gitlab.config.omniauth.providers.find { |provider| provider.name == "bitbucket"} Gitlab.config.omniauth.providers.find { |provider| provider.name == "bitbucket" }
end end
def bitbucket_options def bitbucket_options
......
...@@ -5,10 +5,7 @@ module Gitlab ...@@ -5,10 +5,7 @@ module Gitlab
def initialize(project) def initialize(project)
@project = project @project = project
import_data = project.import_data.try(:data) @client = Client.from_project(@project)
bb_session = import_data["bb_session"] if import_data
@client = Client.new(bb_session["bitbucket_access_token"],
bb_session["bitbucket_access_token_secret"])
@formatter = Gitlab::ImportFormatter.new @formatter = Gitlab::ImportFormatter.new
end end
......
...@@ -6,10 +6,7 @@ module Gitlab ...@@ -6,10 +6,7 @@ module Gitlab
def initialize(project) def initialize(project)
@project = project @project = project
@current_user = project.creator @current_user = project.creator
import_data = project.import_data.try(:data) @client = Client.from_project(@project)
bb_session = import_data["bb_session"] if import_data
@client = Client.new(bb_session["bitbucket_access_token"],
bb_session["bitbucket_access_token_secret"])
end end
def execute def execute
......
...@@ -23,7 +23,8 @@ module Gitlab ...@@ -23,7 +23,8 @@ module Gitlab
import_url: "ssh://git@bitbucket.org/#{repo["owner"]}/#{repo["slug"]}.git", import_url: "ssh://git@bitbucket.org/#{repo["owner"]}/#{repo["slug"]}.git",
).execute ).execute
project.create_import_data(data: { "bb_session" => session_data } ) project.create_or_update_import_data(credentials: { bb_session: session_data })
project project
end end
end end
......
...@@ -8,17 +8,17 @@ module Gitlab ...@@ -8,17 +8,17 @@ module Gitlab
import_data = project.import_data.try(:data) import_data = project.import_data.try(:data)
repo_data = import_data['repo'] if import_data repo_data = import_data['repo'] if import_data
@repo = FogbugzImport::Repository.new(repo_data) if repo_data
@repo = FogbugzImport::Repository.new(repo_data)
@known_labels = Set.new @known_labels = Set.new
else
raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}"
end
end end
def execute def execute
return true unless repo.valid? return true unless repo.valid?
client = Gitlab::FogbugzImport::Client.new(token: fb_session[:token], uri: fb_session[:uri])
data = project.import_data.try(:data)
client = Gitlab::FogbugzImport::Client.new(token: data['fb_session']['token'], uri: data['fb_session']['uri'])
@cases = client.cases(@repo.id.to_i) @cases = client.cases(@repo.id.to_i)
@categories = client.categories @categories = client.categories
...@@ -30,6 +30,10 @@ module Gitlab ...@@ -30,6 +30,10 @@ module Gitlab
private private
def fb_session
@import_data_credentials ||= project.import_data.credentials[:fb_session] if project.import_data && project.import_data.credentials
end
def user_map def user_map
@user_map ||= begin @user_map ||= begin
user_map = Hash.new user_map = Hash.new
...@@ -236,9 +240,8 @@ module Gitlab ...@@ -236,9 +240,8 @@ module Gitlab
end end
def build_attachment_url(rel_url) def build_attachment_url(rel_url)
data = project.import_data.try(:data) uri = fb_session[:uri]
uri = data['fb_session']['uri'] token = fb_session[:token]
token = data['fb_session']['token']
"#{uri}/#{rel_url}&token=#{token}" "#{uri}/#{rel_url}&token=#{token}"
end end
......
...@@ -24,13 +24,7 @@ module Gitlab ...@@ -24,13 +24,7 @@ module Gitlab
import_url: Project::UNKNOWN_IMPORT_URL import_url: Project::UNKNOWN_IMPORT_URL
).execute ).execute
project.create_import_data( project.create_or_update_import_data(data: { 'repo' => repo.raw_data, 'user_map' => user_map }, credentials: { fb_session: fb_session })
data: {
'repo' => repo.raw_data,
'user_map' => user_map,
'fb_session' => fb_session
}
)
project project
end end
......
...@@ -7,10 +7,12 @@ module Gitlab ...@@ -7,10 +7,12 @@ module Gitlab
def initialize(project) def initialize(project)
@project = project @project = project
import_data = project.import_data.try(:data) if import_data_credentials
github_session = import_data["github_session"] if import_data @client = Client.new(import_data_credentials[:user])
@client = Client.new(github_session["github_access_token"]) @formatter = Gitlab::ImportFormatter.new
@formatter = Gitlab::ImportFormatter.new else
raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}"
end
end end
def execute def execute
...@@ -19,6 +21,10 @@ module Gitlab ...@@ -19,6 +21,10 @@ module Gitlab
private private
def import_data_credentials
@import_data_credentials ||= project.import_data.credentials if project.import_data
end
def import_issues def import_issues
client.list_issues(project.import_source, state: :all, client.list_issues(project.import_source, state: :all,
sort: :created, sort: :created,
......
...@@ -11,7 +11,7 @@ module Gitlab ...@@ -11,7 +11,7 @@ module Gitlab
end end
def execute def execute
project = ::Projects::CreateService.new( ::Projects::CreateService.new(
current_user, current_user,
name: repo.name, name: repo.name,
path: repo.name, path: repo.name,
...@@ -23,9 +23,6 @@ module Gitlab ...@@ -23,9 +23,6 @@ module Gitlab
import_url: repo.clone_url.sub("https://", "https://#{@session_data[:github_access_token]}@"), import_url: repo.clone_url.sub("https://", "https://#{@session_data[:github_access_token]}@"),
wiki_enabled: !repo.has_wiki? # If repo has wiki we'll import it later wiki_enabled: !repo.has_wiki? # If repo has wiki we'll import it later
).execute ).execute
project.create_import_data(data: { "github_session" => session_data } )
project
end end
end end
end end
......
...@@ -5,10 +5,13 @@ module Gitlab ...@@ -5,10 +5,13 @@ module Gitlab
def initialize(project) def initialize(project)
@project = project @project = project
import_data = project.import_data.try(:data) credentials = import_data
gitlab_session = import_data["gitlab_session"] if import_data if credentials && credentials[:password]
@client = Client.new(gitlab_session["gitlab_access_token"]) @client = Client.new(credentials[:password])
@formatter = Gitlab::ImportFormatter.new @formatter = Gitlab::ImportFormatter.new
else
raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}"
end
end end
def execute def execute
......
...@@ -23,7 +23,6 @@ module Gitlab ...@@ -23,7 +23,6 @@ module Gitlab
import_url: repo["http_url_to_repo"].sub("://", "://oauth2:#{@session_data[:gitlab_access_token]}@") import_url: repo["http_url_to_repo"].sub("://", "://oauth2:#{@session_data[:gitlab_access_token]}@")
).execute ).execute
project.create_import_data(data: { "gitlab_session" => session_data } )
project project
end end
end end
......
...@@ -24,12 +24,7 @@ module Gitlab ...@@ -24,12 +24,7 @@ module Gitlab
import_url: repo.import_url import_url: repo.import_url
).execute ).execute
project.create_import_data( project.create_or_update_import_data(data: { 'repo' => repo.raw_data, 'user_map' => user_map })
data: {
"repo" => repo.raw_data,
"user_map" => user_map
}
)
project project
end end
......
# I'm borrowing this class from: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3066
# So we should fix the conflict once the CE -> EE merge process starts.
module Gitlab module Gitlab
class ImportUrl class ImportUrl
def initialize(url, credentials: nil) def initialize(url, credentials: nil)
@url = URI.parse(url) @url = URI.parse(URI.encode(url))
@credentials = credentials @credentials = credentials
end end
......
...@@ -11,6 +11,8 @@ module Gitlab ...@@ -11,6 +11,8 @@ module Gitlab
module Instrumentation module Instrumentation
SERIES = 'method_calls' SERIES = 'method_calls'
PROXY_IVAR = :@__gitlab_instrumentation_proxy
def self.configure def self.configure
yield self yield self
end end
...@@ -91,6 +93,18 @@ module Gitlab ...@@ -91,6 +93,18 @@ module Gitlab
end end
end end
# Returns true if a module is instrumented.
#
# mod - The module to check
def self.instrumented?(mod)
mod.instance_variable_defined?(PROXY_IVAR)
end
# Returns the proxy module (if any) of `mod`.
def self.proxy_module(mod)
mod.instance_variable_get(PROXY_IVAR)
end
# Instruments a method. # Instruments a method.
# #
# type - The type (:class or :instance) of method to instrument. # type - The type (:class or :instance) of method to instrument.
...@@ -99,9 +113,8 @@ module Gitlab ...@@ -99,9 +113,8 @@ module Gitlab
def self.instrument(type, mod, name) def self.instrument(type, mod, name)
return unless Metrics.enabled? return unless Metrics.enabled?
name = name.to_sym name = name.to_sym
alias_name = :"_original_#{name}" target = type == :instance ? mod : mod.singleton_class
target = type == :instance ? mod : mod.singleton_class
if type == :instance if type == :instance
target = mod target = mod
...@@ -113,6 +126,12 @@ module Gitlab ...@@ -113,6 +126,12 @@ module Gitlab
method = mod.method(name) method = mod.method(name)
end end
unless instrumented?(target)
target.instance_variable_set(PROXY_IVAR, Module.new)
end
proxy_module = self.proxy_module(target)
# Some code out there (e.g. the "state_machine" Gem) checks the arity of # Some code out there (e.g. the "state_machine" Gem) checks the arity of
# a method to make sure it only passes arguments when the method expects # a method to make sure it only passes arguments when the method expects
# any. If we were to always overwrite a method to take an `*args` # any. If we were to always overwrite a method to take an `*args`
...@@ -125,17 +144,13 @@ module Gitlab ...@@ -125,17 +144,13 @@ module Gitlab
args_signature = '*args, &block' args_signature = '*args, &block'
end end
send_signature = "__send__(#{alias_name.inspect}, #{args_signature})" proxy_module.class_eval <<-EOF, __FILE__, __LINE__ + 1
target.class_eval <<-EOF, __FILE__, __LINE__ + 1
alias_method #{alias_name.inspect}, #{name.inspect}
def #{name}(#{args_signature}) def #{name}(#{args_signature})
trans = Gitlab::Metrics::Instrumentation.transaction trans = Gitlab::Metrics::Instrumentation.transaction
if trans if trans
start = Time.now start = Time.now
retval = #{send_signature} retval = super
duration = (Time.now - start) * 1000.0 duration = (Time.now - start) * 1000.0
if duration >= Gitlab::Metrics.method_call_threshold if duration >= Gitlab::Metrics.method_call_threshold
...@@ -148,10 +163,12 @@ module Gitlab ...@@ -148,10 +163,12 @@ module Gitlab
retval retval
else else
#{send_signature} super
end end
end end
EOF EOF
target.prepend(proxy_module)
end end
# Small layer of indirection to make it easier to stub out the current # Small layer of indirection to make it easier to stub out the current
......
...@@ -9,6 +9,7 @@ module Gitlab ...@@ -9,6 +9,7 @@ module Gitlab
return unless current_transaction return unless current_transaction
current_transaction.increment(:sql_duration, event.duration) current_transaction.increment(:sql_duration, event.duration)
current_transaction.increment(:sql_count, 1)
end end
private private
......
...@@ -13,13 +13,13 @@ describe AutocompleteController do ...@@ -13,13 +13,13 @@ describe AutocompleteController do
project.team << [user2, :developer] project.team << [user2, :developer]
end end
let(:body) { JSON.parse(response.body) }
describe 'GET #users with project ID' do describe 'GET #users with project ID' do
before do before do
get(:users, project_id: project.id) get(:users, project_id: project.id)
end end
let(:body) { JSON.parse(response.body) }
it { expect(body).to be_kind_of(Array) } it { expect(body).to be_kind_of(Array) }
it { expect(body.size).to eq 2 } it { expect(body.size).to eq 2 }
it { expect(body.map { |u| u["username"] }).to include(user.username) } it { expect(body.map { |u| u["username"] }).to include(user.username) }
...@@ -38,6 +38,8 @@ describe AutocompleteController do ...@@ -38,6 +38,8 @@ describe AutocompleteController do
get(:users, project_id: project.id, push_code_to_protected_branches: 'true') get(:users, project_id: project.id, push_code_to_protected_branches: 'true')
end end
let(:body) { JSON.parse(response.body) }
it { expect(body).to be_kind_of(Array) } it { expect(body).to be_kind_of(Array) }
it { expect(body.size).to eq 1 } it { expect(body.size).to eq 1 }
it { expect(body.first["username"]).to eq user.username } it { expect(body.first["username"]).to eq user.username }
...@@ -154,4 +156,24 @@ describe AutocompleteController do ...@@ -154,4 +156,24 @@ describe AutocompleteController do
it { expect(body.size).to eq 0 } it { expect(body.size).to eq 0 }
end end
end end
context 'author of issuable included' do
before do
sign_in(user)
end
let(:body) { JSON.parse(response.body) }
it 'includes the author' do
get(:users, author_id: non_member.id)
expect(body.first["username"]).to eq non_member.username
end
it 'rejects non existent user ids' do
get(:users, author_id: 99999)
expect(body.collect { |u| u['id'] }).not_to include(99999)
end
end
end end
...@@ -42,11 +42,9 @@ feature 'issue move to another project' do ...@@ -42,11 +42,9 @@ feature 'issue move to another project' do
expect(current_url).to include project_path(new_project) expect(current_url).to include project_path(new_project)
page.within('.issue') do expect(page).to have_content("Text with #{cross_reference}!1")
expect(page).to have_content("Text with #{cross_reference}!1") expect(page).to have_content("Moved from #{cross_reference}#1")
expect(page).to have_content("Moved from #{cross_reference}#1") expect(page).to have_content(issue.title)
expect(page).to have_content(issue.title)
end
end end
context 'projects user does not have permission to move issue to exist' do context 'projects user does not have permission to move issue to exist' do
...@@ -74,7 +72,7 @@ feature 'issue move to another project' do ...@@ -74,7 +72,7 @@ feature 'issue move to another project' do
def edit_issue(issue) def edit_issue(issue)
visit issue_path(issue) visit issue_path(issue)
page.within('.issuable-header') { click_link 'Edit' } page.within('.issuable-actions') { first(:link, 'Edit').click }
end end
def issue_path(issue) def issue_path(issue)
......
...@@ -313,6 +313,23 @@ describe 'Issues', feature: true do ...@@ -313,6 +313,23 @@ describe 'Issues', feature: true do
end end
end end
describe 'new issue' do
context 'dropzone upload file', js: true do
before do
visit new_namespace_project_issue_path(project.namespace, project)
end
it 'should upload file when dragging into textarea' do
drop_in_dropzone test_image_file
# Wait for the file to upload
sleep 1
expect(page.find_field("issue_description").value).to have_content 'banana_sample'
end
end
end
def first_issue def first_issue
page.all('ul.issues-list > li').first.text page.all('ul.issues-list > li').first.text
end end
...@@ -320,4 +337,25 @@ describe 'Issues', feature: true do ...@@ -320,4 +337,25 @@ describe 'Issues', feature: true do
def last_issue def last_issue
page.all('ul.issues-list > li').last.text page.all('ul.issues-list > li').last.text
end end
def drop_in_dropzone(file_path)
# Generate a fake input selector
page.execute_script <<-JS
var fakeFileInput = window.$('<input/>').attr(
{id: 'fakeFileInput', type: 'file'}
).appendTo('body');
JS
# Attach the file to the fake input selector with Capybara
attach_file("fakeFileInput", file_path)
# Add the file to a fileList array and trigger the fake drop event
page.execute_script <<-JS
var fileList = [$('#fakeFileInput')[0].files[0]];
var e = jQuery.Event('drop', { dataTransfer : { files : fileList } });
$('.div-dropzone')[0].dropzone.listeners[0].events.drop(e);
JS
end
def test_image_file
File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')
end
end end
require 'spec_helper'
feature 'Member autocomplete', feature: true do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
let(:participant) { create(:user) }
let(:author) { create(:user) }
before do
allow_any_instance_of(Commit).to receive(:author).and_return(author)
login_as user
end
shared_examples "open suggestions" do
it 'suggestions are displayed' do
expect(page).to have_selector('.atwho-view', visible: true)
end
it 'author is suggested' do
page.within('.atwho-view', visible: true) do
expect(page).to have_content(author.username)
end
end
it 'participant is suggested' do
page.within('.atwho-view', visible: true) do
expect(page).to have_content(participant.username)
end
end
end
context 'adding a new note on a Issue', js: true do
before do
issue = create(:issue, author: author, project: project)
create(:note, note: 'Ultralight Beam', noteable: issue, author: participant)
visit_issue(project, issue)
end
context 'when typing @' do
include_examples "open suggestions"
before do
open_member_suggestions
end
end
end
context 'adding a new note on a Merge Request ', js: true do
before do
merge = create(:merge_request, source_project: project, target_project: project, author: author)
create(:note, note: 'Ultralight Beam', noteable: merge, author: participant)
visit_merge_request(project, merge)
end
context 'when typing @' do
include_examples "open suggestions"
before do
open_member_suggestions
end
end
end
context 'adding a new note on a Commit ', js: true do
let(:commit) { project.commit }
before do
allow(commit).to receive(:author).and_return(author)
create(:note_on_commit, author: participant, project: project, commit_id: project.repository.commit.id, note: 'No More Parties in LA')
visit_commit(project, commit)
end
context 'when typing @' do
include_examples "open suggestions"
before do
open_member_suggestions
end
end
end
def open_member_suggestions
sleep 1
page.within('.new-note') do
sleep 1
find('#note_note').native.send_keys('@')
end
end
def visit_issue(project, issue)
visit namespace_project_issue_path(project.namespace, project, issue)
end
def visit_merge_request(project, merge)
visit namespace_project_merge_request_path(project.namespace, project, merge)
end
def visit_commit(project, commit)
visit namespace_project_commit_path(project.namespace, project, commit)
end
end
require 'spec_helper'
feature 'Signup', feature: true do
describe 'signup with no errors' do
it 'creates the user account and sends a confirmation email' do
user = build(:user)
visit root_path
fill_in 'user_name', with: user.name
fill_in 'user_username', with: user.username
fill_in 'user_email', with: user.email
fill_in 'user_password_sign_up', with: user.password
click_button "Sign up"
expect(current_path).to eq user_session_path
expect(page).to have_content("A message with a confirmation link has been sent to your email address.")
end
end
describe 'signup with errors' do
it "displays the errors" do
existing_user = create(:user)
user = build(:user)
visit root_path
fill_in 'user_name', with: user.name
fill_in 'user_username', with: user.username
fill_in 'user_email', with: existing_user.email
fill_in 'user_password_sign_up', with: user.password
click_button "Sign up"
expect(current_path).to eq user_registration_path
expect(page).to have_content("error prohibited this user from being saved")
expect(page).to have_content("Email has already been taken")
end
it 'does not redisplay the password' do
existing_user = create(:user)
user = build(:user)
visit root_path
fill_in 'user_name', with: user.name
fill_in 'user_username', with: user.username
fill_in 'user_email', with: existing_user.email
fill_in 'user_password_sign_up', with: user.password
click_button "Sign up"
expect(current_path).to eq user_registration_path
expect(page.body).not_to match(/#{user.password}/)
end
end
end
...@@ -345,20 +345,76 @@ module Ci ...@@ -345,20 +345,76 @@ module Ci
end end
end end
describe "Variables" do describe 'Variables' do
it "returns variables when defined" do context 'when global variables are defined' do
variables = { it 'returns global variables' do
var1: "value1", variables = {
var2: "value2", VAR1: 'value1',
} VAR2: 'value2',
config = YAML.dump({ }
variables: variables,
before_script: ["pwd"],
rspec: { script: "rspec" }
})
config_processor = GitlabCiYamlProcessor.new(config, path) config = YAML.dump({
expect(config_processor.variables).to eq(variables) variables: variables,
before_script: ['pwd'],
rspec: { script: 'rspec' }
})
config_processor = GitlabCiYamlProcessor.new(config, path)
expect(config_processor.global_variables).to eq(variables)
end
end
context 'when job variables are defined' do
context 'when syntax is correct' do
it 'returns job variables' do
variables = {
KEY1: 'value1',
SOME_KEY_2: 'value2'
}
config = YAML.dump(
{ before_script: ['pwd'],
rspec: {
variables: variables,
script: 'rspec' }
})
config_processor = GitlabCiYamlProcessor.new(config, path)
expect(config_processor.job_variables(:rspec)).to eq variables
end
end
context 'when syntax is incorrect' do
it 'raises error' do
variables = [:KEY1, 'value1', :KEY2, 'value2']
config = YAML.dump(
{ before_script: ['pwd'],
rspec: {
variables: variables,
script: 'rspec' }
})
expect { GitlabCiYamlProcessor.new(config, path) }
.to raise_error(GitlabCiYamlProcessor::ValidationError,
/job: variables should be a map/)
end
end
end
context 'when job variables are not defined' do
it 'returns empty array' do
config = YAML.dump({
before_script: ['pwd'],
rspec: { script: 'rspec' }
})
config_processor = GitlabCiYamlProcessor.new(config, path)
expect(config_processor.job_variables(:rspec)).to eq []
end
end end
end end
...@@ -730,14 +786,14 @@ EOT ...@@ -730,14 +786,14 @@ EOT
config = YAML.dump({ variables: "test", rspec: { script: "test" } }) config = YAML.dump({ variables: "test", rspec: { script: "test" } })
expect do expect do
GitlabCiYamlProcessor.new(config, path) GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-valued strings") end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-value strings")
end end
it "returns errors if variables is not a map of key-valued strings" do it "returns errors if variables is not a map of key-value strings" do
config = YAML.dump({ variables: { test: false }, rspec: { script: "test" } }) config = YAML.dump({ variables: { test: false }, rspec: { script: "test" } })
expect do expect do
GitlabCiYamlProcessor.new(config, path) GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-valued strings") end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-value strings")
end end
it "returns errors if job when is not on_success, on_failure or always" do it "returns errors if job when is not on_success, on_failure or always" do
......
...@@ -34,9 +34,9 @@ describe Gitlab::BitbucketImport::Importer, lib: true do ...@@ -34,9 +34,9 @@ describe Gitlab::BitbucketImport::Importer, lib: true do
let(:project_identifier) { 'namespace/repo' } let(:project_identifier) { 'namespace/repo' }
let(:data) do let(:data) do
{ {
bb_session: { 'bb_session' => {
bitbucket_access_token: "123456", 'bitbucket_access_token' => "123456",
bitbucket_access_token_secret: "secret" 'bitbucket_access_token_secret' => "secret"
} }
} }
end end
...@@ -44,7 +44,7 @@ describe Gitlab::BitbucketImport::Importer, lib: true do ...@@ -44,7 +44,7 @@ describe Gitlab::BitbucketImport::Importer, lib: true do
create( create(
:project, :project,
import_source: project_identifier, import_source: project_identifier,
import_data: ProjectImportData.new(data: data) import_data: ProjectImportData.new(credentials: data)
) )
end end
let(:importer) { Gitlab::BitbucketImport::Importer.new(project) } let(:importer) { Gitlab::BitbucketImport::Importer.new(project) }
......
...@@ -12,7 +12,7 @@ describe Gitlab::GithubImport::ProjectCreator, lib: true do ...@@ -12,7 +12,7 @@ describe Gitlab::GithubImport::ProjectCreator, lib: true do
owner: OpenStruct.new(login: "john") owner: OpenStruct.new(login: "john")
) )
end end
let(:namespace){ create(:group, owner: user) } let(:namespace) { create(:group, owner: user) }
let(:token) { "asdffg" } let(:token) { "asdffg" }
let(:access_params) { { github_access_token: token } } let(:access_params) { { github_access_token: token } }
...@@ -27,6 +27,8 @@ describe Gitlab::GithubImport::ProjectCreator, lib: true do ...@@ -27,6 +27,8 @@ describe Gitlab::GithubImport::ProjectCreator, lib: true do
project = project_creator.execute project = project_creator.execute
expect(project.import_url).to eq("https://asdffg@gitlab.com/asd/vim.git") expect(project.import_url).to eq("https://asdffg@gitlab.com/asd/vim.git")
expect(project.safe_import_url).to eq("https://*****@gitlab.com/asd/vim.git")
expect(project.import_data.credentials).to eq(user: "asdffg", password: nil)
expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
end end
end end
...@@ -2,11 +2,12 @@ require 'spec_helper' ...@@ -2,11 +2,12 @@ require 'spec_helper'
describe Gitlab::GithubImport::WikiFormatter, lib: true do describe Gitlab::GithubImport::WikiFormatter, lib: true do
let(:project) do let(:project) do
create(:project, namespace: create(:namespace, path: 'gitlabhq'), create(:project,
import_url: 'https://xxx@github.com/gitlabhq/sample.gitlabhq.git') namespace: create(:namespace, path: 'gitlabhq'),
import_url: 'https://xxx@github.com/gitlabhq/sample.gitlabhq.git')
end end
subject(:wiki) { described_class.new(project)} subject(:wiki) { described_class.new(project) }
describe '#path_with_namespace' do describe '#path_with_namespace' do
it 'appends .wiki to project path' do it 'appends .wiki to project path' do
......
require 'spec_helper'
describe Gitlab::ImportUrl do
let(:credentials) { { user: 'blah', password: 'password' } }
let(:import_url) do
Gitlab::ImportUrl.new("https://github.com/me/project.git", credentials: credentials)
end
describe :full_url do
it { expect(import_url.full_url).to eq("https://blah:password@github.com/me/project.git") }
end
describe :sanitized_url do
it { expect(import_url.sanitized_url).to eq("https://github.com/me/project.git") }
end
describe :credentials do
it { expect(import_url.credentials).to eq(credentials) }
end
end
...@@ -33,8 +33,16 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -33,8 +33,16 @@ describe Gitlab::Metrics::Instrumentation do
described_class.instrument_method(@dummy, :foo) described_class.instrument_method(@dummy, :foo)
end end
it 'renames the original method' do it 'instruments the Class' do
expect(@dummy).to respond_to(:_original_foo) target = @dummy.singleton_class
expect(described_class.instrumented?(target)).to eq(true)
end
it 'defines a proxy method' do
mod = described_class.proxy_module(@dummy.singleton_class)
expect(mod.method_defined?(:foo)).to eq(true)
end end
it 'calls the instrumented method with the correct arguments' do it 'calls the instrumented method with the correct arguments' do
...@@ -76,6 +84,14 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -76,6 +84,14 @@ describe Gitlab::Metrics::Instrumentation do
expect(dummy.method(:test).arity).to eq(0) expect(dummy.method(:test).arity).to eq(0)
end end
describe 'when a module is instrumented multiple times' do
it 'calls the instrumented method with the correct arguments' do
described_class.instrument_method(@dummy, :foo)
expect(@dummy.foo).to eq('foo')
end
end
end end
describe 'with metrics disabled' do describe 'with metrics disabled' do
...@@ -86,7 +102,9 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -86,7 +102,9 @@ describe Gitlab::Metrics::Instrumentation do
it 'does not instrument the method' do it 'does not instrument the method' do
described_class.instrument_method(@dummy, :foo) described_class.instrument_method(@dummy, :foo)
expect(@dummy).to_not respond_to(:_original_foo) target = @dummy.singleton_class
expect(described_class.instrumented?(target)).to eq(false)
end end
end end
end end
...@@ -100,8 +118,14 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -100,8 +118,14 @@ describe Gitlab::Metrics::Instrumentation do
instrument_instance_method(@dummy, :bar) instrument_instance_method(@dummy, :bar)
end end
it 'renames the original method' do it 'instruments instances of the Class' do
expect(@dummy.method_defined?(:_original_bar)).to eq(true) expect(described_class.instrumented?(@dummy)).to eq(true)
end
it 'defines a proxy method' do
mod = described_class.proxy_module(@dummy)
expect(mod.method_defined?(:bar)).to eq(true)
end end
it 'calls the instrumented method with the correct arguments' do it 'calls the instrumented method with the correct arguments' do
...@@ -144,7 +168,7 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -144,7 +168,7 @@ describe Gitlab::Metrics::Instrumentation do
described_class. described_class.
instrument_instance_method(@dummy, :bar) instrument_instance_method(@dummy, :bar)
expect(@dummy.method_defined?(:_original_bar)).to eq(false) expect(described_class.instrumented?(@dummy)).to eq(false)
end end
end end
end end
...@@ -167,18 +191,17 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -167,18 +191,17 @@ describe Gitlab::Metrics::Instrumentation do
it 'recursively instruments a class hierarchy' do it 'recursively instruments a class hierarchy' do
described_class.instrument_class_hierarchy(@dummy) described_class.instrument_class_hierarchy(@dummy)
expect(@child1).to respond_to(:_original_child1_foo) expect(described_class.instrumented?(@child1.singleton_class)).to eq(true)
expect(@child2).to respond_to(:_original_child2_foo) expect(described_class.instrumented?(@child2.singleton_class)).to eq(true)
expect(@child1.method_defined?(:_original_child1_bar)).to eq(true) expect(described_class.instrumented?(@child1)).to eq(true)
expect(@child2.method_defined?(:_original_child2_bar)).to eq(true) expect(described_class.instrumented?(@child2)).to eq(true)
end end
it 'does not instrument the root module' do it 'does not instrument the root module' do
described_class.instrument_class_hierarchy(@dummy) described_class.instrument_class_hierarchy(@dummy)
expect(@dummy).to_not respond_to(:_original_foo) expect(described_class.instrumented?(@dummy)).to eq(false)
expect(@dummy.method_defined?(:_original_bar)).to eq(false)
end end
end end
...@@ -190,7 +213,7 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -190,7 +213,7 @@ describe Gitlab::Metrics::Instrumentation do
it 'instruments all public class methods' do it 'instruments all public class methods' do
described_class.instrument_methods(@dummy) described_class.instrument_methods(@dummy)
expect(@dummy).to respond_to(:_original_foo) expect(described_class.instrumented?(@dummy.singleton_class)).to eq(true)
end end
it 'only instruments methods directly defined in the module' do it 'only instruments methods directly defined in the module' do
...@@ -223,7 +246,7 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -223,7 +246,7 @@ describe Gitlab::Metrics::Instrumentation do
it 'instruments all public instance methods' do it 'instruments all public instance methods' do
described_class.instrument_instance_methods(@dummy) described_class.instrument_instance_methods(@dummy)
expect(@dummy.method_defined?(:_original_bar)).to eq(true) expect(described_class.instrumented?(@dummy)).to eq(true)
end end
it 'only instruments methods directly defined in the module' do it 'only instruments methods directly defined in the module' do
......
...@@ -28,6 +28,9 @@ describe Gitlab::Metrics::Subscribers::ActiveRecord do ...@@ -28,6 +28,9 @@ describe Gitlab::Metrics::Subscribers::ActiveRecord do
expect(transaction).to receive(:increment). expect(transaction).to receive(:increment).
with(:sql_duration, 0.2) with(:sql_duration, 0.2)
expect(transaction).to receive(:increment).
with(:sql_count, 1)
subscriber.sql(event) subscriber.sql(event)
end end
end end
......
...@@ -238,6 +238,22 @@ describe Ci::Build, models: true do ...@@ -238,6 +238,22 @@ describe Ci::Build, models: true do
it { is_expected.to eq(predefined_variables + predefined_trigger_variable + yaml_variables + secure_variables + trigger_variables) } it { is_expected.to eq(predefined_variables + predefined_trigger_variable + yaml_variables + secure_variables + trigger_variables) }
end end
context 'when job variables are defined' do
##
# Job-level variables are defined in gitlab_ci.yml fixture
#
context 'when job variables are unique' do
let(:build) { create(:ci_build, name: 'staging') }
it 'includes job variables' do
expect(subject).to include(
{ key: :KEY1, value: 'value1', public: true },
{ key: :KEY2, value: 'value2', public: true }
)
end
end
end
end end
end end
end end
......
...@@ -36,4 +36,19 @@ describe ExternalIssue, models: true do ...@@ -36,4 +36,19 @@ describe ExternalIssue, models: true do
expect(issue.title).to eq "External Issue #{issue}" expect(issue.title).to eq "External Issue #{issue}"
end end
end end
describe '#reference_link_text' do
context 'if issue id has a prefix' do
it 'returns the issue ID' do
expect(issue.reference_link_text).to eq 'EXT-1234'
end
end
context 'if issue id is a number' do
let(:issue) { described_class.new('1234', project) }
it 'returns the issue ID prefixed by #' do
expect(issue.reference_link_text).to eq '#1234'
end
end
end
end end
...@@ -816,11 +816,9 @@ describe Repository, models: true do ...@@ -816,11 +816,9 @@ describe Repository, models: true do
describe '#rm_tag' do describe '#rm_tag' do
it 'removes a tag' do it 'removes a tag' do
expect(repository).to receive(:before_remove_tag) expect(repository).to receive(:before_remove_tag)
expect(repository.rugged.tags).to receive(:delete).with('v1.1.0')
expect_any_instance_of(Gitlab::Shell).to receive(:rm_tag). repository.rm_tag('v1.1.0')
with(repository.path_with_namespace, '8.5')
repository.rm_tag('8.5')
end end
end end
...@@ -1022,6 +1020,30 @@ describe Repository, models: true do ...@@ -1022,6 +1020,30 @@ describe Repository, models: true do
end end
end end
describe '.clean_old_archives' do
let(:path) { Gitlab.config.gitlab.repository_downloads_path }
context 'when the downloads directory does not exist' do
it 'does not remove any archives' do
expect(File).to receive(:directory?).with(path).and_return(false)
expect(Gitlab::Popen).not_to receive(:popen)
described_class.clean_old_archives
end
end
context 'when the downloads directory exists' do
it 'removes old archives' do
expect(File).to receive(:directory?).with(path).and_return(true)
expect(Gitlab::Popen).to receive(:popen)
described_class.clean_old_archives
end
end
end
def create_remote_branch(remote_name, branch_name, target) def create_remote_branch(remote_name, branch_name, target)
rugged = repository.rugged rugged = repository.rugged
rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target) rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target)
......
...@@ -6,21 +6,12 @@ describe DeleteTagService, services: true do ...@@ -6,21 +6,12 @@ describe DeleteTagService, services: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:service) { described_class.new(project, user) } let(:service) { described_class.new(project, user) }
let(:tag) { double(:tag, name: '8.5', target: 'abc123') }
describe '#execute' do describe '#execute' do
before do
allow(repository).to receive(:find_tag).and_return(tag)
end
it 'removes the tag' do it 'removes the tag' do
expect_any_instance_of(Gitlab::Shell).to receive(:rm_tag).
and_return(true)
expect(repository).to receive(:before_remove_tag) expect(repository).to receive(:before_remove_tag)
expect(service).to receive(:success) expect(service).to receive(:success)
service.execute('8.5') service.execute('v1.1.0')
end end
end end
end end
...@@ -4,7 +4,7 @@ services: ...@@ -4,7 +4,7 @@ services:
before_script: before_script:
- gem install bundler - gem install bundler
- bundle install - bundle install
- bundle exec rake db:create - bundle exec rake db:create
variables: variables:
...@@ -17,7 +17,7 @@ types: ...@@ -17,7 +17,7 @@ types:
rspec: rspec:
script: "rake spec" script: "rake spec"
tags: tags:
- ruby - ruby
- postgres - postgres
only: only:
...@@ -26,27 +26,32 @@ rspec: ...@@ -26,27 +26,32 @@ rspec:
spinach: spinach:
script: "rake spinach" script: "rake spinach"
allow_failure: true allow_failure: true
tags: tags:
- ruby - ruby
- mysql - mysql
except: except:
- tags - tags
staging: staging:
variables:
KEY1: value1
KEY2: value2
script: "cap deploy stating" script: "cap deploy stating"
type: deploy type: deploy
tags: tags:
- ruby - ruby
- mysql - mysql
except: except:
- stable - stable
production: production:
variables:
DB_NAME: mysql
type: deploy type: deploy
script: script:
- cap deploy production - cap deploy production
- cap notify - cap notify
tags: tags:
- ruby - ruby
- mysql - mysql
only: only:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment