summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG14
-rw-r--r--app/assets/javascripts/api.js.coffee7
-rw-r--r--app/assets/javascripts/application.js.coffee40
-rw-r--r--app/assets/javascripts/blob/blob_ci_yaml.js.coffee23
-rw-r--r--app/assets/javascripts/blob/edit_blob.js.coffee1
-rw-r--r--app/assets/javascripts/gl_dropdown.js.coffee12
-rw-r--r--app/assets/javascripts/gl_form.js.coffee3
-rw-r--r--app/assets/javascripts/lib/text_utility.js.coffee79
-rw-r--r--app/assets/javascripts/notes.js.coffee5
-rw-r--r--app/assets/javascripts/notifications_dropdown.js.coffee7
-rw-r--r--app/assets/javascripts/project.js.coffee38
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/blank.scss23
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss30
-rw-r--r--app/assets/stylesheets/framework/mixins.scss14
-rw-r--r--app/assets/stylesheets/framework/nav.scss18
-rw-r--r--app/assets/stylesheets/framework/panels.scss4
-rw-r--r--app/assets/stylesheets/framework/selects.scss5
-rw-r--r--app/assets/stylesheets/pages/commits.scss12
-rw-r--r--app/assets/stylesheets/pages/diff.scss5
-rw-r--r--app/assets/stylesheets/pages/editor.scss11
-rw-r--r--app/assets/stylesheets/pages/help.scss7
-rw-r--r--app/assets/stylesheets/pages/issuable.scss7
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss15
-rw-r--r--app/assets/stylesheets/pages/note_form.scss17
-rw-r--r--app/assets/stylesheets/pages/notes.scss23
-rw-r--r--app/assets/stylesheets/pages/projects.scss31
-rw-r--r--app/controllers/admin/runner_projects_controller.rb11
-rw-r--r--app/controllers/projects/application_controller.rb2
-rw-r--r--app/controllers/projects/pipelines_controller.rb2
-rw-r--r--app/controllers/projects/runner_projects_controller.rb4
-rw-r--r--app/controllers/projects/runners_controller.rb7
-rw-r--r--app/controllers/projects_controller.rb28
-rw-r--r--app/helpers/application_helper.rb18
-rw-r--r--app/helpers/blob_helper.rb18
-rw-r--r--app/helpers/branches_helper.rb2
-rw-r--r--app/helpers/gitlab_markdown_helper.rb13
-rw-r--r--app/mailers/emails/members.rb5
-rw-r--r--app/models/ci/build.rb8
-rw-r--r--app/models/ci/pipeline.rb8
-rw-r--r--app/models/ci/runner.rb23
-rw-r--r--app/models/commit.rb26
-rw-r--r--app/models/commit_status.rb2
-rw-r--r--app/models/concerns/participable.rb10
-rw-r--r--app/models/repository.rb11
-rw-r--r--app/models/user.rb3
-rw-r--r--app/services/ci/register_build_service.rb2
-rw-r--r--app/views/admin/runners/show.html.haml4
-rw-r--r--app/views/dashboard/_projects_head.html.haml2
-rw-r--r--app/views/groups/show.html.haml2
-rw-r--r--app/views/help/_shortcuts.html.haml6
-rw-r--r--app/views/layouts/_page.html.haml2
-rw-r--r--app/views/layouts/nav/_admin.html.haml2
-rw-r--r--app/views/layouts/nav/_group.html.haml2
-rw-r--r--app/views/layouts/nav/_profile.html.haml2
-rw-r--r--app/views/layouts/nav/_project.html.haml2
-rw-r--r--app/views/profiles/notifications/show.html.haml2
-rw-r--r--app/views/projects/_md_preview.html.haml15
-rw-r--r--app/views/projects/badges/index.html.haml2
-rw-r--r--app/views/projects/blob/_editor.html.haml2
-rw-r--r--app/views/projects/ci/pipelines/_pipeline.html.haml4
-rw-r--r--app/views/projects/commits/_commit.html.haml47
-rw-r--r--app/views/projects/commits/_head.html.haml2
-rw-r--r--app/views/projects/container_registry/_tag.html.haml6
-rw-r--r--app/views/projects/environments/index.html.haml16
-rw-r--r--app/views/projects/environments/new.html.haml5
-rw-r--r--app/views/projects/environments/show.html.haml12
-rw-r--r--app/views/projects/merge_requests/_discussion.html.haml4
-rw-r--r--app/views/projects/notes/_hints.html.haml2
-rw-r--r--app/views/projects/notes/_note.html.haml4
-rw-r--r--app/views/projects/runners/_form.html.haml6
-rw-r--r--app/views/projects/runners/_runner.html.haml6
-rw-r--r--app/views/projects/runners/_specific_runners.html.haml10
-rw-r--r--app/views/projects/runners/show.html.haml3
-rw-r--r--app/views/projects/show.html.haml4
-rw-r--r--app/views/shared/_event_filter.html.haml2
-rw-r--r--app/views/shared/_ref_switcher.html.haml9
-rw-r--r--config/routes.rb3
-rw-r--r--db/migrate/20160509091049_add_locked_to_ci_runner.rb13
-rw-r--r--db/migrate/20160615191922_set_missing_stage_on_ci_builds.rb9
-rw-r--r--db/migrate/20160620115026_add_index_on_runners_locked.rb12
-rw-r--r--db/schema.rb5
-rw-r--r--doc/ci/README.md1
-rw-r--r--doc/ci/environments.md58
-rw-r--r--doc/ci/runners/README.md8
-rw-r--r--doc/ci/yaml/README.md17
-rw-r--r--doc/project_services/emails_on_push.md17
-rw-r--r--doc/project_services/img/emails_on_push_service.pngbin0 -> 98160 bytes
-rw-r--r--doc/project_services/project_services.md2
-rw-r--r--doc/update/8.6-to-8.7.md2
-rw-r--r--doc/workflow/add-user/add-user.md24
-rw-r--r--doc/workflow/add-user/img/access_requests_management.pngbin0 -> 15105 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_email_accept.pngbin10833 -> 22961 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_email_ready.pngbin16177 -> 40305 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_email_search.pngbin15889 -> 45884 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_give_permissions.pngbin22089 -> 56480 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_import_members_from_another_project.pngbin18897 -> 38874 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_imported_members.pngbin23897 -> 37873 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_list_members.pngbin15732 -> 24427 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_members_menu.pngbin8295 -> 42319 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_search_people.pngbin13518 -> 39941 bytes
-rw-r--r--doc/workflow/add-user/img/request_access_button.pngbin0 -> 36588 bytes
-rw-r--r--doc/workflow/add-user/img/withdraw_access_request_button.pngbin0 -> 37960 bytes
-rw-r--r--doc/workflow/groups.md22
-rw-r--r--doc/workflow/groups/access_requests_management.pngbin0 -> 15193 bytes
-rw-r--r--doc/workflow/groups/request_access_button.pngbin0 -> 30470 bytes
-rw-r--r--doc/workflow/groups/withdraw_access_request_button.pngbin0 -> 31681 bytes
-rw-r--r--doc/workflow/shortcuts.pngbin90936 -> 108255 bytes
-rw-r--r--features/steps/profile/notifications.rb4
-rw-r--r--features/steps/project/network_graph.rb20
-rw-r--r--features/steps/project/project.rb4
-rw-r--r--features/steps/project/source/browse_files.rb14
-rw-r--r--lib/api/api.rb2
-rw-r--r--lib/api/entities.rb11
-rw-r--r--lib/api/gitignores.rb29
-rw-r--r--lib/api/internal.rb2
-rw-r--r--lib/api/runners.rb12
-rw-r--r--lib/api/templates.rb36
-rw-r--r--lib/banzai/filter/relative_link_filter.rb51
-rw-r--r--lib/ci/api/runners.rb9
-rw-r--r--lib/container_registry/tag.rb1
-rw-r--r--lib/gitlab/backend/grack_auth.rb2
-rw-r--r--lib/gitlab/gitignore.rb56
-rw-r--r--lib/gitlab/lfs/response.rb7
-rw-r--r--lib/gitlab/lfs/router.rb7
-rw-r--r--lib/gitlab/template/base_template.rb67
-rw-r--r--lib/gitlab/template/gitignore.rb22
-rw-r--r--lib/gitlab/template/gitlab_ci_yml.rb27
-rw-r--r--lib/tasks/gitlab/update_gitignore.rake46
-rw-r--r--lib/tasks/gitlab/update_templates.rake54
-rw-r--r--spec/controllers/projects_controller_spec.rb20
-rw-r--r--spec/factories/ci/pipelines.rb (renamed from spec/factories/ci/commits.rb)0
-rw-r--r--spec/features/admin/admin_runners_spec.rb34
-rw-r--r--spec/features/container_registry_spec.rb3
-rw-r--r--spec/features/environments_spec.rb6
-rw-r--r--spec/features/issues_spec.rb2
-rw-r--r--spec/features/projects/badges/list_spec.rb8
-rw-r--r--spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb30
-rw-r--r--spec/fixtures/container_registry/tag_manifest.json17
-rw-r--r--spec/helpers/application_helper_spec.rb45
-rw-r--r--spec/javascripts/issue_spec.js.coffee13
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb7
-rw-r--r--spec/lib/gitlab/lfs/lfs_router_spec.rb561
-rw-r--r--spec/lib/gitlab/template/gitignore_spec.rb (renamed from spec/lib/gitlab/gitignore_spec.rb)6
-rw-r--r--spec/mailers/notify_spec.rb61
-rw-r--r--spec/models/build_spec.rb127
-rw-r--r--spec/models/ci/runner_spec.rb241
-rw-r--r--spec/models/commit_spec.rb12
-rw-r--r--spec/models/commit_status_spec.rb19
-rw-r--r--spec/models/concerns/participable_spec.rb10
-rw-r--r--spec/requests/api/builds_spec.rb11
-rw-r--r--spec/requests/api/gitignores_spec.rb29
-rw-r--r--spec/requests/api/runners_spec.rb24
-rw-r--r--spec/requests/api/templates_spec.rb52
-rw-r--r--spec/workers/merge_worker_spec.rb2
-rw-r--r--vendor/gitignore/Android.gitignore3
-rw-r--r--vendor/gitignore/C++.gitignore1
-rw-r--r--vendor/gitignore/CMake.gitignore1
-rw-r--r--vendor/gitignore/D.gitignore4
-rw-r--r--vendor/gitignore/Global/Bazaar.gitignore2
-rw-r--r--vendor/gitignore/Global/OSX.gitignore3
-rw-r--r--vendor/gitignore/Global/README.md10
-rw-r--r--vendor/gitignore/Global/SublimeText.gitignore13
-rw-r--r--vendor/gitignore/Haskell.gitignore1
-rw-r--r--vendor/gitignore/Julia.gitignore4
-rw-r--r--vendor/gitignore/LICENSE19
-rw-r--r--vendor/gitignore/Laravel.gitignore1
-rw-r--r--vendor/gitignore/Objective-C.gitignore9
-rw-r--r--vendor/gitignore/Qt.gitignore2
-rw-r--r--vendor/gitignore/README.md14
-rw-r--r--vendor/gitignore/Rails.gitignore4
-rw-r--r--vendor/gitignore/Swift.gitignore2
-rw-r--r--vendor/gitignore/UnrealEngine.gitignore1
-rw-r--r--vendor/gitignore/VisualStudio.gitignore1
-rw-r--r--vendor/gitlab-ci-yml/Docker.gitlab-ci.yml7
-rw-r--r--vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml18
-rw-r--r--vendor/gitlab-ci-yml/LICENSE21
-rw-r--r--vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml27
-rw-r--r--vendor/gitlab-ci-yml/Pages/brunch.gitlab-ci.yml16
-rw-r--r--vendor/gitlab-ci-yml/Pages/doxygen.gitlab-ci.yml13
-rw-r--r--vendor/gitlab-ci-yml/Pages/harp.gitlab-ci.yml16
-rw-r--r--vendor/gitlab-ci-yml/Pages/hexo.gitlab-ci.yml25
-rw-r--r--vendor/gitlab-ci-yml/Pages/html.gitlab-ci.yml12
-rw-r--r--vendor/gitlab-ci-yml/Pages/hugo.gitlab-ci.yml11
-rw-r--r--vendor/gitlab-ci-yml/Pages/hyde.gitlab-ci.yml25
-rw-r--r--vendor/gitlab-ci-yml/Pages/jekyll.gitlab-ci.yml24
-rw-r--r--vendor/gitlab-ci-yml/Pages/lektor.gitlab-ci.yml12
-rw-r--r--vendor/gitlab-ci-yml/Pages/metalsmith.gitlab-ci.yml17
-rw-r--r--vendor/gitlab-ci-yml/Pages/middleman.gitlab-ci.yml27
-rw-r--r--vendor/gitlab-ci-yml/Pages/nanoc.gitlab-ci.yml12
-rw-r--r--vendor/gitlab-ci-yml/Pages/octopress.gitlab-ci.yml15
-rw-r--r--vendor/gitlab-ci-yml/Pages/pelican.gitlab-ci.yml10
-rw-r--r--vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml30
193 files changed, 2370 insertions, 921 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 01e7e7a1606..0c3aadc29d1 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
Please view this file on the master branch, on stable branches it's out of date.
v 8.9.0 (unreleased)
+ - Fix builds API response not including commit data
- Fix error when CI job variables key specified but not defined
- Fix pipeline status when there are no builds in pipeline
- Fix Error 500 when using closes_issues API with an external issue tracker
@@ -8,6 +9,7 @@ v 8.9.0 (unreleased)
- Bulk assign/unassign labels to issues.
- Ability to prioritize labels !4009 / !3205 (Thijs Wouters)
- Show Star and Fork buttons on mobile.
+ - Performance improvements on RelativeLinkFilter
- Fix endless redirections when accessing user OAuth applications when they are disabled
- Allow enabling wiki page events from Webhook management UI
- Bump rouge to 1.11.0
@@ -18,7 +20,9 @@ v 8.9.0 (unreleased)
- Redesign all Devise emails. !4297
- Don't show 'Leave Project' to group members
- Fix wiki page events' webhook to point to the wiki repository
+ - Add a border around images to differentiate them from the background.
- Don't show tags for revert and cherry-pick operations
+ - Show image ID on registry page
- Fix issue todo not remove when leave project !4150 (Long Nguyen)
- Allow customisable text on the 'nearly there' page after a user signs up
- Bump recaptcha gem to 3.0.0 to remove deprecated stoken support
@@ -70,6 +74,7 @@ v 8.9.0 (unreleased)
- Todos will display target state if issuable target is 'Closed' or 'Merged'
- Validate only and except regexp
- Fix bug when sorting issues by milestone due date and filtering by two or more labels
+ - POST to API /projects/:id/runners/:runner_id would give 409 if the runner was already enabled for this project
- Add support for using Yubikeys (U2F) for two-factor authentication
- Link to blank group icon doesn't throw a 404 anymore
- Remove 'main language' feature
@@ -77,13 +82,16 @@ v 8.9.0 (unreleased)
- Pipelines can be canceled only when there are running builds
- Allow authentication using personal access tokens
- Use downcased path to container repository as this is expected path by Docker
+ - Allow to use CI token to fetch LFS objects
- Custom notification settings
- Projects pending deletion will render a 404 page
- Measure queue duration between gitlab-workhorse and Rails
- Added Gfm autocomplete for labels
+ - Added edit note 'up' shortcut documentation to the help panel and docs screenshot #18114
- Make Omniauth providers specs to not modify global configuration
- Remove unused JiraIssue class and replace references with ExternalIssue. !4659 (Ilan Shamir)
- Make authentication service for Container Registry to be compatible with < Docker 1.11
+ - Make it possible to lock a runner from being enabled for other projects
- Add Application Setting to configure Container Registry token expire delay (default 5min)
- Cache assigned issue and merge request counts in sidebar nav
- Use Knapsack only in CI environment
@@ -101,6 +109,7 @@ v 8.9.0 (unreleased)
- An indicator is now displayed at the top of the comment field for confidential issues.
- Show categorised search queries in the search autocomplete
- RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented
+ - Dropdown for `.gitlab-ci.yml` templates
- Improve issuables APIs performance when accessing notes !4471
- Add sorting dropdown to tags page !4423
- External links now open in a new tab
@@ -129,11 +138,16 @@ v 8.9.0 (unreleased)
- Various associations are now eager loaded when parsing issue references to reduce the number of queries executed
- Set inverse_of for Project/Service association to reduce the number of queries
- Update tanuki logo highlight/loading colors
+ - Remove explicit Gitlab::Metrics.action assignments, are already automatic.
- Use Git cached counters for branches and tags on project page
+ - Cache participable participants in an instance variable.
- Filter parameters for request_uri value on instrumented transactions.
- Remove duplicated keys add UNIQUE index to keys fingerprint column
+ - ExtractsPath get ref_names from repository cache, if not there access git.
- Cache user todo counts from TodoService
- Ensure Todos counters doesn't count Todos for projects pending delete
+ - Add left/right arrows horizontal navigation
+ - Add tooltip to pin/unpin navbar
v 8.8.5
- Import GitHub repositories respecting the API rate limit !4166
diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee
index 3f61ea1eaf4..cf46f15a156 100644
--- a/app/assets/javascripts/api.js.coffee
+++ b/app/assets/javascripts/api.js.coffee
@@ -7,6 +7,7 @@
labelsPath: "/api/:version/projects/:id/labels"
licensePath: "/api/:version/licenses/:key"
gitignorePath: "/api/:version/gitignores/:key"
+ gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key"
group: (group_id, callback) ->
url = Api.buildUrl(Api.groupPath)
@@ -110,6 +111,12 @@
$.get url, (gitignore) ->
callback(gitignore)
+ gitlabCiYml: (key, callback) ->
+ url = Api.buildUrl(Api.gitlabCiYmlPath).replace(':key', key)
+
+ $.get url, (file) ->
+ callback(file)
+
buildUrl: (url) ->
url = gon.relative_url_root + url if gon.relative_url_root?
return url.replace(':version', gon.api_version)
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index 2f9f6c3ef5b..397892d15c0 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -257,7 +257,7 @@ $ ->
# Sidenav pinning
if $(window).width() < 1440 and $.cookie('pin_nav') is 'true'
- $.cookie('pin_nav', 'false')
+ $.cookie('pin_nav', 'false', { path: '/' })
$('.page-with-sidebar')
.toggleClass('page-sidebar-collapsed page-sidebar-expanded')
.removeClass('page-sidebar-pinned')
@@ -268,17 +268,33 @@ $ ->
.on 'click', '.js-nav-pin', (e) ->
e.preventDefault()
+ $pinBtn = $(e.currentTarget)
+ $page = $ '.page-with-sidebar'
+ $topNav = $ '.navbar-fixed-top'
+ $tooltip = $ "##{$pinBtn.attr('aria-describedby')}"
+ doPinNav = not $page.is('.page-sidebar-pinned')
+ tooltipText = 'Pin navigation'
+
$(this).toggleClass 'is-active'
- if $.cookie('pin_nav') is 'true'
- $.cookie 'pin_nav', 'false'
- $('.page-with-sidebar')
- .removeClass('page-sidebar-pinned')
- .toggleClass('page-sidebar-collapsed page-sidebar-expanded')
- $('.navbar-fixed-top')
- .removeClass('header-pinned-nav')
- .toggleClass('header-collapsed header-expanded')
+ if doPinNav
+ $page.addClass('page-sidebar-pinned')
+ $topNav.addClass('header-pinned-nav')
else
- $.cookie 'pin_nav', 'true'
- $('.page-with-sidebar').addClass('page-sidebar-pinned')
- $('.navbar-fixed-top').addClass('header-pinned-nav')
+ $tooltip.remove() # Remove it immediately when collapsing the sidebar
+ $page.removeClass('page-sidebar-pinned')
+ .toggleClass('page-sidebar-collapsed page-sidebar-expanded')
+ $topNav.removeClass('header-pinned-nav')
+ .toggleClass('header-collapsed header-expanded')
+
+ # Save settings
+ $.cookie 'pin_nav', doPinNav, { path: '/' }
+
+ if $.cookie('pin_nav') is 'true' or doPinNav
+ tooltipText = 'Unpin navigation'
+
+ # Update tooltip text immediately
+ $tooltip.find('.tooltip-inner').text(tooltipText)
+
+ # Persist tooltip title
+ $pinBtn.attr('title', tooltipText).tooltip('fixTitle')
diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.coffee b/app/assets/javascripts/blob/blob_ci_yaml.js.coffee
new file mode 100644
index 00000000000..d9a03d05529
--- /dev/null
+++ b/app/assets/javascripts/blob/blob_ci_yaml.js.coffee
@@ -0,0 +1,23 @@
+#= require blob/template_selector
+
+class @BlobCiYamlSelector extends TemplateSelector
+ requestFile: (query) ->
+ Api.gitlabCiYml query.name, @requestFileSuccess.bind(@)
+
+class @BlobCiYamlSelectors
+ constructor: (opts) ->
+ {
+ @$dropdowns = $('.js-gitlab-ci-yml-selector')
+ @editor
+ } = opts
+
+ @$dropdowns.each (i, dropdown) =>
+ $dropdown = $(dropdown)
+
+ new BlobCiYamlSelector(
+ pattern: /(.gitlab-ci.yml)/,
+ data: $dropdown.data('data'),
+ wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'),
+ dropdown: $dropdown,
+ editor: @editor
+ )
diff --git a/app/assets/javascripts/blob/edit_blob.js.coffee b/app/assets/javascripts/blob/edit_blob.js.coffee
index 636f909dbd0..19e584519d7 100644
--- a/app/assets/javascripts/blob/edit_blob.js.coffee
+++ b/app/assets/javascripts/blob/edit_blob.js.coffee
@@ -15,6 +15,7 @@ class @EditBlob
new BlobLicenseSelectors { @editor }
new BlobGitignoreSelectors { @editor }
+ new BlobCiYamlSelectors { @editor }
initModePanesAndLinks: ->
@$editModePanes = $(".js-edit-mode-pane")
diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee
index 2a8a1f05b35..2a7bf0bc306 100644
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ b/app/assets/javascripts/gl_dropdown.js.coffee
@@ -58,7 +58,7 @@ class GitLabDropdownFilter
filter: (search_text) ->
data = @options.data()
- if data?
+ if data? and not @options.filterByText
results = data
if search_text isnt ''
@@ -102,10 +102,11 @@ class GitLabDropdownFilter
$el = $(@)
matches = fuzzaldrinPlus.match($el.text().trim(), search_text)
- if matches.length
- $el.show()
- else
- $el.hide()
+ unless $el.is('.dropdown-header')
+ if matches.length
+ $el.show()
+ else
+ $el.hide()
else
elements.show()
@@ -191,6 +192,7 @@ class GitLabDropdown
if @options.filterable
@filter = new GitLabDropdownFilter @filterInput,
filterInputBlur: @filterInputBlur
+ filterByText: @options.filterByText
remote: @options.filterRemote
query: @options.data
keys: searchFields
diff --git a/app/assets/javascripts/gl_form.js.coffee b/app/assets/javascripts/gl_form.js.coffee
index d540cc4dc46..77512d187c9 100644
--- a/app/assets/javascripts/gl_form.js.coffee
+++ b/app/assets/javascripts/gl_form.js.coffee
@@ -34,6 +34,8 @@ class @GLForm
# form and textarea event listeners
@addEventListeners()
+ gl.text.init(@form)
+
# hide discard button
@form.find('.js-note-discard').hide()
@@ -42,6 +44,7 @@ class @GLForm
clearEventListeners: ->
@textarea.off 'focus'
@textarea.off 'blur'
+ gl.text.removeListeners(@form)
addEventListeners: ->
@textarea.on 'focus', ->
diff --git a/app/assets/javascripts/lib/text_utility.js.coffee b/app/assets/javascripts/lib/text_utility.js.coffee
new file mode 100644
index 00000000000..bb2772dfed2
--- /dev/null
+++ b/app/assets/javascripts/lib/text_utility.js.coffee
@@ -0,0 +1,79 @@
+((w) ->
+ w.gl ?= {}
+ w.gl.text ?= {}
+
+ gl.text.randomString = -> Math.random().toString(36).substring(7)
+
+ gl.text.replaceRange = (s, start, end, substitute) ->
+ s.substring(0, start) + substitute + s.substring(end);
+
+ gl.text.selectedText = (text, textarea) ->
+ text.substring(textarea.selectionStart, textarea.selectionEnd)
+
+ gl.text.insertText = (textArea, text, tag, selected, wrap) ->
+ selectedSplit = selected.split('\n')
+ startChar = if not wrap and textArea.selectionStart > 0 then '\n' else ''
+
+ if selectedSplit.length > 1 and not wrap
+ insertText = selectedSplit.map((val) ->
+ if val.indexOf(tag) is 0
+ "#{val.replace(tag, '')}"
+ else
+ "#{tag}#{val}"
+ ).join('\n')
+ else
+ insertText = "#{startChar}#{tag}#{selected}#{if wrap then tag else ' '}"
+
+ if document.queryCommandSupported('insertText')
+ document.execCommand 'insertText', false, insertText
+ else
+ try
+ document.execCommand("ms-beginUndoUnit")
+
+ textArea.value = @replaceRange(
+ text,
+ textArea.selectionStart,
+ textArea.selectionEnd,
+ insertText)
+ try
+ document.execCommand("ms-endUndoUnit")
+
+ @moveCursor(textArea, tag, wrap)
+
+ gl.text.moveCursor = (textArea, tag, wrapped) ->
+ return unless textArea.setSelectionRange
+
+ if textArea.selectionStart is textArea.selectionEnd
+ if wrapped
+ pos = textArea.selectionStart - tag.length
+ else
+ pos = textArea.selectionStart
+
+ textArea.setSelectionRange pos, pos
+
+ gl.text.updateText = (textArea, tag, wrap) ->
+ $textArea = $(textArea)
+ oldVal = $textArea.val()
+ textArea = $textArea.get(0)
+ text = $textArea.val()
+ selected = @selectedText(text, textArea)
+ $textArea.focus()
+
+ @insertText(textArea, text, tag, selected, wrap)
+
+ gl.text.init = (form) ->
+ self = @
+ $('.js-md', form)
+ .off 'click'
+ .on 'click', ->
+ $this = $(@)
+ self.updateText(
+ $this.closest('.md-area').find('textarea'),
+ $this.data('md-tag'),
+ not $this.data('md-prepend')
+ )
+
+ gl.text.removeListeners = (form) ->
+ $('.js-md', form).off()
+
+) window
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index e2d3241437b..17f7e180127 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -102,12 +102,15 @@ class @Notes
keydownNoteText: (e) ->
$this = $(this)
- if $this.val() is '' and e.which is 38 #aka the up key
+ if $this.val() is '' and e.which is 38 and not isMetaKey e
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])
+ isMetaKey = (e) ->
+ (e.metaKey or e.ctrlKey or e.altKey or e.shiftKey)
+
initRefresh: ->
clearInterval(Notes.interval)
Notes.interval = setInterval =>
diff --git a/app/assets/javascripts/notifications_dropdown.js.coffee b/app/assets/javascripts/notifications_dropdown.js.coffee
index 74d2298c1fa..0bbd082c156 100644
--- a/app/assets/javascripts/notifications_dropdown.js.coffee
+++ b/app/assets/javascripts/notifications_dropdown.js.coffee
@@ -1,5 +1,5 @@
class @NotificationsDropdown
- $ ->
+ constructor: ->
$(document)
.off 'click', '.update-notification'
.on 'click', '.update-notification', (e) ->
@@ -18,7 +18,8 @@ class @NotificationsDropdown
.off 'ajax:success', '.notification-form'
.on 'ajax:success', '.notification-form', (e, data) ->
if data.saved
- new Flash('Notification settings saved', 'notice')
- $(e.currentTarget).closest('.notification-dropdown').replaceWith(data.html)
+ $(e.currentTarget)
+ .closest('.notification-dropdown')
+ .replaceWith(data.html)
else
new Flash('Failed to save new settings', 'alert')
diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee
index d12bad97a05..96e10dd7e8a 100644
--- a/app/assets/javascripts/project.js.coffee
+++ b/app/assets/javascripts/project.js.coffee
@@ -19,6 +19,7 @@ class @Project
$('.clone').text(url)
# Ref switcher
+ @initRefSwitcher()
$('.project-refs-select').on 'change', ->
$(@).parents('form').submit()
@@ -34,7 +35,6 @@ class @Project
$(@).parents('.no-password-message').remove()
e.preventDefault()
-
@projectSelectDropdown()
projectSelectDropdown: ->
@@ -50,3 +50,39 @@ class @Project
changeProject: (url) ->
window.location = url
+
+ initRefSwitcher: ->
+ $('.js-project-refs-dropdown').each ->
+ $dropdown = $(@)
+ selected = $dropdown.data('selected')
+
+ $dropdown.glDropdown(
+ data: (term, callback) ->
+ $.ajax(
+ url: $dropdown.data('refs-url')
+ data:
+ ref: $dropdown.data('ref')
+ ).done (refs) ->
+ callback(refs)
+ selectable: true
+ filterable: true
+ filterByText: true
+ fieldName: 'ref'
+ renderRow: (ref) ->
+ if ref.header?
+ "<li class='dropdown-header'>#{ref.header}</li>"
+ else
+ isActiveClass = if ref is selected then 'is-active' else ''
+
+ "<li>
+ <a href='#' data-ref='#{escape(ref)}' class='#{isActiveClass}'>
+ #{ref}
+ </a>
+ </li>"
+ id: (obj, $el) ->
+ $el.data('ref')
+ toggleLabel: (obj, $el) ->
+ $el.text().trim()
+ clicked: (e) ->
+ $dropdown.closest('form').submit()
+ )
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 3cbddc59f11..a306b8f3f29 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -37,3 +37,4 @@
@import "framework/timeline.scss";
@import "framework/typography.scss";
@import "framework/zen.scss";
+@import "framework/blank";
diff --git a/app/assets/stylesheets/framework/blank.scss b/app/assets/stylesheets/framework/blank.scss
new file mode 100644
index 00000000000..40b5171a8c6
--- /dev/null
+++ b/app/assets/stylesheets/framework/blank.scss
@@ -0,0 +1,23 @@
+.blank-state {
+ padding-top: 20px;
+ padding-bottom: 20px;
+ text-align: center;
+}
+
+.blank-state-no-icon {
+ padding-top: 40px;
+ padding-bottom: 40px;
+}
+
+.blank-state-title {
+ margin-top: 0;
+ margin-bottom: 5px;
+ font-size: 19px;
+ font-weight: normal;
+}
+
+.blank-state-text {
+ margin-top: 0;
+ margin-bottom: $gl-padding;
+ font-size: 15px;
+}
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index fd885b38680..fd8eaa8a691 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -65,6 +65,11 @@
a {
padding-top: 0;
line-height: 1;
+ border-bottom: 1px solid $border-color;
+
+ &.btn.btn-xs {
+ padding: 2px 5px;
+ }
}
}
}
@@ -97,5 +102,30 @@
white-space: pre-wrap;
word-break: keep-all;
}
+
+ @include bulleted-list;
+ }
+}
+
+.toolbar-group {
+ float: left;
+ margin-right: -5px;
+ margin-left: $gl-padding;
+
+ &:first-child {
+ margin-left: 0;
+ }
+}
+
+.toolbar-btn {
+ float: left;
+ padding: 0 5px;
+ color: #959494;
+ background: transparent;
+ border: 0;
+ outline: 0;
+
+ &:hover {
+ color: $gl-link-color;
}
}
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 828e7224231..5ec5a96a597 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -110,3 +110,17 @@
font-size: 16px;
line-height: 24px;
}
+
+@mixin bulleted-list {
+ > ul {
+ list-style-type: disc;
+
+ ul {
+ list-style-type: circle;
+
+ ul {
+ list-style-type: square;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index a55918f8711..694f09c0464 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -18,6 +18,13 @@
opacity: 0;
transition-duration: .3s;
}
+
+ .fa {
+ position: relative;
+ top: 3px;
+ font-size: 13px;
+ color: $btn-placeholder-gray;
+ }
}
@mixin scrolling-links() {
@@ -136,7 +143,7 @@
}
/* Small devices (phones, tablets, 768px and lower) */
- @media (max-width: $screen-sm-max) {
+ @media (max-width: $screen-xs-max) {
width: 100%;
}
}
@@ -220,6 +227,7 @@
form {
display: block;
height: auto;
+ margin-bottom: 14px;
input {
width: 100%;
@@ -319,11 +327,19 @@
.fade-right {
@include fade(left, rgba(250, 250, 250, 0.4), $background-color);
right: 0;
+
+ .fa {
+ right: -7px;
+ }
}
.fade-left {
@include fade(right, rgba(250, 250, 250, 0.4), $background-color);
left: 0;
+
+ .fa {
+ left: -7px;
+ }
}
li {
diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss
index ae7bdf14c40..874416e1007 100644
--- a/app/assets/stylesheets/framework/panels.scss
+++ b/app/assets/stylesheets/framework/panels.scss
@@ -9,6 +9,10 @@
margin-top: -2px;
float: right;
}
+
+ .dropdown-menu-toggle {
+ line-height: 20px;
+ }
}
.panel-body {
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index f242706ebe4..21d87cc9d34 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -165,11 +165,6 @@
background-size: 16px 16px !important;
}
-/** Branch/tag selector **/
-.project-refs-form .select2-container {
- width: 160px !important;
-}
-
.select2-results .select2-no-results,
.select2-results .select2-searching,
.select2-results .select2-ajax-error,
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 761e33f0df7..de534d28421 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -80,9 +80,14 @@
.commit {
padding: 10px 0;
+ position: relative;
@media (min-width: $screen-sm-min) {
- padding-left: 46px;
+ padding-left: 20px;
+
+ .commit-info-block {
+ padding-left: 44px;
+ }
}
&:not(:last-child) {
@@ -95,8 +100,11 @@
vertical-align: baseline;
}
+
.avatar {
- margin-left: -46px;
+ position: absolute;
+ top: 10px;
+ left: 16px;
}
.item-title {
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 1a7d5f9666e..5286b73cc50 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -4,6 +4,11 @@
margin-bottom: $gl-padding;
border-radius: 3px;
+ .commit-short-id {
+ font-family: $regular_font;
+ font-weight: 400;
+ }
+
.diff-header {
position: relative;
background: $background-color;
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index a34b06f1054..1aa4e06d975 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -60,13 +60,14 @@
.encoding-selector,
.license-selector,
- .gitignore-selector {
+ .gitignore-selector,
+ .gitlab-ci-yml-selector {
display: inline-block;
vertical-align: top;
font-family: $regular_font;
}
- .gitignore-selector, .license-selector {
+ .gitignore-selector, .license-selector, .gitlab-ci-yml-selector {
.dropdown {
line-height: 21px;
}
@@ -76,4 +77,10 @@
width: 220px;
}
}
+
+ .gitlab-ci-yml-selector {
+ .dropdown-menu-toggle {
+ width: 250px;
+ }
+ }
}
diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss
index 4a95b7b852e..0b710ef168b 100644
--- a/app/assets/stylesheets/pages/help.scss
+++ b/app/assets/stylesheets/pages/help.scss
@@ -57,4 +57,11 @@
.documentation {
padding: 7px;
+
+ // Border around images in the help pages.
+ img:not(.emoji) {
+ border: 1px solid $table-border-gray;
+ padding: 5px;
+ margin: 5px;
+ }
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 687117233f6..21ff6ab71f0 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -4,6 +4,13 @@
margin-right: 1px;
}
}
+
+ // Border around images in issue and MR descriptions.
+ .description img:not(.emoji) {
+ border: 1px solid $table-border-gray;
+ padding: 5px;
+ margin: 5px;
+ }
}
.issuable-filter-count {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index e67271adfb1..aca82f7f7bf 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -119,7 +119,12 @@
margin-bottom: 0;
}
- @media (max-width: $screen-sm-max) {
+ .btn-grouped {
+ margin-left: 0;
+ margin-right: 7px;
+ }
+
+ @media (max-width: $screen-xs-max) {
h4 {
font-size: 15px;
}
@@ -131,10 +136,14 @@
.btn,
.btn-group,
.accept-action {
- width: 100%;
margin-bottom: 4px;
}
+ .accept-action {
+ width: 100%;
+ text-align: center;
+ }
+
.accept-control {
width: 100%;
text-align: center;
@@ -284,7 +293,7 @@
margin-bottom: 0;
}
- @media (min-width: $screen-sm-min) {
+ @media (min-width: $screen-xs-min) {
float: left;
width: 50%;
margin-bottom: 0;
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 577dddae741..3784010348a 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -179,6 +179,10 @@
border-top: 1px solid $border-color;
}
+.md-helper {
+ padding-top: 10px;
+}
+
.toolbar-button {
padding: 0;
background: none;
@@ -219,3 +223,16 @@
float: left;
}
}
+
+.note-form-actions {
+ @media (max-width: $screen-xs-max) {
+ .btn {
+ float: none;
+ width: 100%;
+
+ &:not(:last-child) {
+ margin-bottom: 10px;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 35d728aec83..ffba3dc5bc6 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -84,24 +84,14 @@ ul.notes {
word-wrap: break-word;
@include md-typography;
+ // Reset ul style types since we're nested inside a ul already
+ @include bulleted-list;
+
// On diffs code should wrap nicely and not overflow
code {
white-space: pre-wrap;
}
- // Reset ul style types since we're nested inside a ul already
- & > ul {
- list-style-type: disc;
-
- ul {
- list-style-type: circle;
-
- ul {
- list-style-type: square;
- }
- }
- }
-
ul.task-list {
ul:not(.task-list) {
padding-left: 1.3em;
@@ -117,6 +107,13 @@ ul.notes {
code {
word-break: keep-all;
}
+
+ // Border around images in issue and MR comments.
+ img:not(.emoji) {
+ border: 1px solid $table-border-gray;
+ padding: 5px;
+ margin: 5px 0;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index f138a2f5387..d3e59d7fdb9 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -101,7 +101,8 @@
.notifications-btn {
- .fa-bell {
+ .fa-bell,
+ .fa-spinner {
margin-right: 6px;
}
@@ -373,7 +374,7 @@ a.deploy-project-label {
.project-stats {
margin-top: $gl-padding;
margin-bottom: 0;
- padding: 16px 0;
+ padding: 0;
background-color: $white-light;
font-size: 0;
@@ -382,13 +383,14 @@ a.deploy-project-label {
}
.nav li {
- display: inline;
+ display: inline-block;
+ margin: 16px 0;
+ margin-right: 16px;
}
.nav > li > a {
background-color: transparent;
- margin-right: 12px;
- padding: 0 10px;
+ padding: 5px 10px;
font-size: 15px;
color: $notes-light-color;
}
@@ -402,12 +404,17 @@ a.deploy-project-label {
font-size: 17px;
}
- li.missing a {
- color: #5a6069;
- border: 1px dashed #dce0e5;
+ li.missing {
+ border: 1px dashed $border-gray-light;
+ border-radius: $border-radius-default;
+
+ a {
+ color: $notes-light-color;
+ display: block;
+ }
&:hover {
- background-color: #f0f2f5;
+ background-color: $gray-normal;
}
}
@@ -616,3 +623,9 @@ pre.light-well {
color: $gl-text-green;
}
}
+
+.project-refs-form {
+ .dropdown-menu {
+ width: 300px;
+ }
+}
diff --git a/app/controllers/admin/runner_projects_controller.rb b/app/controllers/admin/runner_projects_controller.rb
index d25619d94e0..bf20c5305a7 100644
--- a/app/controllers/admin/runner_projects_controller.rb
+++ b/app/controllers/admin/runner_projects_controller.rb
@@ -1,15 +1,14 @@
class Admin::RunnerProjectsController < Admin::ApplicationController
before_action :project, only: [:create]
- def index
- @runner_projects = project.runner_projects.all
- @runner_project = project.runner_projects.new
- end
-
def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id])
- if @runner.assign_to(@project, current_user)
+ return head(403) if @runner.is_shared? || @runner.locked?
+
+ runner_project = @runner.assign_to(@project, current_user)
+
+ if runner_project.persisted?
redirect_to admin_runner_path(@runner)
else
redirect_to admin_runner_path(@runner), alert: 'Failed adding runner to project'
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 776ba92c9ab..996909a28c6 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -74,7 +74,7 @@ class Projects::ApplicationController < ApplicationController
end
def require_branch_head
- unless @repository.branch_names.include?(@ref)
+ unless @repository.branch_exists?(@ref)
redirect_to(
namespace_project_tree_path(@project.namespace, @project, @ref),
notice: "This action is not allowed unless you are on a branch"
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 127bd1a4318..487963fdcd7 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -54,6 +54,6 @@ class Projects::PipelinesController < Projects::ApplicationController
end
def commit
- @commit ||= @pipeline.commit_data
+ @commit ||= @pipeline.commit
end
end
diff --git a/app/controllers/projects/runner_projects_controller.rb b/app/controllers/projects/runner_projects_controller.rb
index bedeb4a295c..dc1a18f8d42 100644
--- a/app/controllers/projects/runner_projects_controller.rb
+++ b/app/controllers/projects/runner_projects_controller.rb
@@ -6,11 +6,13 @@ class Projects::RunnerProjectsController < Projects::ApplicationController
def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id])
+ return head(403) if @runner.is_shared? || @runner.locked?
return head(403) unless current_user.ci_authorized_runners.include?(@runner)
path = runners_path(project)
+ runner_project = @runner.assign_to(project, current_user)
- if @runner.assign_to(project, current_user)
+ if runner_project.persisted?
redirect_to path
else
redirect_to path, alert: 'Failed adding runner to project'
diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb
index 0b4fa572501..53c36635efe 100644
--- a/app/controllers/projects/runners_controller.rb
+++ b/app/controllers/projects/runners_controller.rb
@@ -5,10 +5,9 @@ class Projects::RunnersController < Projects::ApplicationController
layout 'project_settings'
def index
- @runners = project.runners.ordered
- @specific_runners = current_user.ci_authorized_runners.
- where.not(id: project.runners).
- ordered.page(params[:page]).per(20)
+ @project_runners = project.runners.ordered
+ @assignable_runners = current_user.ci_authorized_runners.
+ assignable_for(project).ordered.page(params[:page]).per(20)
@shared_runners = Ci::Runner.shared.active
@shared_runners_count = @shared_runners.count(:all)
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 8044c637825..2b1f50fd01e 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -1,7 +1,7 @@
class ProjectsController < Projects::ApplicationController
include ExtractsPath
- before_action :authenticate_user!, except: [:show, :activity]
+ before_action :authenticate_user!, except: [:show, :activity, :refs]
before_action :project, except: [:new, :create]
before_action :repository, except: [:new, :create]
before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists?
@@ -251,6 +251,24 @@ class ProjectsController < Projects::ApplicationController
}
end
+ def refs
+ options = {
+ 'Branches' => @repository.branch_names,
+ }
+
+ unless @repository.tag_count.zero?
+ options['Tags'] = VersionSorter.rsort(@repository.tag_names)
+ end
+
+ # If reference is commit id - we should add it to branch/tag selectbox
+ ref = Addressable::URI.unescape(params[:ref])
+ if ref && options.flatten(2).exclude?(ref) && ref =~ /\A[0-9a-zA-Z]{6,52}\z/
+ options['Commits'] = [ref]
+ end
+
+ render json: options.to_json
+ end
+
private
def determine_layout
@@ -285,8 +303,14 @@ class ProjectsController < Projects::ApplicationController
project.repository_exists? && !project.empty_repo?
end
- # Override get_id from ExtractsPath, which returns the branch and file path
+ # Override extract_ref from ExtractsPath, which returns the branch and file path
# for the blob/tree, which in this case is just the root of the default branch.
+ # This way we avoid to access the repository.ref_names.
+ def extract_ref(_id)
+ [get_id, '']
+ end
+
+ # Override get_id from ExtractsPath in this case is just the root of the default branch.
def get_id
project.repository.root_ref
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 439b015b3b8..41859841834 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -101,22 +101,6 @@ module ApplicationHelper
'Never'
end
- def grouped_options_refs
- repository = @project.repository
-
- options = [
- ['Branches', repository.branch_names],
- ['Tags', VersionSorter.rsort(repository.tag_names)]
- ]
-
- # If reference is commit id - we should add it to branch/tag selectbox
- if @ref && !options.flatten.include?(@ref) && @ref =~ /\A[0-9a-zA-Z]{6,52}\z/
- options << ['Commit', [@ref]]
- end
-
- grouped_options_for_select(options, @ref || @project.default_branch)
- end
-
# Define whenever show last push event
# with suggestion to create MR
def show_last_push_widget?(event)
@@ -132,7 +116,7 @@ module ApplicationHelper
return false if project.merge_requests.where(source_branch: event.branch_name).opened.any?
# Skip if user removed branch right after that
- return false unless project.repository.branch_names.include?(event.branch_name)
+ return false unless project.repository.branch_exists?(event.branch_name)
true
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 5b54b34070c..4b4bc3d4276 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -29,7 +29,7 @@ module BlobHelper
if !on_top_of_branch?(project, ref)
button_tag "Edit", class: "btn disabled has-tooltip btn-file-option", title: "You can only edit files when you are on a branch", data: { container: 'body' }
elsif can_edit_blob?(blob, project, ref)
- link_to "Edit", edit_path, class: 'btn btn-file-option'
+ link_to "Edit", edit_path, class: 'btn btn-sm'
elsif can?(current_user, :fork_project, project)
continue_params = {
to: edit_path,
@@ -186,12 +186,16 @@ module BlobHelper
end
def gitignore_names
- return @gitignore_names if defined?(@gitignore_names)
+ @gitignore_names ||=
+ Gitlab::Template::Gitignore.categories.keys.map do |k|
+ [k, Gitlab::Template::Gitignore.by_category(k).map { |t| { name: t.name } }]
+ end.to_h
+ end
- @gitignore_names = {
- Global: Gitlab::Gitignore.global.map { |gitignore| { name: gitignore.name } },
- # Note that the key here doesn't cover it really
- Languages: Gitlab::Gitignore.languages_frameworks.map{ |gitignore| { name: gitignore.name } }
- }
+ def gitlab_ci_ymls
+ @gitlab_ci_ymls ||=
+ Gitlab::Template::GitlabCiYml.categories.keys.map do |k|
+ [k, Gitlab::Template::GitlabCiYml.by_category(k).map { |t| { name: t.name } }]
+ end.to_h
end
end
diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb
index 3ee3fc74f0c..c533659b600 100644
--- a/app/helpers/branches_helper.rb
+++ b/app/helpers/branches_helper.rb
@@ -10,7 +10,7 @@ module BranchesHelper
end
def can_push_branch?(project, branch_name)
- return false unless project.repository.branch_names.include?(branch_name)
+ return false unless project.repository.branch_exists?(branch_name)
::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name)
end
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 067a00660aa..a0dafc52622 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -185,4 +185,17 @@ module GitlabMarkdownHelper
''
end
end
+
+ def markdown_toolbar_button(options = {})
+ data = options[:data].merge({ container: "body" })
+ content_tag :button,
+ type: "button",
+ class: "toolbar-btn js-md has-tooltip hidden-xs",
+ tabindex: -1,
+ data: data,
+ title: options[:title],
+ aria: { label: options[:title] } do
+ icon(options[:icon])
+ end
+ end
end
diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb
index 6dde2e9847d..45311690293 100644
--- a/app/mailers/emails/members.rb
+++ b/app/mailers/emails/members.rb
@@ -12,6 +12,11 @@ module Emails
@member_id = member_id
admins = member_source.members.owners_and_masters.includes(:user).pluck(:notification_email)
+ # A project in a group can have no explicit owners/masters, in that case
+ # we fallbacks to the group's owners/masters.
+ if admins.empty? && member_source.respond_to?(:group) && member_source.group
+ admins = member_source.group.members.owners_and_masters.includes(:user).pluck(:notification_email)
+ end
mail(to: admins,
subject: subject("Request to join the #{member_source.human_name} #{member_source.model_name.singular}"))
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index d618c84e983..2b0bec33131 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -300,18 +300,12 @@ module Ci
project.valid_runners_token? token
end
- def can_be_served?(runner)
- return false unless has_tags? || runner.run_untagged?
-
- (tag_list - runner.tag_list).empty?
- end
-
def has_tags?
tag_list.any?
end
def any_runners_online?
- project.any_runners? { |runner| runner.active? && runner.online? && can_be_served?(runner) }
+ project.any_runners? { |runner| runner.active? && runner.online? && runner.can_pick?(self) }
end
def stuck?
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 5b264ecffc5..ca5a685dd11 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -37,22 +37,22 @@ module Ci
end
def git_author_name
- commit_data.author_name if commit_data
+ commit.try(:author_name)
end
def git_author_email
- commit_data.author_email if commit_data
+ commit.try(:author_email)
end
def git_commit_message
- commit_data.message if commit_data
+ commit.try(:message)
end
def short_sha
Ci::Pipeline.truncate_sha(sha)
end
- def commit_data
+ def commit
@commit ||= project.commit(sha)
rescue
nil
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index adb65292208..b64ec79ec2b 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -4,7 +4,7 @@ module Ci
LAST_CONTACT_TIME = 5.minutes.ago
AVAILABLE_SCOPES = %w[specific shared active paused online]
- FORM_EDITABLE = %i[description tag_list active run_untagged]
+ FORM_EDITABLE = %i[description tag_list active run_untagged locked]
has_many :builds, class_name: 'Ci::Build'
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
@@ -26,6 +26,13 @@ module Ci
.where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id)
end
+ scope :assignable_for, ->(project) do
+ # FIXME: That `to_sql` is needed to workaround a weird Rails bug.
+ # Without that, placeholders would miss one and couldn't match.
+ where(locked: false).
+ where.not("id IN (#{project.runners.select(:id).to_sql})").specific
+ end
+
validate :tag_constraints
acts_as_taggable
@@ -56,7 +63,7 @@ module Ci
def assign_to(project, current_user = nil)
self.is_shared = false if shared?
self.save
- project.runner_projects.create!(runner_id: self.id)
+ project.runner_projects.create(runner_id: self.id)
end
def display_name
@@ -91,6 +98,10 @@ module Ci
!shared?
end
+ def can_pick?(build)
+ assignable_for?(build.project) && accepting_tags?(build)
+ end
+
def only_for?(project)
projects == [project]
end
@@ -111,5 +122,13 @@ module Ci
'can not be empty when runner is not allowed to pick untagged jobs')
end
end
+
+ def assignable_for?(project)
+ !locked? || projects.exists?(id: project.id)
+ end
+
+ def accepting_tags?(build)
+ (run_untagged? || build.has_tags?) && (build.tag_list - tag_list).empty?
+ end
end
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index d69d518fadd..174ccbaea6c 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -271,6 +271,32 @@ class Commit
merged_merge_request ? 'merge request' : 'commit'
end
+ # Get the URI type of the given path
+ #
+ # Used to build URLs to files in the repository in GFM.
+ #
+ # path - String path to check
+ #
+ # Examples:
+ #
+ # uri_type('doc/README.md') # => :blob
+ # uri_type('doc/logo.png') # => :raw
+ # uri_type('doc/api') # => :tree
+ # uri_type('not/found') # => :nil
+ #
+ # Returns a symbol
+ def uri_type(path)
+ entry = @raw.tree.path(path)
+ if entry[:type] == :blob
+ blob = Gitlab::Git::Blob.new(name: entry[:name])
+ blob.image? ? :raw : :blob
+ else
+ entry[:type]
+ end
+ rescue Rugged::TreeError
+ nil
+ end
+
private
def repo_changes
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index ab13db4b297..e437e3417a8 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -8,6 +8,8 @@ class CommitStatus < ActiveRecord::Base
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id, touch: true
belongs_to :user
+ delegate :commit, to: :pipeline
+
validates :pipeline, presence: true, unless: :importing?
validates_presence_of :name
diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb
index 9056722f45e..9822844357d 100644
--- a/app/models/concerns/participable.rb
+++ b/app/models/concerns/participable.rb
@@ -53,6 +53,16 @@ module Participable
#
# Returns an Array of User instances.
def participants(current_user = nil)
+ @participants ||= Hash.new do |hash, user|
+ hash[user] = raw_participants(user)
+ end
+
+ @participants[current_user]
+ end
+
+ private
+
+ def raw_participants(current_user = nil)
current_user ||= author
ext = Gitlab::ReferenceExtractor.new(project, current_user)
participants = Set.new
diff --git a/app/models/repository.rb b/app/models/repository.rb
index bbd7682d8e7..221c87164ca 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -191,8 +191,12 @@ class Repository
end
end
+ def ref_names
+ branch_names + tag_names
+ end
+
def branch_names
- cache.fetch(:branch_names) { branches.map(&:name) }
+ @branch_names ||= cache.fetch(:branch_names) { branches.map(&:name) }
end
def branch_exists?(branch_name)
@@ -267,6 +271,7 @@ class Repository
def expire_branches_cache
cache.expire(:branch_names)
+ @branch_names = nil
@local_branches = nil
end
@@ -332,10 +337,6 @@ class Repository
@lookup_cache ||= {}
end
- def expire_branch_names
- cache.expire(:branch_names)
- end
-
def expire_avatar_cache(branch_name = nil, revision = nil)
# Avatars are pulled from the default branch, thus if somebody pushes to a
# different branch there's no need to expire anything.
diff --git a/app/models/user.rb b/app/models/user.rb
index 2e458329cb9..876ccc69d8d 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -487,9 +487,8 @@ class User < ActiveRecord::Base
events.recent.find do |event|
project = Project.find_by_id(event.project_id)
next unless project
- repo = project.repository
- if repo.branch_names.include?(event.branch_name)
+ if project.repository.branch_exists?(event.branch_name)
merge_requests = MergeRequest.where("created_at >= ?", event.created_at).
where(source_project_id: project.id,
source_branch: event.branch_name)
diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb
index f0ed09a629a..9a187f5d694 100644
--- a/app/services/ci/register_build_service.rb
+++ b/app/services/ci/register_build_service.rb
@@ -21,7 +21,7 @@ module Ci
end
build = builds.find do |build|
- build.can_be_served?(current_runner)
+ current_runner.can_pick?(build)
end
if build
diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index e049b40bfab..61abfc6ecbe 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -28,7 +28,7 @@
.col-md-6
%h4 Restrict projects for this runner
- if @runner.projects.any?
- %table.table
+ %table.table.assigned-projects
%thead
%tr
%th Assigned projects
@@ -44,7 +44,7 @@
.pull-right
= link_to 'Disable', [:admin, project.namespace.becomes(Namespace), project, runner_project], method: :delete, class: 'btn btn-danger btn-xs'
- %table.table
+ %table.table.unassigned-projects
%thead
%tr
%th Project
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index d35f332e1e0..f7abad54286 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -13,7 +13,7 @@
Explore Projects
.nav-controls
- = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
+ = form_tag request.path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short projects-list-filter', spellcheck: false, id: 'project-filter-form-field', tabindex: "2"
= render 'shared/projects/dropdown'
- if current_user.can_create_project?
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 62ebd69485c..aecefbc6e8f 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -33,7 +33,7 @@
= link_to "#shared", 'data-toggle' => 'tab' do
Shared Projects
.nav-controls
- = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
+ = form_tag request.path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
= render 'shared/projects/dropdown'
- if can? current_user, :create_projects, @group
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index 01648047ce2..8cc0b59edeb 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -28,8 +28,12 @@
.key &#8984; shift p
- else
.key ctrl shift p
-
%td Toggle Markdown preview
+ %tr
+ %td.shortcut
+ .key
+ %i.fa.fa-arrow-up
+ %td Edit last comment (when focused on an empty textarea)
%tbody
%tr
%th
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 199ab3c38c3..2234bf79c87 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -13,7 +13,7 @@
= image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
.username
= current_user.username
- = link_to '#', class: "nav-header-btn text-center pin-nav-btn #{'is-active' if pinned_nav?} js-nav-pin", title: 'Pin/Unpin navigation' do
+ = link_to '#', class: "nav-header-btn text-center pin-nav-btn has-tooltip #{'is-active' if pinned_nav?} js-nav-pin", title: pinned_nav? ? "Unpin navigation" : "Pin Navigation", data: {placement: 'right', container: 'body'} do
%span.sr-only Toggle navigation pinning
= icon('thumb-tack')
- if defined?(nav) && nav
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index ffdc7b7f504..66e5ec1ad1a 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -3,6 +3,7 @@
%ul.nav-links.scrolling-tabs
%li.fade-left
+ = icon('arrow-left')
= nav_link(controller: %w(dashboard admin projects users groups builds runners), html_options: {class: 'home'}) do
= link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
%span
@@ -37,3 +38,4 @@
%span
Spam Logs
%li.fade-right
+ = icon('arrow-right')
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 3bd04c2c8b4..f7aa9fab7cf 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -3,6 +3,7 @@
%ul.nav-links.scrolling-tabs
%li.fade-left
+ = icon('arrow-left')
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: 'Home' do
%span
@@ -32,3 +33,4 @@
%span
Members
%li.fade-right
+ = icon('arrow-right')
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index 110a72d3a3c..44ea939b7e4 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -1,5 +1,6 @@
%ul.nav-links.scrolling-tabs
%li.fade-left
+ = icon('arrow-left')
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do
%span
@@ -44,3 +45,4 @@
%span
Audit Log
%li.fade-right
+ = icon('arrow-right')
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 823051d1b83..1ace5d23ba3 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -26,6 +26,7 @@
%div{ class: nav_control_class }
%ul.nav-links.scrolling-tabs
%li.fade-left
+ = icon('arrow-left')
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
%span
@@ -110,3 +111,4 @@
= link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
Commits
%li.fade-right
+ = icon('arrow-right')
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 5afd83a522e..f77738f97f5 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -28,7 +28,7 @@
= label_tag :global_notification_level, "Global notification level", class: "label-light"
%br
.clearfix
- .form-group.pull-left
+ .form-group.pull-left.global-notification-setting
= render 'shared/notifications/button', notification_setting: @global_notification_setting, left_align: true
.clearfix
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 28a28282fd3..ca6714ef42b 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -14,8 +14,17 @@
%span This is a confidential issue. Your comment will not be visible to the public.
%li.pull-right
- %button.zen-control.zen-control-full.js-zen-enter{ type: 'button', tabindex: -1 }
- Go full screen
+ .toolbar-group
+ = markdown_toolbar_button({icon: "bold fw", data: { "md-tag" => "**" }, title: "Add bold text" })
+ = markdown_toolbar_button({icon: "italic fw", data: { "md-tag" => "*" }, title: "Add italic text" })
+ = markdown_toolbar_button({icon: "quote-right fw", data: { "md-tag" => "> ", "md-prepend" => true }, title: "Insert a quote" })
+ = markdown_toolbar_button({icon: "code fw", data: { "md-tag" => "`" }, title: "Insert code" })
+ = markdown_toolbar_button({icon: "list-ul fw", data: { "md-tag" => "* ", "md-prepend" => true }, title: "Add a bullet list" })
+ = markdown_toolbar_button({icon: "list-ol fw", data: { "md-tag" => "1. ", "md-prepend" => true }, title: "Add a numbered list" })
+ = markdown_toolbar_button({icon: "check-square-o fw", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: "Add a task list" })
+ .toolbar-group
+ %button.toolbar-btn.js-zen-enter.has-tooltip.hidden-xs{ type: "button", tabindex: -1, aria: { label: "Go full screen" }, title: "Go full screen", data: { container: "body" } }
+ =icon("arrows-alt fw")
.md-write-holder
= yield
@@ -24,7 +33,7 @@
- if defined?(referenced_users) && referenced_users
%div.referenced-users.hide
%span
- = icon('exclamation-triangle')
+ = icon("exclamation-triangle")
You are about to add
%strong
%span.js-referenced-users-count 0
diff --git a/app/views/projects/badges/index.html.haml b/app/views/projects/badges/index.html.haml
index ee63bc55a30..ac80951dd4f 100644
--- a/app/views/projects/badges/index.html.haml
+++ b/app/views/projects/badges/index.html.haml
@@ -7,7 +7,7 @@
%b Builds badge &middot;
= @build_badge.to_html
.pull-right
- = render 'shared/ref_switcher', destination: 'badges'
+ = render 'shared/ref_switcher', destination: 'badges', align_right: true
.panel-body
.row
.col-md-2.text-center
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index ae89637df60..29c7d45074a 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -17,6 +17,8 @@
= dropdown_tag("Choose a License template", options: { toggle_class: 'js-license-selector', title: "Choose a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } )
.gitignore-selector.js-gitignore-selector-wrap.hidden
= dropdown_tag("Choose a .gitignore template", options: { toggle_class: 'js-gitignore-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
+ .gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.hidden
+ = dropdown_tag("Choose a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } )
.encoding-selector
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index b8d8758fd2b..e38d1ff5ff0 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -24,8 +24,8 @@
%span.label.label-warning stuck
%p.commit-title
- - if commit_data = pipeline.commit_data
- = link_to_gfm truncate(commit_data.title, length: 60), namespace_project_commit_path(@project.namespace, @project, commit_data.id), class: "commit-row-message"
+ - if commit = pipeline.commit
+ = link_to_gfm truncate(commit.title, length: 60), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "commit-row-message"
- else
Cant find HEAD commit for this branch
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index a959b34a539..929496f81d8 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -10,29 +10,30 @@
= cache(cache_key) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
= commit_author_avatar(commit, size: 36)
- .commit-row-title
- %span.item-title
- = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
- %span.commit-row-message.visible-xs-inline
- &middot;
- = commit.short_id
- - if commit.status
- = render_commit_status(commit, cssclass: 'visible-xs-inline')
- - if commit.description?
- %a.text-expander.hidden-xs.js-toggle-button ...
+ .commit-info-block
+ .commit-row-title
+ %span.item-title
+ = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
+ %span.commit-row-message.visible-xs-inline
+ &middot;
+ = commit.short_id
+ - if commit.status
+ = render_commit_status(commit, cssclass: 'visible-xs-inline')
+ - if commit.description?
+ %a.text-expander.hidden-xs.js-toggle-button ...
- .commit-actions.hidden-xs
- - if commit.status
- = render_commit_status(commit, cssclass: 'btn btn-transparent')
- = clipboard_button_with_class({ clipboard_text: commit.id }, css_class: 'btn-transparent')
- = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent"
- = link_to_browse_code(project, commit)
+ .commit-actions.hidden-xs
+ - if commit.status
+ = render_commit_status(commit, cssclass: 'btn btn-transparent')
+ = clipboard_button_with_class({ clipboard_text: commit.id }, css_class: 'btn-transparent')
+ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent"
+ = link_to_browse_code(project, commit)
- - if commit.description?
- %pre.commit-row-description.js-toggle-content
- = preserve(markdown(escape_once(commit.description), pipeline: :single_line, author: commit.author))
+ - if commit.description?
+ %pre.commit-row-description.js-toggle-content
+ = preserve(markdown(escape_once(commit.description), pipeline: :single_line, author: commit.author))
- .commit-row-info
- = commit_author_link(commit, avatar: false, size: 24)
- authored
- #{time_ago_with_tooltip(commit.committed_date)}
+ .commit-row-info
+ = commit_author_link(commit, avatar: false, size: 24)
+ authored
+ #{time_ago_with_tooltip(commit.committed_date)}
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index 888c6b6701b..54dab4bff07 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -2,6 +2,7 @@
.nav-links.sub-nav.scrolling-tabs
%ul{ class: (container_class) }
%li.fade-left
+ = icon('arrow-left')
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
= link_to project_files_path(@project) do
Files
@@ -26,3 +27,4 @@
= link_to namespace_project_tags_path(@project.namespace, @project) do
Tags
%li.fade-right
+ = icon('arrow-right')
diff --git a/app/views/projects/container_registry/_tag.html.haml b/app/views/projects/container_registry/_tag.html.haml
index f35faa6afb5..10822b6184c 100644
--- a/app/views/projects/container_registry/_tag.html.haml
+++ b/app/views/projects/container_registry/_tag.html.haml
@@ -3,9 +3,9 @@
= escape_once(tag.name)
= clipboard_button(clipboard_text: "docker pull #{tag.path}")
%td
- - if layer = tag.layers.first
- %span.has-tooltip{ title: "#{layer.revision}" }
- = layer.short_revision
+ - if tag.revision
+ %span.has-tooltip{ title: "#{tag.revision}" }
+ = tag.short_revision
- else
\-
%td
diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml
index ae9e77e7d89..a03f117291f 100644
--- a/app/views/projects/environments/index.html.haml
+++ b/app/views/projects/environments/index.html.haml
@@ -3,16 +3,24 @@
= render "projects/pipelines/head"
%div{ class: (container_class) }
- - if can?(current_user, :create_environment, @project)
+ - if can?(current_user, :create_environment, @project) && !@environments.blank?
.top-area
.nav-controls
= link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do
New environment
- if @environments.blank?
- %ul.content-list.environments
- %li.nothing-here-block
- No environments to show
+ .blank-state.blank-state-no-icon
+ %h2.blank-state-title
+ You don't have any environments right now.
+ %p.blank-state-text
+ Environments are places where code gets deployed, such as staging or production.
+ %br
+ = succeed "." do
+ = link_to "Read more about environments", help_page_path("ci", "environments")
+ - if can?(current_user, :create_environment, @project)
+ = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do
+ New environment
- else
.table-holder
%table.table.environments
diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml
index 54465828ba9..da325efecd2 100644
--- a/app/views/projects/environments/new.html.haml
+++ b/app/views/projects/environments/new.html.haml
@@ -4,6 +4,9 @@
.col-lg-3
%h4.prepend-top-0
New Environment
- %p Environments allow you to track deployments of your application
+ %p
+ Environments allow you to track deployments of your application
+ = succeed "." do
+ = link_to "Read more about environments", help_page_path("ci", "environments")
= render 'form'
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 069b77b5adf..4c15e2759d6 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -13,10 +13,14 @@
= link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete
- if @deployments.blank?
- %ul.content-list.environments
- %li.nothing-here-block
- No deployments for
- %strong= @environment.name
+ .blank-state.blank-state-no-icon
+ %h2.blank-state-title
+ You don't have any deployments right now.
+ %p.blank-state-text
+ Define environments in the deploy stage(s) in
+ %code .gitlab-ci.yml
+ to track deployments here.
+ = link_to "Read more", help_page_path("ci", "environments"), class: "btn btn-success"
- else
.table-holder
%table.table.environments
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
index 393998f15b9..53dd300c35c 100644
--- a/app/views/projects/merge_requests/_discussion.html.haml
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -1,8 +1,8 @@
- content_for :note_actions do
- if can?(current_user, :update_merge_request, @merge_request)
- if @merge_request.open?
- = link_to 'Close merge request', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-nr btn-comment btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request", data: {original_text: "Close merge request", alternative_text: "Comment & close merge request"}
+ = link_to 'Close merge request', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-nr btn-comment btn-close close-mr-link js-note-target-close", title: "Close merge request", data: {original_text: "Close merge request", alternative_text: "Comment & close merge request"}
- if @merge_request.closed?
- = link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request", data: {original_text: "Reopen merge request", alternative_text: "Comment & reopen merge request"}
+ = link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request", data: {original_text: "Reopen merge request", alternative_text: "Comment & reopen merge request"}
#notes= render "projects/notes/notes_with_form"
diff --git a/app/views/projects/notes/_hints.html.haml b/app/views/projects/notes/_hints.html.haml
index 0b002043408..7d1cbc62e86 100644
--- a/app/views/projects/notes/_hints.html.haml
+++ b/app/views/projects/notes/_hints.html.haml
@@ -5,4 +5,4 @@
is supported
%button.toolbar-button.markdown-selector{ type: 'button', tabindex: '-1' }
= icon('file-image-o', class: 'toolbar-button-icon')
- Attach a file
+ Attach a file \ No newline at end of file
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index bcdbff08011..c04d291412c 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -18,9 +18,9 @@
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
.note-actions
- access = note.project.team.human_max_access(note.author.id)
- - if access
+ - if access and not note.system
%span.note-role.hidden-xs= access
- - if current_user
+ - if current_user and not note.system
= link_to '#', title: 'Award Emoji', class: 'note-action-button note-emoji-button js-add-award js-note-emoji', data: { position: 'right' } do
= icon('spinner spin')
= icon('smile-o')
diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml
index d62f5c8f131..c45a9d4f81f 100644
--- a/app/views/projects/runners/_form.html.haml
+++ b/app/views/projects/runners/_form.html.haml
@@ -13,6 +13,12 @@
= f.check_box :run_untagged
%span.light Indicates whether this runner can pick jobs without tags
.form-group
+ = label :locked, 'Lock to current projects', class: 'control-label'
+ .col-sm-10
+ .checkbox
+ = f.check_box :locked
+ %span.light When a runner is locked, it cannot be assigned to other projects
+ .form-group
= label_tag :token, class: 'control-label' do
Token
.col-sm-10
diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml
index 96e2aac451f..85225857758 100644
--- a/app/views/projects/runners/_runner.html.haml
+++ b/app/views/projects/runners/_runner.html.haml
@@ -2,8 +2,10 @@
%h4
= runner_status_icon(runner)
%span.monospace
- - if @runners.include?(runner)
+ - if @project_runners.include?(runner)
= link_to runner.short_sha, runner_path(runner)
+ - if runner.locked?
+ = icon('lock', class: 'has-tooltip', title: 'Locked to current projects')
%small
= link_to edit_namespace_project_runner_path(@project.namespace, @project, runner) do
%i.fa.fa-edit.btn
@@ -11,7 +13,7 @@
= runner.short_sha
.pull-right
- - if @runners.include?(runner)
+ - if @project_runners.include?(runner)
- if runner.belongs_to_one_project?
= link_to 'Remove runner', runner_path(runner), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
- else
diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml
index 8ae9f0d95f7..d469dda5b81 100644
--- a/app/views/projects/runners/_specific_runners.html.haml
+++ b/app/views/projects/runners/_specific_runners.html.haml
@@ -17,13 +17,13 @@
Start runner!
-- if @runners.any?
+- if @project_runners.any?
%h4.underlined-title Runners activated for this project
%ul.bordered-list.activated-specific-runners
- = render partial: 'runner', collection: @runners, as: :runner
+ = render partial: 'runner', collection: @project_runners, as: :runner
-- if @specific_runners.any?
+- if @assignable_runners.any?
%h4.underlined-title Available specific runners
%ul.bordered-list.available-specific-runners
- = render partial: 'runner', collection: @specific_runners, as: :runner
- = paginate @specific_runners
+ = render partial: 'runner', collection: @assignable_runners, as: :runner
+ = paginate @assignable_runners
diff --git a/app/views/projects/runners/show.html.haml b/app/views/projects/runners/show.html.haml
index f24e1b9144e..61b99f35d74 100644
--- a/app/views/projects/runners/show.html.haml
+++ b/app/views/projects/runners/show.html.haml
@@ -23,6 +23,9 @@
%td Can run untagged jobs
%td= @runner.run_untagged? ? 'Yes' : 'No'
%tr
+ %td Locked to this project
+ %td= @runner.locked? ? 'Yes' : 'No'
+ %tr
%td Tags
%td
- @runner.tag_list.each do |tag|
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index e9ca46a74bf..5f041aedfc0 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -57,6 +57,10 @@
%li.missing
= link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do
Add Contribution guide
+ - unless @repository.gitlab_ci_yml
+ %li.missing
+ = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do
+ Set up CI
- if @repository.commit
.content-block.second-block.white
diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml
index 6f9809a9ed8..aa18e6f236f 100644
--- a/app/views/shared/_event_filter.html.haml
+++ b/app/views/shared/_event_filter.html.haml
@@ -1,7 +1,9 @@
%ul.nav-links.event-filter.scrolling-tabs
%li.fade-left
+ = icon('arrow-left')
= event_filter_link EventFilter.push, 'Push events'
= event_filter_link EventFilter.merged, 'Merge events'
= event_filter_link EventFilter.comments, 'Comments'
= event_filter_link EventFilter.team, 'Team'
%li.fade-right
+ = icon('arrow-right')
diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml
index eb2e1919e19..ea7162d4d63 100644
--- a/app/views/shared/_ref_switcher.html.haml
+++ b/app/views/shared/_ref_switcher.html.haml
@@ -1,7 +1,14 @@
+- dropdown_toggle_text = @ref || @project.default_branch
= form_tag switch_namespace_project_refs_path(@project.namespace, @project), method: :get, class: "project-refs-form" do
- = select_tag "ref", grouped_options_refs, class: "project-refs-select select2 select2-sm"
= hidden_field_tag :destination, destination
- if defined?(path)
= hidden_field_tag :path, path
- @options && @options.each do |key, value|
= hidden_field_tag key, value, id: nil
+ .dropdown
+ = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_namespace_project_path(@project.namespace, @project) }, { toggle_class: "js-project-refs-dropdown" }
+ .dropdown-menu.dropdown-menu-selectable{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) }
+ = dropdown_title "Switch branch/tag"
+ = dropdown_filter "Search branches and tags"
+ = dropdown_content
+ = dropdown_loading
diff --git a/config/routes.rb b/config/routes.rb
index de6094fa0ed..e45293cdf7f 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -295,7 +295,7 @@ Rails.application.routes.draw do
post :repository_check
end
- resources :runner_projects
+ resources :runner_projects, only: [:create, :destroy]
end
end
@@ -479,6 +479,7 @@ Rails.application.routes.draw do
get :download_export
get :autocomplete_sources
get :activity
+ get :refs
end
scope module: :projects do
diff --git a/db/migrate/20160509091049_add_locked_to_ci_runner.rb b/db/migrate/20160509091049_add_locked_to_ci_runner.rb
new file mode 100644
index 00000000000..3fbaef3b7f0
--- /dev/null
+++ b/db/migrate/20160509091049_add_locked_to_ci_runner.rb
@@ -0,0 +1,13 @@
+class AddLockedToCiRunner < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default(:ci_runners, :locked, :boolean,
+ default: false, allow_null: false)
+ end
+
+ def down
+ remove_column(:ci_runners, :locked)
+ end
+end
diff --git a/db/migrate/20160615191922_set_missing_stage_on_ci_builds.rb b/db/migrate/20160615191922_set_missing_stage_on_ci_builds.rb
new file mode 100644
index 00000000000..bd0463886bc
--- /dev/null
+++ b/db/migrate/20160615191922_set_missing_stage_on_ci_builds.rb
@@ -0,0 +1,9 @@
+class SetMissingStageOnCiBuilds < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ def up
+ update_column_in_batches(:ci_builds, :stage, :test) do |table, query|
+ query.where(table[:stage].eq(nil))
+ end
+ end
+end
diff --git a/db/migrate/20160620115026_add_index_on_runners_locked.rb b/db/migrate/20160620115026_add_index_on_runners_locked.rb
new file mode 100644
index 00000000000..dfa5110dea4
--- /dev/null
+++ b/db/migrate/20160620115026_add_index_on_runners_locked.rb
@@ -0,0 +1,12 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddIndexOnRunnersLocked < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ def change
+ add_concurrent_index :ci_runners, :locked
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 4c1eb8cc1b3..7a8377f687c 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20160616103948) do
+ActiveRecord::Schema.define(version: 20160620115026) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -287,9 +287,11 @@ ActiveRecord::Schema.define(version: 20160616103948) do
t.string "platform"
t.string "architecture"
t.boolean "run_untagged", default: true, null: false
+ t.boolean "locked", default: false, null: false
end
add_index "ci_runners", ["description"], name: "index_ci_runners_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
+ add_index "ci_runners", ["locked"], name: "index_ci_runners_on_locked", using: :btree
add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree
add_index "ci_runners", ["token"], name: "index_ci_runners_on_token_trigram", using: :gin, opclasses: {"token"=>"gin_trgm_ops"}
@@ -708,6 +710,7 @@ ActiveRecord::Schema.define(version: 20160616103948) do
t.integer "level", default: 0, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
+ t.text "events"
end
add_index "notification_settings", ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type", using: :btree
diff --git a/doc/ci/README.md b/doc/ci/README.md
index ef72df97ce6..5a1cb5319c6 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -5,6 +5,7 @@
- [Get started with GitLab CI](quick_start/README.md)
- [CI examples for various languages](examples/README.md)
- [Learn how to enable or disable GitLab CI](enable_or_disable_ci.md)
+- [Environments and deployments](environments.md)
- [Learn how `.gitlab-ci.yml` works](yaml/README.md)
- [Configure a Runner, the application that runs your builds](runners/README.md)
- [Use Docker images with GitLab Runner](docker/using_docker_images.md)
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
new file mode 100644
index 00000000000..040379bb381
--- /dev/null
+++ b/doc/ci/environments.md
@@ -0,0 +1,58 @@
+# Introduction to environments and deployments
+
+>**Note:**
+Introduced in GitLab 8.9.
+
+## Environments
+
+Environments are places where code gets deployed, such as staging or production.
+CI/CD [Pipelines] usually have one or more [jobs] that deploy to an environment.
+Defining environments in a project's `.gitlab-ci.yml` lets developers track
+[deployments] to these environments.
+
+## Deployments
+
+Deployments are created when [jobs] deploy versions of code to [environments].
+
+## Defining environments
+
+You can create and delete environments manually in the web interface, but we
+recommend that you define your environments in `.gitlab-ci.yml` first, which
+will automatically create environments for you after the first deploy.
+
+The `environment` is just a hint for GitLab that this job actually deploys to
+this environment. Each time the job succeeds, a deployment is recorded,
+remembering the git SHA and environment.
+
+Add something like this to your `.gitlab-ci.yml`:
+```
+production:
+ stage: deploy
+ script: dpl...
+ environment: production
+```
+
+See full [documentation](yaml/README.md#environment).
+
+## Seeing environment status
+
+You can find the environment list under **Pipelines > Environments** for your
+project. You'll see the git SHA and date of the last deployment to each
+environment defined.
+
+>**Note:**
+Only deploys that happen after your `.gitlab-ci.yml` is properly configured will
+show up in the environments and deployments lists.
+
+## Seeing deployment history
+
+Clicking on an environment will show the history of deployments.
+
+>**Note:**
+Only deploys that happen after your `.gitlab-ci.yml` is properly configured will
+show up in the environments and deployments lists.
+
+[Pipelines]: quick_start/README.md
+[jobs]: yaml/README.md#jobs
+[environments]: #environments
+[deployments]: #deployments
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index 400784da617..ddebd987650 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -96,6 +96,12 @@ To register the runner, run the command below and follow instructions:
sudo gitlab-ci-multi-runner register
```
+### Lock a specific runner from being enabled for other projects
+
+You can configure a runner to assign it exclusively to a project. When a
+runner is locked this way, it can no longer be enabled for other projects.
+This setting is available on each runner in *Project Settings* > *Runners*.
+
### Making an existing Shared Runner Specific
If you are an admin on your GitLab instance,
@@ -128,7 +134,7 @@ the appropriate dependencies to run Rails test suites.
### Prevent runner with tags from picking jobs without tags
You can configure a runner to prevent it from picking jobs with tags when
-the runnner does not have tags assigned. This setting is available on each
+the runner does not have tags assigned. This setting is available on each
runner in *Project Settings* > *Runners*.
### Be careful with sensitive information
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 6053bf14536..d0fbcbe9988 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -530,14 +530,18 @@ The above script will:
### environment
>**Note:**
-Introduced in GitLab v8.9.0.
+Introduced in GitLab 8.9.
-`environment` is used to define that job does deployment to specific environment.
-This allows to easily track all deployments to your environments straight from GitLab.
+`environment` is used to define that a job deploys to a specific environment.
+This allows easy tracking of all deployments to your environments straight from
+GitLab.
-If `environment` is specified and no environment under that name does exist a new one will be created automatically.
+If `environment` is specified and no environment under that name exists, a new
+one will be created automatically.
-The `environment` name must contain only letters, digits, '-' and '_'.
+The `environment` name must contain only letters, digits, '-' and '_'. Common
+names are `qa`, `staging`, and `production`, but you can use whatever name works
+with your workflow.
---
@@ -550,7 +554,8 @@ deploy to production:
environment: production
```
-The `deploy to production` job will be marked as doing deployment to `production` environment.
+The `deploy to production` job will be marked as doing deployment to
+`production` environment.
### artifacts
diff --git a/doc/project_services/emails_on_push.md b/doc/project_services/emails_on_push.md
new file mode 100644
index 00000000000..2f9f36f962e
--- /dev/null
+++ b/doc/project_services/emails_on_push.md
@@ -0,0 +1,17 @@
+## Enabling emails on push
+
+To receive email notifications for every change that is pushed to the project, visit
+your project's **Settings > Services > Emails on push** and activate the service.
+
+In the _Recipients_ area, provide a list of emails separated by commas.
+
+You can configure any of the following settings depending on your preference.
+
++ **Push events** - Email will be triggered when a push event is recieved
++ **Tag push events** - Email will be triggered when a tag is created and pushed
++ **Send from committer** - Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. `user@gitlab.com`).
++ **Disable code diffs** - Don't include possibly sensitive code diffs in notification body.
+
+---
+
+![Email on push service settings](img/emails_on_push_service.png)
diff --git a/doc/project_services/img/emails_on_push_service.png b/doc/project_services/img/emails_on_push_service.png
new file mode 100644
index 00000000000..cd6f79ad1eb
--- /dev/null
+++ b/doc/project_services/img/emails_on_push_service.png
Binary files differ
diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md
index a5af620d9be..f81a035f70b 100644
--- a/doc/project_services/project_services.md
+++ b/doc/project_services/project_services.md
@@ -33,7 +33,7 @@ further configuration instructions and details. Contributions are welcome.
| Campfire | Simple web-based real-time group chat |
| Custom Issue Tracker | Custom issue tracker |
| Drone CI | Continuous Integration platform built on Docker, written in Go |
-| Emails on push | Email the commits and diff of each push to a list of recipients |
+| [Emails on push](emails_on_push.md) | Email the commits and diff of each push to a list of recipients |
| External Wiki | Replaces the link to the internal wiki with a link to an external wiki |
| Flowdock | Flowdock is a collaboration web app for technical teams |
| Gemnasium | Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities |
diff --git a/doc/update/8.6-to-8.7.md b/doc/update/8.6-to-8.7.md
index bb463d43a7c..cb66ef920bb 100644
--- a/doc/update/8.6-to-8.7.md
+++ b/doc/update/8.6-to-8.7.md
@@ -45,7 +45,7 @@ sudo -u git -H git checkout 8-7-stable-ee
```bash
cd /home/git/gitlab-shell
-sudo -u git -H git fetch --all --tags
+sudo -u git -H git fetch --tags
sudo -u git -H git checkout v2.7.2
```
diff --git a/doc/workflow/add-user/add-user.md b/doc/workflow/add-user/add-user.md
index fffa0aba57f..4b551130255 100644
--- a/doc/workflow/add-user/add-user.md
+++ b/doc/workflow/add-user/add-user.md
@@ -8,7 +8,7 @@ You should have `master` or `owner` permissions to add or import a new user
to your project.
The first step to add or import a user, go to your project and click on
-**Members** on the left side of your screen.
+**Members** in the drop-down menu on the right side of your screen.
![Members](img/add_user_members_menu.png)
@@ -87,3 +87,25 @@ invitation, change their access level or even delete them.
Once the user accepts the invitation, they will be prompted to create a new
GitLab account using the same e-mail address the invitation was sent to.
+
+## Request access to a project
+
+As a user, you can request to be a member of a project. Go to the project you'd
+like to be a member of, and click the **Request Access** button on the right
+side of your screen.
+
+![Request access button](img/request_access_button.png)
+
+---
+
+Project owners & masters will be notified of your request and will be able to approve or
+decline it on the members page.
+
+![Manage access requests](img/access_requests_management.png)
+
+---
+
+If you change your mind before your request is approved, just click the
+**Withdraw Access Request** button.
+
+![Withdraw access request button](img/withdraw_access_request_button.png)
diff --git a/doc/workflow/add-user/img/access_requests_management.png b/doc/workflow/add-user/img/access_requests_management.png
new file mode 100644
index 00000000000..e9641cb4f85
--- /dev/null
+++ b/doc/workflow/add-user/img/access_requests_management.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_email_accept.png b/doc/workflow/add-user/img/add_user_email_accept.png
index 910affc9659..18aabf93d50 100644
--- a/doc/workflow/add-user/img/add_user_email_accept.png
+++ b/doc/workflow/add-user/img/add_user_email_accept.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_email_ready.png b/doc/workflow/add-user/img/add_user_email_ready.png
index 5f02ce89b3e..385d64330c0 100644
--- a/doc/workflow/add-user/img/add_user_email_ready.png
+++ b/doc/workflow/add-user/img/add_user_email_ready.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_email_search.png b/doc/workflow/add-user/img/add_user_email_search.png
index 140979fbe13..84741edbca4 100644
--- a/doc/workflow/add-user/img/add_user_email_search.png
+++ b/doc/workflow/add-user/img/add_user_email_search.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_give_permissions.png b/doc/workflow/add-user/img/add_user_give_permissions.png
index 8ef9156c8d5..7e580384e54 100644
--- a/doc/workflow/add-user/img/add_user_give_permissions.png
+++ b/doc/workflow/add-user/img/add_user_give_permissions.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_import_members_from_another_project.png b/doc/workflow/add-user/img/add_user_import_members_from_another_project.png
index 5770d5cf0c4..8dbd73a5bc8 100644
--- a/doc/workflow/add-user/img/add_user_import_members_from_another_project.png
+++ b/doc/workflow/add-user/img/add_user_import_members_from_another_project.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_imported_members.png b/doc/workflow/add-user/img/add_user_imported_members.png
index dea4b3f40ad..abac1f59c02 100644
--- a/doc/workflow/add-user/img/add_user_imported_members.png
+++ b/doc/workflow/add-user/img/add_user_imported_members.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_list_members.png b/doc/workflow/add-user/img/add_user_list_members.png
index 7daa6ca7d9e..e17d88c6f5f 100644
--- a/doc/workflow/add-user/img/add_user_list_members.png
+++ b/doc/workflow/add-user/img/add_user_list_members.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_members_menu.png b/doc/workflow/add-user/img/add_user_members_menu.png
index f1797b95f67..ec5d39f402d 100644
--- a/doc/workflow/add-user/img/add_user_members_menu.png
+++ b/doc/workflow/add-user/img/add_user_members_menu.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_search_people.png b/doc/workflow/add-user/img/add_user_search_people.png
index 5ac10ce80d4..eaa062376f4 100644
--- a/doc/workflow/add-user/img/add_user_search_people.png
+++ b/doc/workflow/add-user/img/add_user_search_people.png
Binary files differ
diff --git a/doc/workflow/add-user/img/request_access_button.png b/doc/workflow/add-user/img/request_access_button.png
new file mode 100644
index 00000000000..984d640b0f0
--- /dev/null
+++ b/doc/workflow/add-user/img/request_access_button.png
Binary files differ
diff --git a/doc/workflow/add-user/img/withdraw_access_request_button.png b/doc/workflow/add-user/img/withdraw_access_request_button.png
new file mode 100644
index 00000000000..ff54a0e4384
--- /dev/null
+++ b/doc/workflow/add-user/img/withdraw_access_request_button.png
Binary files differ
diff --git a/doc/workflow/groups.md b/doc/workflow/groups.md
index 34ada1774d8..1a316e80976 100644
--- a/doc/workflow/groups.md
+++ b/doc/workflow/groups.md
@@ -51,6 +51,28 @@ If necessary, you can increase the access level of an individual user for a spec
![Barry effectively has 'Master' access to GitLab CI now](groups/override_access_level.png)
+## Request access to a group
+
+As a user, you can request to be a member of a group. Go to the group you'd
+like to be a member of, and click the **Request Access** button on the right
+side of your screen.
+
+![Request access button](groups/request_access_button.png)
+
+---
+
+Group owners & masters will be notified of your request and will be able to approve or
+decline it on the members page.
+
+![Manage access requests](groups/access_requests_management.png)
+
+---
+
+If you change your mind before your request is approved, just click the
+**Withdraw Access Request** button.
+
+![Withdraw access request button](groups/withdraw_access_request_button.png)
+
## Managing group memberships via LDAP
In GitLab Enterprise Edition it is possible to manage GitLab group memberships using LDAP groups.
diff --git a/doc/workflow/groups/access_requests_management.png b/doc/workflow/groups/access_requests_management.png
new file mode 100644
index 00000000000..ffede8e9bd6
--- /dev/null
+++ b/doc/workflow/groups/access_requests_management.png
Binary files differ
diff --git a/doc/workflow/groups/request_access_button.png b/doc/workflow/groups/request_access_button.png
new file mode 100644
index 00000000000..ff0ac8747a7
--- /dev/null
+++ b/doc/workflow/groups/request_access_button.png
Binary files differ
diff --git a/doc/workflow/groups/withdraw_access_request_button.png b/doc/workflow/groups/withdraw_access_request_button.png
new file mode 100644
index 00000000000..99d7a326ed8
--- /dev/null
+++ b/doc/workflow/groups/withdraw_access_request_button.png
Binary files differ
diff --git a/doc/workflow/shortcuts.png b/doc/workflow/shortcuts.png
index beb6c53ec77..16be0413b64 100644
--- a/doc/workflow/shortcuts.png
+++ b/doc/workflow/shortcuts.png
Binary files differ
diff --git a/features/steps/profile/notifications.rb b/features/steps/profile/notifications.rb
index 979f4692d5a..7e339443b75 100644
--- a/features/steps/profile/notifications.rb
+++ b/features/steps/profile/notifications.rb
@@ -15,8 +15,6 @@ class Spinach::Features::ProfileNotifications < Spinach::FeatureSteps
end
step 'I should see Notification saved message' do
- page.within '.flash-container' do
- expect(page).to have_content 'Notification settings saved'
- end
+ expect(page).to have_content 'On mention'
end
end
diff --git a/features/steps/project/network_graph.rb b/features/steps/project/network_graph.rb
index 9b59b682676..019b3124a86 100644
--- a/features/steps/project/network_graph.rb
+++ b/features/steps/project/network_graph.rb
@@ -20,11 +20,11 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
end
step 'page should select "master" in select box' do
- expect(page).to have_selector '.select2-chosen', text: "master"
+ expect(page).to have_selector '.dropdown-menu-toggle', text: "master"
end
step 'page should select "v1.0.0" in select box' do
- expect(page).to have_selector '.select2-chosen', text: "v1.0.0"
+ expect(page).to have_selector '.dropdown-menu-toggle', text: "v1.0.0"
end
step 'page should have "master" on graph' do
@@ -40,11 +40,19 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
end
When 'I switch ref to "feature"' do
- select 'feature', from: 'ref'
+ first('.js-project-refs-dropdown').click
+
+ page.within '.project-refs-form' do
+ click_link 'feature'
+ end
end
When 'I switch ref to "v1.0.0"' do
- select 'v1.0.0', from: 'ref'
+ first('.js-project-refs-dropdown').click
+
+ page.within '.project-refs-form' do
+ click_link 'v1.0.0'
+ end
end
When 'click "Show only selected branch" checkbox' do
@@ -68,11 +76,11 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
end
step 'page should select "feature" in select box' do
- expect(page).to have_selector '.select2-chosen', text: "feature"
+ expect(page).to have_selector '.dropdown-menu-toggle', text: "feature"
end
step 'page should select "v1.0.0" in select box' do
- expect(page).to have_selector '.select2-chosen', text: "v1.0.0"
+ expect(page).to have_selector '.dropdown-menu-toggle', text: "v1.0.0"
end
step 'page should have "feature" on graph' do
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index 98b57e5cbfb..76fefee9254 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -134,8 +134,8 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'I should see Notification saved message' do
- page.within '.flash-container' do
- expect(page).to have_content 'Notification settings saved'
+ page.within '#notifications-button' do
+ expect(page).to have_content 'On mention'
end
end
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index 79a3ed8197e..0fe046dcbf6 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -290,15 +290,23 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step "I switch ref to 'test'" do
- select "'test'", from: 'ref'
+ first('.js-project-refs-dropdown').click
+
+ page.within '.project-refs-form' do
+ click_link 'test'
+ end
end
step "I switch ref to fix" do
- select "fix", from: 'ref'
+ first('.js-project-refs-dropdown').click
+
+ page.within '.project-refs-form' do
+ click_link 'fix'
+ end
end
step "I see the ref 'test' has been selected" do
- expect(page).to have_selector '.select2-chosen', text: "'test'"
+ expect(page).to have_selector '.dropdown-toggle-text', text: "'test'"
end
step "I visit the 'test' tree" do
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 0e7a1cc2623..f8f680a6311 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -33,7 +33,6 @@ module API
mount ::API::Commits
mount ::API::DeployKeys
mount ::API::Files
- mount ::API::Gitignores
mount ::API::GroupMembers
mount ::API::Groups
mount ::API::Internal
@@ -58,6 +57,7 @@ module API
mount ::API::Subscriptions
mount ::API::SystemHooks
mount ::API::Tags
+ mount ::API::Templates
mount ::API::Triggers
mount ::API::Users
mount ::API::Variables
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 2e397643ed1..5a23a18fe9c 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -423,6 +423,7 @@ module API
class RunnerDetails < Runner
expose :tag_list
expose :run_untagged
+ expose :locked
expose :version, :revision, :platform, :architecture
expose :contacted_at
expose :token, if: lambda { |runner, options| options[:current_user].is_admin? || !runner.is_shared? }
@@ -444,11 +445,7 @@ module API
expose :created_at, :started_at, :finished_at
expose :user, with: User
expose :artifacts_file, using: BuildArtifactFile, if: -> (build, opts) { build.artifacts? }
- expose :commit, with: RepoCommit do |repo_obj, _options|
- if repo_obj.respond_to?(:commit)
- repo_obj.commit.commit_data
- end
- end
+ expose :commit, with: RepoCommit
expose :runner, with: Runner
end
@@ -472,11 +469,11 @@ module API
expose :content
end
- class GitignoresList < Grape::Entity
+ class TemplatesList < Grape::Entity
expose :name
end
- class Gitignore < Grape::Entity
+ class Template < Grape::Entity
expose :name, :content
end
end
diff --git a/lib/api/gitignores.rb b/lib/api/gitignores.rb
deleted file mode 100644
index 270c9501dd2..00000000000
--- a/lib/api/gitignores.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-module API
- class Gitignores < Grape::API
-
- # Get the list of the available gitignore templates
- #
- # Example Request:
- # GET /gitignores
- get 'gitignores' do
- present Gitlab::Gitignore.all, with: Entities::GitignoresList
- end
-
- # Get the text for a specific gitignore
- #
- # Parameters:
- # name (required) - The name of a license
- #
- # Example Request:
- # GET /gitignores/Elixir
- #
- get 'gitignores/:name' do
- required_attributes! [:name]
-
- gitignore = Gitlab::Gitignore.find(params[:name])
- not_found!('.gitignore') unless gitignore
-
- present gitignore, with: Entities::Gitignore
- end
- end
-end
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 3ac7b50c4ce..1d361569d59 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -23,8 +23,6 @@ module API
end
post "/allowed" do
- Gitlab::Metrics.action = 'Grape#/internal/allowed'
-
status 200
actor =
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index 4faba9dc87b..ecc8f2fc5a2 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -49,7 +49,7 @@ module API
runner = get_runner(params[:id])
authenticate_update_runner!(runner)
- attrs = attributes_for_keys [:description, :active, :tag_list, :run_untagged]
+ attrs = attributes_for_keys [:description, :active, :tag_list, :run_untagged, :locked]
if runner.update(attrs)
present runner, with: Entities::RunnerDetails, current_user: current_user
else
@@ -96,9 +96,14 @@ module API
runner = get_runner(params[:runner_id])
authenticate_enable_runner!(runner)
- Ci::RunnerProject.create(runner: runner, project: user_project)
- present runner, with: Entities::Runner
+ runner_project = runner.assign_to(user_project)
+
+ if runner_project.persisted?
+ present runner, with: Entities::Runner
+ else
+ conflict!("Runner was already enabled for this project")
+ end
end
# Disable project's runner
@@ -163,6 +168,7 @@ module API
def authenticate_enable_runner!(runner)
forbidden!("Runner is shared") if runner.is_shared?
+ forbidden!("Runner is locked") if runner.locked?
return if current_user.is_admin?
forbidden!("No access granted") unless user_can_access_runner?(runner)
end
diff --git a/lib/api/templates.rb b/lib/api/templates.rb
new file mode 100644
index 00000000000..18408797756
--- /dev/null
+++ b/lib/api/templates.rb
@@ -0,0 +1,36 @@
+module API
+ class Templates < Grape::API
+ TEMPLATE_TYPES = {
+ gitignores: Gitlab::Template::Gitignore,
+ gitlab_ci_ymls: Gitlab::Template::GitlabCiYml
+ }.freeze
+
+ TEMPLATE_TYPES.each do |template, klass|
+ # Get the list of the available template
+ #
+ # Example Request:
+ # GET /gitignores
+ # GET /gitlab_ci_ymls
+ get template.to_s do
+ present klass.all, with: Entities::TemplatesList
+ end
+
+ # Get the text for a specific template
+ #
+ # Parameters:
+ # name (required) - The name of a template
+ #
+ # Example Request:
+ # GET /gitignores/Elixir
+ # GET /gitlab_ci_ymls/Ruby
+ get "#{template}/:name" do
+ required_attributes! [:name]
+
+ new_template = klass.find(params[:name])
+ not_found!(template.to_s.singularize) unless new_template
+
+ present new_template, with: Entities::Template
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index ea21c7b041c..c78da404607 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -14,6 +14,8 @@ module Banzai
def call
return doc unless linkable_files?
+ @uri_types = {}
+
doc.search('a:not(.gfm)').each do |el|
process_link_attr el.attribute('href')
end
@@ -48,7 +50,7 @@ module Banzai
uri.path = [
relative_url_root,
context[:project].path_with_namespace,
- path_type(file_path),
+ uri_type(file_path),
ref || context[:project].default_branch, # if no ref exists, point to the default branch
file_path
].compact.join('/').squeeze('/').chomp('/')
@@ -87,7 +89,7 @@ module Banzai
return path unless request_path
parts = request_path.split('/')
- parts.pop if path_type(request_path) != 'tree'
+ parts.pop if uri_type(request_path) != :tree
while path.start_with?('../')
parts.pop
@@ -98,45 +100,20 @@ module Banzai
end
def file_exists?(path)
- return false if path.nil?
- repository.blob_at(current_sha, path).present? ||
- repository.tree(current_sha, path).entries.any?
- end
-
- # Get the type of the given path
- #
- # path - String path to check
- #
- # Examples:
- #
- # path_type('doc/README.md') # => 'blob'
- # path_type('doc/logo.png') # => 'raw'
- # path_type('doc/api') # => 'tree'
- #
- # Returns a String
- def path_type(path)
- unescaped_path = Addressable::URI.unescape(path)
-
- if tree?(unescaped_path)
- 'tree'
- elsif image?(unescaped_path)
- 'raw'
- else
- 'blob'
- end
+ path.present? && !!uri_type(path)
end
- def tree?(path)
- repository.tree(current_sha, path).entries.any?
- end
+ def uri_type(path)
+ @uri_types[path] ||= begin
+ unescaped_path = Addressable::URI.unescape(path)
- def image?(path)
- repository.blob_at(current_sha, path).try(:image?)
+ current_commit.uri_type(unescaped_path)
+ end
end
- def current_sha
- context[:commit].try(:id) ||
- ref ? repository.commit(ref).try(:sha) : repository.head_commit.sha
+ def current_commit
+ @current_commit ||= context[:commit] ||
+ ref ? repository.commit(ref) : repository.head_commit
end
def relative_url_root
@@ -148,7 +125,7 @@ module Banzai
end
def repository
- context[:project].try(:repository)
+ @repository ||= context[:project].try(:repository)
end
end
end
diff --git a/lib/ci/api/runners.rb b/lib/ci/api/runners.rb
index 0c41f22c7c5..bcc82969eb3 100644
--- a/lib/ci/api/runners.rb
+++ b/lib/ci/api/runners.rb
@@ -28,12 +28,9 @@ module Ci
post "register" do
required_attributes! [:token]
- attributes = { description: params[:description],
- tag_list: params[:tag_list] }
-
- unless params[:run_untagged].nil?
- attributes[:run_untagged] = params[:run_untagged]
- end
+ attributes = attributes_for_keys(
+ [:description, :tag_list, :run_untagged, :locked]
+ )
runner =
if runner_registration_token_valid?
diff --git a/lib/container_registry/tag.rb b/lib/container_registry/tag.rb
index 7a0929d774e..708d01b95a1 100644
--- a/lib/container_registry/tag.rb
+++ b/lib/container_registry/tag.rb
@@ -3,6 +3,7 @@ module ContainerRegistry
attr_reader :repository, :name
delegate :registry, :client, to: :repository
+ delegate :revision, :short_revision, to: :config_blob, allow_nil: true
def initialize(repository, name)
@repository, @name = repository, name
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index 7e3f5abba62..ab7b811c5d8 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -31,7 +31,7 @@ module Grack
auth!
- lfs_response = Gitlab::Lfs::Router.new(project, @user, @request).try_call
+ lfs_response = Gitlab::Lfs::Router.new(project, @user, @ci, @request).try_call
return lfs_response unless lfs_response.nil?
if @user.nil? && !@ci
diff --git a/lib/gitlab/gitignore.rb b/lib/gitlab/gitignore.rb
deleted file mode 100644
index f46b43b61a4..00000000000
--- a/lib/gitlab/gitignore.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-module Gitlab
- class Gitignore
- FILTER_REGEX = /\.gitignore\z/.freeze
-
- def initialize(path)
- @path = path
- end
-
- def name
- File.basename(@path, '.gitignore')
- end
-
- def content
- File.read(@path)
- end
-
- class << self
- def all
- languages_frameworks + global
- end
-
- def find(key)
- file_name = "#{key}.gitignore"
-
- directory = select_directory(file_name)
- directory ? new(File.join(directory, file_name)) : nil
- end
-
- def global
- files_for_folder(global_dir).map { |file| new(File.join(global_dir, file)) }
- end
-
- def languages_frameworks
- files_for_folder(gitignore_dir).map { |file| new(File.join(gitignore_dir, file)) }
- end
-
- private
-
- def select_directory(file_name)
- [gitignore_dir, global_dir].find { |dir| File.exist?(File.join(dir, file_name)) }
- end
-
- def global_dir
- File.join(gitignore_dir, 'Global')
- end
-
- def gitignore_dir
- Rails.root.join('vendor/gitignore')
- end
-
- def files_for_folder(dir)
- Dir.glob("#{dir.to_s}/*.gitignore").map { |file| file.gsub(FILTER_REGEX, '') }
- end
- end
- end
-end
diff --git a/lib/gitlab/lfs/response.rb b/lib/gitlab/lfs/response.rb
index 9d9617761b3..e3ed2f6791d 100644
--- a/lib/gitlab/lfs/response.rb
+++ b/lib/gitlab/lfs/response.rb
@@ -2,10 +2,11 @@ module Gitlab
module Lfs
class Response
- def initialize(project, user, request)
+ def initialize(project, user, ci, request)
@origin_project = project
@project = storage_project(project)
@user = user
+ @ci = ci
@env = request.env
@request = request
end
@@ -189,7 +190,7 @@ module Gitlab
return render_not_enabled unless Gitlab.config.lfs.enabled
unless @project.public?
- return render_unauthorized unless @user
+ return render_unauthorized unless @user || @ci
return render_forbidden unless user_can_fetch?
end
@@ -210,7 +211,7 @@ module Gitlab
def user_can_fetch?
# Check user access against the project they used to initiate the pull
- @user.can?(:download_code, @origin_project)
+ @ci || @user.can?(:download_code, @origin_project)
end
def user_can_push?
diff --git a/lib/gitlab/lfs/router.rb b/lib/gitlab/lfs/router.rb
index 78d02891102..69bd5e62305 100644
--- a/lib/gitlab/lfs/router.rb
+++ b/lib/gitlab/lfs/router.rb
@@ -1,9 +1,12 @@
module Gitlab
module Lfs
class Router
- def initialize(project, user, request)
+ attr_reader :project, :user, :ci, :request
+
+ def initialize(project, user, ci, request)
@project = project
@user = user
+ @ci = ci
@env = request.env
@request = request
end
@@ -80,7 +83,7 @@ module Gitlab
def lfs
return unless @project
- Gitlab::Lfs::Response.new(@project, @user, @request)
+ Gitlab::Lfs::Response.new(@project, @user, @ci, @request)
end
def sanitize_tmp_filename(name)
diff --git a/lib/gitlab/template/base_template.rb b/lib/gitlab/template/base_template.rb
new file mode 100644
index 00000000000..760ff3e614a
--- /dev/null
+++ b/lib/gitlab/template/base_template.rb
@@ -0,0 +1,67 @@
+module Gitlab
+ module Template
+ class BaseTemplate
+ def initialize(path)
+ @path = path
+ end
+
+ def name
+ File.basename(@path, self.class.extension)
+ end
+
+ def content
+ File.read(@path)
+ end
+
+ class << self
+ def all
+ self.categories.keys.flat_map { |cat| by_category(cat) }
+ end
+
+ def find(key)
+ file_name = "#{key}#{self.extension}"
+
+ directory = select_directory(file_name)
+ directory ? new(File.join(category_directory(directory), file_name)) : nil
+ end
+
+ def categories
+ raise NotImplementedError
+ end
+
+ def extension
+ raise NotImplementedError
+ end
+
+ def base_dir
+ raise NotImplementedError
+ end
+
+ def by_category(category)
+ templates_for_directory(category_directory(category))
+ end
+
+ def category_directory(category)
+ File.join(base_dir, categories[category])
+ end
+
+ private
+
+ def select_directory(file_name)
+ categories.keys.find do |category|
+ File.exist?(File.join(category_directory(category), file_name))
+ end
+ end
+
+ def templates_for_directory(dir)
+ dir << '/' unless dir.end_with?('/')
+ Dir.glob(File.join(dir, "*#{self.extension}")).select { |f| f =~ filter_regex }.map { |f| new(f) }
+ end
+
+ def filter_regex
+ @filter_reges ||= /#{Regexp.escape(extension)}\z/
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/template/gitignore.rb b/lib/gitlab/template/gitignore.rb
new file mode 100644
index 00000000000..964fbfd4de3
--- /dev/null
+++ b/lib/gitlab/template/gitignore.rb
@@ -0,0 +1,22 @@
+module Gitlab
+ module Template
+ class Gitignore < BaseTemplate
+ class << self
+ def extension
+ '.gitignore'
+ end
+
+ def categories
+ {
+ "Languages" => '',
+ "Global" => 'Global'
+ }
+ end
+
+ def base_dir
+ Rails.root.join('vendor/gitignore')
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/template/gitlab_ci_yml.rb b/lib/gitlab/template/gitlab_ci_yml.rb
new file mode 100644
index 00000000000..7f480fe33c0
--- /dev/null
+++ b/lib/gitlab/template/gitlab_ci_yml.rb
@@ -0,0 +1,27 @@
+module Gitlab
+ module Template
+ class GitlabCiYml < BaseTemplate
+ def content
+ explanation = "# This file is a template, and might need editing before it works on your project."
+ [explanation, super].join("\n")
+ end
+
+ class << self
+ def extension
+ '.gitlab-ci.yml'
+ end
+
+ def categories
+ {
+ "General" => '',
+ "Pages" => 'Pages'
+ }
+ end
+
+ def base_dir
+ Rails.root.join('vendor/gitlab-ci-yml')
+ end
+ end
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/update_gitignore.rake b/lib/tasks/gitlab/update_gitignore.rake
deleted file mode 100644
index 4fd48cccb1d..00000000000
--- a/lib/tasks/gitlab/update_gitignore.rake
+++ /dev/null
@@ -1,46 +0,0 @@
-namespace :gitlab do
- desc "GitLab | Update gitignore"
- task :update_gitignore do
- unless clone_gitignores
- puts "Cloning the gitignores failed".color(:red)
- return
- end
-
- remove_unneeded_files(gitignore_directory)
- remove_unneeded_files(global_directory)
-
- puts "Done".color(:green)
- end
-
- def clone_gitignores
- FileUtils.rm_rf(gitignore_directory) if Dir.exist?(gitignore_directory)
- FileUtils.cd vendor_directory
-
- system('git clone --depth=1 --branch=master https://github.com/github/gitignore.git')
- end
-
- # Retain only certain files:
- # - The LICENSE, because we have to
- # - The sub dir global
- # - The gitignores themself
- # - Dir.entires returns also the entries '.' and '..'
- def remove_unneeded_files(path)
- Dir.foreach(path) do |file|
- FileUtils.rm_rf(File.join(path, file)) unless file =~ /(\.{1,2}|LICENSE|Global|\.gitignore)\z/
- end
- end
-
- private
-
- def vendor_directory
- Rails.root.join('vendor')
- end
-
- def gitignore_directory
- File.join(vendor_directory, 'gitignore')
- end
-
- def global_directory
- File.join(gitignore_directory, 'Global')
- end
-end
diff --git a/lib/tasks/gitlab/update_templates.rake b/lib/tasks/gitlab/update_templates.rake
new file mode 100644
index 00000000000..4f76dad7286
--- /dev/null
+++ b/lib/tasks/gitlab/update_templates.rake
@@ -0,0 +1,54 @@
+namespace :gitlab do
+ desc "GitLab | Update templates"
+ task :update_templates do
+ TEMPLATE_DATA.each { |template| update(template) }
+ end
+
+ def update(template)
+ sub_dir = template.repo_url.match(/([a-z-]+)\.git\z/)[1]
+ dir = File.join(vendor_directory, sub_dir)
+
+ unless clone_repository(template.repo_url, dir)
+ puts "Cloning the #{sub_dir} templates failed".red
+ return
+ end
+
+ remove_unneeded_files(dir, template.cleanup_regex)
+ puts "Done".green
+ end
+
+ def clone_repository(url, directory)
+ FileUtils.rm_rf(directory) if Dir.exist?(directory)
+
+ system("git clone #{url} --depth=1 --branch=master #{directory}")
+ end
+
+ # Retain only certain files:
+ # - The LICENSE, because we have to
+ # - The sub dirs so we can organise the file by category
+ # - The templates themself
+ # - Dir.entries returns also the entries '.' and '..'
+ def remove_unneeded_files(directory, regex)
+ Dir.foreach(directory) do |file|
+ FileUtils.rm_rf(File.join(directory, file)) unless file =~ regex
+ end
+ end
+
+ private
+
+ Template = Struct.new(:repo_url, :cleanup_regex)
+ TEMPLATE_DATA = [
+ Template.new(
+ "https://github.com/github/gitignore.git",
+ /(\.{1,2}|LICENSE|Global|\.gitignore)\z/
+ ),
+ Template.new(
+ "https://gitlab.com/gitlab-org/gitlab-ci-yml.git",
+ /(\.{1,2}|LICENSE|Pages|\.gitlab-ci.yml)\z/
+ )
+ ]
+
+ def vendor_directory
+ Rails.root.join('vendor')
+ end
+end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index fba545560c7..146b2c2e131 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -237,4 +237,24 @@ describe ProjectsController do
expect(response.status).to eq(401)
end
end
+
+ describe "GET refs" do
+ it "should get a list of branches and tags" do
+ get :refs, namespace_id: public_project.namespace.path, id: public_project.path
+
+ parsed_body = JSON.parse(response.body)
+ expect(parsed_body["Branches"]).to include("master")
+ expect(parsed_body["Tags"]).to include("v1.0.0")
+ expect(parsed_body["Commits"]).to be_nil
+ end
+
+ it "should get a list of branches, tags and commits" do
+ get :refs, namespace_id: public_project.namespace.path, id: public_project.path, ref: "123456"
+
+ parsed_body = JSON.parse(response.body)
+ expect(parsed_body["Branches"]).to include("master")
+ expect(parsed_body["Tags"]).to include("v1.0.0")
+ expect(parsed_body["Commits"]).to include("123456")
+ end
+ end
end
diff --git a/spec/factories/ci/commits.rb b/spec/factories/ci/pipelines.rb
index a039bef6f3c..a039bef6f3c 100644
--- a/spec/factories/ci/commits.rb
+++ b/spec/factories/ci/pipelines.rb
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index 9499cd4e025..2d297776cb0 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -60,6 +60,40 @@ describe "Admin Runners" do
it { expect(page).to have_content(@project1.name_with_namespace) }
it { expect(page).not_to have_content(@project2.name_with_namespace) }
end
+
+ describe 'enable/create' do
+ before do
+ @project1.runners << runner
+ visit admin_runner_path(runner)
+ end
+
+ it 'enables specific runner for project' do
+ within '.unassigned-projects' do
+ click_on 'Enable'
+ end
+
+ assigned_project = page.find('.assigned-projects')
+
+ expect(assigned_project).to have_content(@project2.path)
+ end
+ end
+
+ describe 'disable/destroy' do
+ before do
+ @project1.runners << runner
+ visit admin_runner_path(runner)
+ end
+
+ it 'enables specific runner for project' do
+ within '.assigned-projects' do
+ click_on 'Disable'
+ end
+
+ new_runner_project = page.find('.unassigned-projects')
+
+ expect(new_runner_project).to have_content(@project1.path)
+ end
+ end
end
describe 'runners registration token' do
diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb
index 53b4f027117..203e55a36f2 100644
--- a/spec/features/container_registry_spec.rb
+++ b/spec/features/container_registry_spec.rb
@@ -26,7 +26,8 @@ describe "Container Registry" do
end
context 'when there are tags' do
- it { expect(page).to have_content(tag_name)}
+ it { expect(page).to have_content(tag_name) }
+ it { expect(page).to have_content('d7a513a66') }
end
end
diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb
index 40fea5211e9..7fb28f4174b 100644
--- a/spec/features/environments_spec.rb
+++ b/spec/features/environments_spec.rb
@@ -20,7 +20,7 @@ feature 'Environments', feature: true do
context 'without environments' do
scenario 'does show no environments' do
- expect(page).to have_content('No environments to show')
+ expect(page).to have_content('You don\'t have any environments right now.')
end
end
@@ -61,7 +61,7 @@ feature 'Environments', feature: true do
context 'without deployments' do
scenario 'does show no deployments' do
- expect(page).to have_content('No deployments for')
+ expect(page).to have_content('You don\'t have any deployments right now.')
end
end
@@ -108,7 +108,7 @@ feature 'Environments', feature: true do
end
scenario 'does create a new pipeline' do
- expect(page).to have_content('production')
+ expect(page).to have_content('Production')
end
end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index c3cb3379440..5065dfb849c 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -22,7 +22,7 @@ describe 'Issues', feature: true do
before do
visit edit_namespace_project_issue_path(project.namespace, project, issue)
- click_button "Go full screen"
+ find('.js-zen-enter').click
end
it 'should open new issue popup' do
diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb
index 51be81d634c..01e90618a98 100644
--- a/spec/features/projects/badges/list_spec.rb
+++ b/spec/features/projects/badges/list_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
feature 'list of badges' do
- include Select2Helper
-
background do
user = create(:user)
project = create(:project)
@@ -24,7 +22,11 @@ feature 'list of badges' do
end
scenario 'user changes current ref on badges list page', js: true do
- select2('improve/awesome', from: '#ref')
+ first('.js-project-refs-dropdown').click
+
+ page.within '.project-refs-form' do
+ click_link 'improve/awesome'
+ end
expect(page).to have_content 'badges/improve/awesome/build.svg'
end
diff --git a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
new file mode 100644
index 00000000000..d516e8ce55a
--- /dev/null
+++ b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+feature 'User wants to add a .gitlab-ci.yml file', feature: true do
+ include WaitForAjax
+
+ before do
+ user = create(:user)
+ project = create(:project)
+ project.team << [user, :master]
+ login_as user
+ visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: '.gitlab-ci.yml')
+ end
+
+ scenario 'user can see .gitlab-ci.yml dropdown' do
+ expect(page).to have_css('.gitlab-ci-yml-selector')
+ end
+
+ scenario 'user can pick a template from the dropdown', js: true do
+ find('.js-gitlab-ci-yml-selector').click
+ wait_for_ajax
+ within '.gitlab-ci-yml-selector' do
+ find('.dropdown-input-field').set('jekyll')
+ find('.dropdown-content li', text: 'jekyll').click
+ end
+ wait_for_ajax
+
+ expect(page).to have_content('This file is a template, and might need editing before it works on your project')
+ expect(page).to have_content('jekyll build -d test')
+ end
+end
diff --git a/spec/fixtures/container_registry/tag_manifest.json b/spec/fixtures/container_registry/tag_manifest.json
index 1b6008e2872..8d1b874c29b 100644
--- a/spec/fixtures/container_registry/tag_manifest.json
+++ b/spec/fixtures/container_registry/tag_manifest.json
@@ -1 +1,16 @@
-{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/octet-stream","size":1145,"digest":"sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac"},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","size":2319870,"digest":"sha256:420890c9e918b6668faaedd9000e220190f2493b0693ee563ebd7b4cc754a57d"}]}
+{
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
+ "config": {
+ "mediaType": "application/octet-stream",
+ "size": 1145,
+ "digest": "sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac"
+ },
+ "layers": [
+ {
+ "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
+ "size": 2319870,
+ "digest": "sha256:420890c9e918b6668faaedd9000e220190f2493b0693ee563ebd7b4cc754a57d"
+ }
+ ]
+}
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index f6c1005d265..bb28866f010 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -174,51 +174,6 @@ describe ApplicationHelper do
end
end
- describe 'grouped_options_refs' do
- let(:options) { helper.grouped_options_refs }
- let(:project) { create(:project) }
-
- before do
- assign(:project, project)
-
- # Override Rails' grouped_options_for_select helper to just return the
- # first argument (`options`), since it's easier to work with than the
- # generated HTML.
- allow(helper).to receive(:grouped_options_for_select).
- and_wrap_original { |_, *args| args.first }
- end
-
- it 'includes a list of branch names' do
- expect(options[0][0]).to eq('Branches')
- expect(options[0][1]).to include('master', 'feature')
- end
-
- it 'includes a list of tag names' do
- expect(options[1][0]).to eq('Tags')
- expect(options[1][1]).to include('v1.0.0', 'v1.1.0')
- end
-
- it 'includes a specific commit ref if defined' do
- # Must be an instance variable
- ref = '2ed06dc41dbb5936af845b87d79e05bbf24c73b8'
- assign(:ref, ref)
-
- expect(options[2][0]).to eq('Commit')
- expect(options[2][1]).to eq([ref])
- end
-
- it 'sorts tags in a natural order' do
- # Stub repository.tag_names to make sure we get some valid testing data
- expect(project.repository).to receive(:tag_names).
- and_return(['v1.0.9', 'v1.0.10', 'v2.0', 'v3.1.4.2', 'v2.0rc1¿',
- 'v1.0.9a', 'v2.0-rc1', 'v2.0rc2'])
-
- expect(options[1][1]).
- to eq(['v3.1.4.2', 'v2.0', 'v2.0rc2', 'v2.0rc1¿', 'v2.0-rc1', 'v1.0.10',
- 'v1.0.9', 'v1.0.9a'])
- end
- end
-
describe 'simple_sanitize' do
let(:a_tag) { '<a href="#">Foo</a>' }
diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee
index ea27f36e9b5..71f0c1076c5 100644
--- a/spec/javascripts/issue_spec.js.coffee
+++ b/spec/javascripts/issue_spec.js.coffee
@@ -1,3 +1,4 @@
+#= require lib/text_utility
#= require issue
describe 'Issue', ->
@@ -38,7 +39,7 @@ describe 'reopen/close issue', ->
expect(typeof $btnClose.prop('disabled')).toBe('undefined')
$btnClose.trigger('click')
-
+
expect($btnReopen).toBeVisible()
expect($btnClose).toBeHidden()
expect($('div.status-box-closed')).toBeVisible()
@@ -50,7 +51,7 @@ describe 'reopen/close issue', ->
expect(req.type).toBe('PUT')
expect(req.url).toBe('http://goesnowhere.nothing/whereami')
req.success saved: false
-
+
$btnClose = $('a.btn-close')
$btnReopen = $('a.btn-reopen')
$btnClose.attr('href','http://goesnowhere.nothing/whereami')
@@ -59,7 +60,7 @@ describe 'reopen/close issue', ->
expect(typeof $btnClose.prop('disabled')).toBe('undefined')
$btnClose.trigger('click')
-
+
expect($btnReopen).toBeHidden()
expect($btnClose).toBeVisible()
expect($('div.status-box-closed')).toBeHidden()
@@ -73,7 +74,7 @@ describe 'reopen/close issue', ->
expect(req.type).toBe('PUT')
expect(req.url).toBe('http://goesnowhere.nothing/whereami')
req.error()
-
+
$btnClose = $('a.btn-close')
$btnReopen = $('a.btn-reopen')
$btnClose.attr('href','http://goesnowhere.nothing/whereami')
@@ -82,7 +83,7 @@ describe 'reopen/close issue', ->
expect(typeof $btnClose.prop('disabled')).toBe('undefined')
$btnClose.trigger('click')
-
+
expect($btnReopen).toBeHidden()
expect($btnClose).toBeVisible()
expect($('div.status-box-closed')).toBeHidden()
@@ -105,4 +106,4 @@ describe 'reopen/close issue', ->
expect($btnReopen).toBeHidden()
expect($btnClose).toBeVisible()
expect($('div.status-box-open')).toBeVisible()
- expect($('div.status-box-closed')).toBeHidden() \ No newline at end of file
+ expect($('div.status-box-closed')).toBeHidden()
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index 0e6685f0ffb..b9e4a4eaf0e 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -132,11 +132,8 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
path = 'files/images/한글.png'
escaped = Addressable::URI.escape(path)
- # Stub these methods so the file doesn't actually need to be in the repo
- allow_any_instance_of(described_class).
- to receive(:file_exists?).and_return(true)
- allow_any_instance_of(described_class).
- to receive(:image?).with(path).and_return(true)
+ # Stub this method so the file doesn't actually need to be in the repo
+ allow_any_instance_of(described_class).to receive(:uri_type).and_return(:raw)
doc = filter(image(escaped))
expect(doc.at_css('img')['src']).to match '/raw/'
diff --git a/spec/lib/gitlab/lfs/lfs_router_spec.rb b/spec/lib/gitlab/lfs/lfs_router_spec.rb
index 88814bc474d..659facd6c19 100644
--- a/spec/lib/gitlab/lfs/lfs_router_spec.rb
+++ b/spec/lib/gitlab/lfs/lfs_router_spec.rb
@@ -17,12 +17,15 @@ describe Gitlab::Lfs::Router, lib: true do
}
end
- let(:lfs_router_auth) { new_lfs_router(project, user) }
- let(:lfs_router_noauth) { new_lfs_router(project, nil) }
- let(:lfs_router_public_auth) { new_lfs_router(public_project, user) }
- let(:lfs_router_public_noauth) { new_lfs_router(public_project, nil) }
- let(:lfs_router_forked_noauth) { new_lfs_router(forked_project, nil) }
- let(:lfs_router_forked_auth) { new_lfs_router(forked_project, user_two) }
+ let(:lfs_router_auth) { new_lfs_router(project, user: user) }
+ let(:lfs_router_ci_auth) { new_lfs_router(project, ci: true) }
+ let(:lfs_router_noauth) { new_lfs_router(project) }
+ let(:lfs_router_public_auth) { new_lfs_router(public_project, user: user) }
+ let(:lfs_router_public_ci_auth) { new_lfs_router(public_project, ci: true) }
+ let(:lfs_router_public_noauth) { new_lfs_router(public_project) }
+ let(:lfs_router_forked_noauth) { new_lfs_router(forked_project) }
+ let(:lfs_router_forked_auth) { new_lfs_router(forked_project, user: user_two) }
+ let(:lfs_router_forked_ci_auth) { new_lfs_router(forked_project, ci: true) }
let(:sample_oid) { "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" }
let(:sample_size) { 499013 }
@@ -80,6 +83,7 @@ describe Gitlab::Lfs::Router, lib: true do
context 'with required headers' do
before do
+ project.lfs_objects << lfs_object
env['HTTP_X_SENDFILE_TYPE'] = "X-Sendfile"
end
@@ -91,7 +95,6 @@ describe Gitlab::Lfs::Router, lib: true do
context 'when user has project access' do
before do
- project.lfs_objects << lfs_object
project.team << [user, :master]
end
@@ -104,6 +107,17 @@ describe Gitlab::Lfs::Router, lib: true do
expect(lfs_router_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path)
end
end
+
+ context 'when CI is authorized' do
+ it "responds with status 200" do
+ expect(lfs_router_ci_auth.try_call.first).to eq(200)
+ end
+
+ it "responds with the file location" do
+ expect(lfs_router_ci_auth.try_call[1]['Content-Type']).to eq("application/octet-stream")
+ expect(lfs_router_ci_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path)
+ end
+ end
end
context 'without required headers' do
@@ -134,143 +148,145 @@ describe Gitlab::Lfs::Router, lib: true do
end
describe 'download' do
- describe 'when user is authenticated' do
- before do
- body = { 'operation' => 'download',
- 'objects' => [
- { 'oid' => sample_oid,
- 'size' => sample_size
- }]
- }.to_json
- env['rack.input'] = StringIO.new(body)
- end
+ before do
+ body = { 'operation' => 'download',
+ 'objects' => [
+ { 'oid' => sample_oid,
+ 'size' => sample_size
+ }]
+ }.to_json
+ env['rack.input'] = StringIO.new(body)
+ end
- describe 'when user has download access' do
+ shared_examples 'an authorized requests' do
+ context 'when downloading an lfs object that is assigned to our project' do
before do
- @auth = authorize(user)
- env["HTTP_AUTHORIZATION"] = @auth
- project.team << [user, :reporter]
+ project.lfs_objects << lfs_object
end
- context 'when downloading an lfs object that is assigned to our project' do
- before do
- project.lfs_objects << lfs_object
- end
-
- it 'responds with status 200 and href to download' do
- response = lfs_router_auth.try_call
- expect(response.first).to eq(200)
- response_body = ActiveSupport::JSON.decode(response.last.first)
+ it 'responds with status 200 and href to download' do
+ response = router.try_call
+ expect(response.first).to eq(200)
+ response_body = ActiveSupport::JSON.decode(response.last.first)
- expect(response_body).to eq('objects' => [
- { 'oid' => sample_oid,
- 'size' => sample_size,
- 'actions' => {
- 'download' => {
- 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
- 'header' => { 'Authorization' => @auth }
- }
+ expect(response_body).to eq('objects' => [
+ { 'oid' => sample_oid,
+ 'size' => sample_size,
+ 'actions' => {
+ 'download' => {
+ 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
+ 'header' => { 'Authorization' => auth }
}
- }])
- end
+ }
+ }])
end
+ end
- context 'when downloading an lfs object that is assigned to other project' do
- before do
- public_project.lfs_objects << lfs_object
- end
+ context 'when downloading an lfs object that is assigned to other project' do
+ before do
+ public_project.lfs_objects << lfs_object
+ end
- it 'responds with status 200 and error message' do
- response = lfs_router_auth.try_call
- expect(response.first).to eq(200)
- response_body = ActiveSupport::JSON.decode(response.last.first)
+ it 'responds with status 200 and error message' do
+ response = router.try_call
+ expect(response.first).to eq(200)
+ response_body = ActiveSupport::JSON.decode(response.last.first)
- expect(response_body).to eq('objects' => [
- { 'oid' => sample_oid,
- 'size' => sample_size,
- 'error' => {
- 'code' => 404,
- 'message' => "Object does not exist on the server or you don't have permissions to access it",
- }
- }])
- end
+ expect(response_body).to eq('objects' => [
+ { 'oid' => sample_oid,
+ 'size' => sample_size,
+ 'error' => {
+ 'code' => 404,
+ 'message' => "Object does not exist on the server or you don't have permissions to access it",
+ }
+ }])
end
+ end
- context 'when downloading a lfs object that does not exist' do
- before do
- body = { 'operation' => 'download',
- 'objects' => [
- { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078
- }]
- }.to_json
- env['rack.input'] = StringIO.new(body)
- end
+ context 'when downloading a lfs object that does not exist' do
+ before do
+ body = { 'operation' => 'download',
+ 'objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078
+ }]
+ }.to_json
+ env['rack.input'] = StringIO.new(body)
+ end
- it "responds with status 200 and error message" do
- response = lfs_router_auth.try_call
- expect(response.first).to eq(200)
- response_body = ActiveSupport::JSON.decode(response.last.first)
+ it "responds with status 200 and error message" do
+ response = router.try_call
+ expect(response.first).to eq(200)
+ response_body = ActiveSupport::JSON.decode(response.last.first)
- expect(response_body).to eq('objects' => [
- { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078,
- 'error' => {
- 'code' => 404,
- 'message' => "Object does not exist on the server or you don't have permissions to access it",
- }
- }])
- end
+ expect(response_body).to eq('objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078,
+ 'error' => {
+ 'code' => 404,
+ 'message' => "Object does not exist on the server or you don't have permissions to access it",
+ }
+ }])
end
+ end
- context 'when downloading one new and one existing lfs object' do
- before do
- body = { 'operation' => 'download',
- 'objects' => [
- { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078
- },
- { 'oid' => sample_oid,
- 'size' => sample_size
- }
- ]
- }.to_json
- env['rack.input'] = StringIO.new(body)
- project.lfs_objects << lfs_object
- end
+ context 'when downloading one new and one existing lfs object' do
+ before do
+ body = { 'operation' => 'download',
+ 'objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078
+ },
+ { 'oid' => sample_oid,
+ 'size' => sample_size
+ }
+ ]
+ }.to_json
+ env['rack.input'] = StringIO.new(body)
+ project.lfs_objects << lfs_object
+ end
- it "responds with status 200 with upload hypermedia link for the new object" do
- response = lfs_router_auth.try_call
- expect(response.first).to eq(200)
- response_body = ActiveSupport::JSON.decode(response.last.first)
+ it "responds with status 200 with upload hypermedia link for the new object" do
+ response = router.try_call
+ expect(response.first).to eq(200)
+ response_body = ActiveSupport::JSON.decode(response.last.first)
- expect(response_body).to eq('objects' => [
- { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078,
- 'error' => {
- 'code' => 404,
- 'message' => "Object does not exist on the server or you don't have permissions to access it",
- }
- },
- { 'oid' => sample_oid,
- 'size' => sample_size,
- 'actions' => {
- 'download' => {
- 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
- 'header' => { 'Authorization' => @auth }
- }
+ expect(response_body).to eq('objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078,
+ 'error' => {
+ 'code' => 404,
+ 'message' => "Object does not exist on the server or you don't have permissions to access it",
+ }
+ },
+ { 'oid' => sample_oid,
+ 'size' => sample_size,
+ 'actions' => {
+ 'download' => {
+ 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
+ 'header' => { 'Authorization' => auth }
}
- }])
- end
+ }
+ }])
end
end
+ end
+
+ context 'when user is authenticated' do
+ let(:auth) { authorize(user) }
+
+ before do
+ env["HTTP_AUTHORIZATION"] = auth
+ project.team << [user, role]
+ end
+
+ it_behaves_like 'an authorized requests' do
+ let(:role) { :reporter }
+ let(:router) { lfs_router_auth }
+ end
context 'when user does is not member of the project' do
- before do
- @auth = authorize(user)
- env["HTTP_AUTHORIZATION"] = @auth
- project.team << [user, :guest]
- end
+ let(:role) { :guest }
it 'responds with 403' do
expect(lfs_router_auth.try_call.first).to eq(403)
@@ -278,11 +294,7 @@ describe Gitlab::Lfs::Router, lib: true do
end
context 'when user does not have download access' do
- before do
- @auth = authorize(user)
- env["HTTP_AUTHORIZATION"] = @auth
- project.team << [user, :guest]
- end
+ let(:role) { :guest }
it 'responds with 403' do
expect(lfs_router_auth.try_call.first).to eq(403)
@@ -290,18 +302,19 @@ describe Gitlab::Lfs::Router, lib: true do
end
end
- context 'when user is not authenticated' do
+ context 'when CI is authorized' do
+ let(:auth) { 'gitlab-ci-token:password' }
+
before do
- body = { 'operation' => 'download',
- 'objects' => [
- { 'oid' => sample_oid,
- 'size' => sample_size
- }],
+ env["HTTP_AUTHORIZATION"] = auth
+ end
- }.to_json
- env['rack.input'] = StringIO.new(body)
+ it_behaves_like 'an authorized requests' do
+ let(:router) { lfs_router_ci_auth }
end
+ end
+ context 'when user is not authenticated' do
describe 'is accessing public project' do
before do
public_project.lfs_objects << lfs_object
@@ -338,17 +351,17 @@ describe Gitlab::Lfs::Router, lib: true do
end
describe 'upload' do
- describe 'when user is authenticated' do
- before do
- body = { 'operation' => 'upload',
- 'objects' => [
- { 'oid' => sample_oid,
- 'size' => sample_size
- }]
- }.to_json
- env['rack.input'] = StringIO.new(body)
- end
+ before do
+ body = { 'operation' => 'upload',
+ 'objects' => [
+ { 'oid' => sample_oid,
+ 'size' => sample_size
+ }]
+ }.to_json
+ env['rack.input'] = StringIO.new(body)
+ end
+ describe 'when request is authenticated' do
describe 'when user has project push access' do
before do
@auth = authorize(user)
@@ -440,15 +453,15 @@ describe Gitlab::Lfs::Router, lib: true do
expect(lfs_router_auth.try_call.first).to eq(403)
end
end
- end
- context 'when user is not authenticated' do
- before do
- env['rack.input'] = StringIO.new(
- { 'objects' => [], 'operation' => 'upload' }.to_json
- )
+ context 'when CI is authorized' do
+ it 'responds with 401' do
+ expect(lfs_router_ci_auth.try_call.first).to eq(401)
+ end
end
+ end
+ context 'when user is not authenticated' do
context 'when user has push access' do
before do
project.team << [user, :master]
@@ -465,6 +478,18 @@ describe Gitlab::Lfs::Router, lib: true do
end
end
end
+
+ context 'when CI is authorized' do
+ let(:auth) { 'gitlab-ci-token:password' }
+
+ before do
+ env["HTTP_AUTHORIZATION"] = auth
+ end
+
+ it "responds with status 403" do
+ expect(lfs_router_public_ci_auth.try_call.first).to eq(401)
+ end
+ end
end
describe 'unsupported' do
@@ -490,13 +515,68 @@ describe Gitlab::Lfs::Router, lib: true do
env['REQUEST_METHOD'] = 'PUT'
end
- describe 'to one project' do
- describe 'when user has push access to the project' do
+ shared_examples 'unauthorized' do
+ context 'and request is sent by gitlab-workhorse to authorize the request' do
before do
- project.team << [user, :master]
+ header_for_upload_authorize(router.project)
+ end
+
+ it 'responds with status 401' do
+ expect(router.try_call.first).to eq(401)
+ end
+ end
+
+ context 'and request is sent by gitlab-workhorse to finalize the upload' do
+ before do
+ headers_for_upload_finalize(router.project)
+ end
+
+ it 'responds with status 401' do
+ expect(router.try_call.first).to eq(401)
+ end
+ end
+
+ context 'and request is sent with a malformed headers' do
+ before do
+ env["PATH_INFO"] = "#{router.project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}"
+ env["HTTP_X_GITLAB_LFS_TMP"] = "cat /etc/passwd"
+ end
+
+ it 'does not recognize it as a valid lfs command' do
+ expect(router.try_call).to eq(nil)
+ end
+ end
+ end
+
+ shared_examples 'forbidden' do
+ context 'and request is sent by gitlab-workhorse to authorize the request' do
+ before do
+ header_for_upload_authorize(router.project)
+ end
+
+ it 'responds with 403' do
+ expect(router.try_call.first).to eq(403)
+ end
+ end
+
+ context 'and request is sent by gitlab-workhorse to finalize the upload' do
+ before do
+ headers_for_upload_finalize(router.project)
+ end
+
+ it 'responds with 403' do
+ expect(router.try_call.first).to eq(403)
end
+ end
+ end
+
+ describe 'to one project' do
+ describe 'when user is authenticated' do
+ describe 'when user has push access to the project' do
+ before do
+ project.team << [user, :developer]
+ end
- describe 'when user is authenticated' do
context 'and request is sent by gitlab-workhorse to authorize the request' do
before do
header_for_upload_authorize(project)
@@ -524,100 +604,35 @@ describe Gitlab::Lfs::Router, lib: true do
end
end
- describe 'when user is unauthenticated' do
- let(:lfs_router_noauth) { new_lfs_router(project, nil) }
+ describe 'and user does not have push access' do
+ let(:router) { lfs_router_auth }
- context 'and request is sent by gitlab-workhorse to authorize the request' do
- before do
- header_for_upload_authorize(project)
- end
-
- it 'responds with status 401' do
- expect(lfs_router_noauth.try_call.first).to eq(401)
- end
- end
-
- context 'and request is sent by gitlab-workhorse to finalize the upload' do
- before do
- headers_for_upload_finalize(project)
- end
-
- it 'responds with status 401' do
- expect(lfs_router_noauth.try_call.first).to eq(401)
- end
- end
-
- context 'and request is sent with a malformed headers' do
- before do
- env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}"
- env["HTTP_X_GITLAB_LFS_TMP"] = "cat /etc/passwd"
- end
-
- it 'does not recognize it as a valid lfs command' do
- expect(lfs_router_noauth.try_call).to eq(nil)
- end
- end
+ it_behaves_like 'forbidden'
end
end
- describe 'and user does not have push access' do
- describe 'when user is authenticated' do
- context 'and request is sent by gitlab-workhorse to authorize the request' do
- before do
- header_for_upload_authorize(project)
- end
-
- it 'responds with 403' do
- expect(lfs_router_auth.try_call.first).to eq(403)
- end
- end
-
- context 'and request is sent by gitlab-workhorse to finalize the upload' do
- before do
- headers_for_upload_finalize(project)
- end
-
- it 'responds with 403' do
- expect(lfs_router_auth.try_call.first).to eq(403)
- end
- end
- end
+ context 'when CI is authenticated' do
+ let(:router) { lfs_router_ci_auth }
- describe 'when user is unauthenticated' do
- let(:lfs_router_noauth) { new_lfs_router(project, nil) }
-
- context 'and request is sent by gitlab-workhorse to authorize the request' do
- before do
- header_for_upload_authorize(project)
- end
+ it_behaves_like 'unauthorized'
+ end
- it 'responds with 401' do
- expect(lfs_router_noauth.try_call.first).to eq(401)
- end
- end
+ context 'for unauthenticated' do
+ let(:router) { new_lfs_router(project) }
- context 'and request is sent by gitlab-workhorse to finalize the upload' do
- before do
- headers_for_upload_finalize(project)
- end
-
- it 'responds with 401' do
- expect(lfs_router_noauth.try_call.first).to eq(401)
- end
- end
- end
+ it_behaves_like 'unauthorized'
end
end
- describe "to a forked project" do
+ describe 'to a forked project' do
let(:forked_project) { fork_project(public_project, user) }
- describe 'when user has push access to the project' do
- before do
- forked_project.team << [user_two, :master]
- end
+ describe 'when user is authenticated' do
+ describe 'when user has push access to the project' do
+ before do
+ forked_project.team << [user_two, :developer]
+ end
- describe 'when user is authenticated' do
context 'and request is sent by gitlab-workhorse to authorize the request' do
before do
header_for_upload_authorize(forked_project)
@@ -645,78 +660,28 @@ describe Gitlab::Lfs::Router, lib: true do
end
end
- describe 'when user is unauthenticated' do
- context 'and request is sent by gitlab-workhorse to authorize the request' do
- before do
- header_for_upload_authorize(forked_project)
- end
-
- it 'responds with status 401' do
- expect(lfs_router_forked_noauth.try_call.first).to eq(401)
- end
- end
-
- context 'and request is sent by gitlab-workhorse to finalize the upload' do
- before do
- headers_for_upload_finalize(forked_project)
- end
+ describe 'and user does not have push access' do
+ let(:router) { lfs_router_forked_auth }
- it 'responds with status 401' do
- expect(lfs_router_forked_noauth.try_call.first).to eq(401)
- end
- end
+ it_behaves_like 'forbidden'
end
end
- describe 'and user does not have push access' do
- describe 'when user is authenticated' do
- context 'and request is sent by gitlab-workhorse to authorize the request' do
- before do
- header_for_upload_authorize(forked_project)
- end
+ context 'when CI is authenticated' do
+ let(:router) { lfs_router_forked_ci_auth }
- it 'responds with 403' do
- expect(lfs_router_forked_auth.try_call.first).to eq(403)
- end
- end
-
- context 'and request is sent by gitlab-workhorse to finalize the upload' do
- before do
- headers_for_upload_finalize(forked_project)
- end
-
- it 'responds with 403' do
- expect(lfs_router_forked_auth.try_call.first).to eq(403)
- end
- end
- end
-
- describe 'when user is unauthenticated' do
- context 'and request is sent by gitlab-workhorse to authorize the request' do
- before do
- header_for_upload_authorize(forked_project)
- end
-
- it 'responds with 401' do
- expect(lfs_router_forked_noauth.try_call.first).to eq(401)
- end
- end
+ it_behaves_like 'unauthorized'
+ end
- context 'and request is sent by gitlab-workhorse to finalize the upload' do
- before do
- headers_for_upload_finalize(forked_project)
- end
+ context 'for unauthenticated' do
+ let(:router) { lfs_router_forked_noauth }
- it 'responds with 401' do
- expect(lfs_router_forked_noauth.try_call.first).to eq(401)
- end
- end
- end
+ it_behaves_like 'unauthorized'
end
describe 'and second project not related to fork or a source project' do
let(:second_project) { create(:project) }
- let(:lfs_router_second_project) { new_lfs_router(second_project, user) }
+ let(:lfs_router_second_project) { new_lfs_router(second_project, user: user) }
before do
public_project.lfs_objects << lfs_object
@@ -745,8 +710,8 @@ describe Gitlab::Lfs::Router, lib: true do
ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
end
- def new_lfs_router(project, user)
- Gitlab::Lfs::Router.new(project, user, request)
+ def new_lfs_router(project, user: nil, ci: false)
+ Gitlab::Lfs::Router.new(project, user, ci, request)
end
def header_for_upload_authorize(project)
diff --git a/spec/lib/gitlab/gitignore_spec.rb b/spec/lib/gitlab/template/gitignore_spec.rb
index 72baa516cc4..bc0ec9325cc 100644
--- a/spec/lib/gitlab/gitignore_spec.rb
+++ b/spec/lib/gitlab/template/gitignore_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
-describe Gitlab::Gitignore do
- subject { Gitlab::Gitignore }
+describe Gitlab::Template::Gitignore do
+ subject { described_class }
describe '.all' do
it 'strips the gitignore suffix' do
@@ -24,7 +24,7 @@ describe Gitlab::Gitignore do
it 'returns the Gitignore object of a valid file' do
ruby = subject.find('Ruby')
- expect(ruby).to be_a Gitlab::Gitignore
+ expect(ruby).to be_a Gitlab::Template::Gitignore
expect(ruby.name).to eq('Ruby')
end
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 1e6eb20ab39..ae55a01ebea 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -401,23 +401,56 @@ describe Notify do
end
describe 'project access requested' do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
- let(:project_member) do
- project.request_access(user)
- project.members.request.find_by(user_id: user.id)
+ context 'for a project in a user namespace' do
+ let(:project) { create(:project).tap { |p| p.team << [p.owner, :master, p.owner] } }
+ let(:user) { create(:user) }
+ let(:project_member) do
+ project.request_access(user)
+ project.members.request.find_by(user_id: user.id)
+ end
+ subject { Notify.member_access_requested_email('project', project_member.id) }
+
+ it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like "a user cannot unsubscribe through footer link"
+
+ it 'contains all the useful information' do
+ to_emails = subject.header[:to].addrs
+ expect(to_emails.size).to eq(1)
+ expect(to_emails[0].address).to eq(project.members.owners_and_masters.first.user.notification_email)
+
+ is_expected.to have_subject "Request to join the #{project.name_with_namespace} project"
+ is_expected.to have_body_text /#{project.name_with_namespace}/
+ is_expected.to have_body_text /#{namespace_project_project_members_url(project.namespace, project)}/
+ is_expected.to have_body_text /#{project_member.human_access}/
+ end
end
- subject { Notify.member_access_requested_email('project', project_member.id) }
- it_behaves_like 'an email sent from GitLab'
- it_behaves_like 'it should not have Gmail Actions links'
- it_behaves_like "a user cannot unsubscribe through footer link"
+ context 'for a project in a group' do
+ let(:group_owner) { create(:user) }
+ let(:group) { create(:group).tap { |g| g.add_owner(group_owner) } }
+ let(:project) { create(:project, namespace: group) }
+ let(:user) { create(:user) }
+ let(:project_member) do
+ project.request_access(user)
+ project.members.request.find_by(user_id: user.id)
+ end
+ subject { Notify.member_access_requested_email('project', project_member.id) }
- it 'contains all the useful information' do
- is_expected.to have_subject "Request to join the #{project.name_with_namespace} project"
- is_expected.to have_body_text /#{project.name_with_namespace}/
- is_expected.to have_body_text /#{namespace_project_project_members_url(project.namespace, project)}/
- is_expected.to have_body_text /#{project_member.human_access}/
+ it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like "a user cannot unsubscribe through footer link"
+
+ it 'contains all the useful information' do
+ to_emails = subject.header[:to].addrs
+ expect(to_emails.size).to eq(1)
+ expect(to_emails[0].address).to eq(group.members.owners_and_masters.first.user.notification_email)
+
+ is_expected.to have_subject "Request to join the #{project.name_with_namespace} project"
+ is_expected.to have_body_text /#{project.name_with_namespace}/
+ is_expected.to have_body_text /#{namespace_project_project_members_url(project.namespace, project)}/
+ is_expected.to have_body_text /#{project_member.human_access}/
+ end
end
end
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index 5d1fa8226e5..8154001cf46 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -2,7 +2,12 @@ require 'spec_helper'
describe Ci::Build, models: true do
let(:project) { create(:project) }
- let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ let(:pipeline) do
+ create(:ci_pipeline, project: project,
+ sha: project.commit.id)
+ end
+
let(:build) { create(:ci_build, pipeline: pipeline) }
it { is_expected.to validate_presence_of :ref }
@@ -36,32 +41,44 @@ describe Ci::Build, models: true do
subject { build.ignored? }
context 'if build is not allowed to fail' do
- before { build.allow_failure = false }
+ before do
+ build.allow_failure = false
+ end
context 'and build.status is success' do
- before { build.status = 'success' }
+ before do
+ build.status = 'success'
+ end
it { is_expected.to be_falsey }
end
context 'and build.status is failed' do
- before { build.status = 'failed' }
+ before do
+ build.status = 'failed'
+ end
it { is_expected.to be_falsey }
end
end
context 'if build is allowed to fail' do
- before { build.allow_failure = true }
+ before do
+ build.allow_failure = true
+ end
context 'and build.status is success' do
- before { build.status = 'success' }
+ before do
+ build.status = 'success'
+ end
it { is_expected.to be_falsey }
end
context 'and build.status is failed' do
- before { build.status = 'failed' }
+ before do
+ build.status = 'failed'
+ end
it { is_expected.to be_truthy }
end
@@ -75,7 +92,9 @@ describe Ci::Build, models: true do
context 'if build.trace contains text' do
let(:text) { 'example output' }
- before { build.trace = text }
+ before do
+ build.trace = text
+ end
it { is_expected.to include(text) }
it { expect(subject.length).to be >= text.length }
@@ -188,7 +207,9 @@ describe Ci::Build, models: true do
]
end
- before { build.update_attributes(stage: 'stage') }
+ before do
+ build.update_attributes(stage: 'stage')
+ end
it { is_expected.to eq(predefined_variables + yaml_variables) }
@@ -199,7 +220,9 @@ describe Ci::Build, models: true do
]
end
- before { build.update_attributes(tag: true) }
+ before do
+ build.update_attributes(tag: true)
+ end
it { is_expected.to eq(tag_variable + predefined_variables + yaml_variables) }
end
@@ -257,57 +280,6 @@ describe Ci::Build, models: true do
end
end
- describe '#can_be_served?' do
- let(:runner) { create(:ci_runner) }
-
- before { build.project.runners << runner }
-
- context 'when runner does not have tags' do
- it 'can handle builds without tags' do
- expect(build.can_be_served?(runner)).to be_truthy
- end
-
- it 'cannot handle build with tags' do
- build.tag_list = ['aa']
- expect(build.can_be_served?(runner)).to be_falsey
- end
- end
-
- context 'when runner has tags' do
- before { runner.tag_list = ['bb', 'cc'] }
-
- shared_examples 'tagged build picker' do
- it 'can handle build with matching tags' do
- build.tag_list = ['bb']
- expect(build.can_be_served?(runner)).to be_truthy
- end
-
- it 'cannot handle build without matching tags' do
- build.tag_list = ['aa']
- expect(build.can_be_served?(runner)).to be_falsey
- end
- end
-
- context 'when runner can pick untagged jobs' do
- it 'can handle builds without tags' do
- expect(build.can_be_served?(runner)).to be_truthy
- end
-
- it_behaves_like 'tagged build picker'
- end
-
- context 'when runner can not pick untagged jobs' do
- before { runner.run_untagged = false }
-
- it 'can not handle builds without tags' do
- expect(build.can_be_served?(runner)).to be_falsey
- end
-
- it_behaves_like 'tagged build picker'
- end
- end
- end
-
describe '#has_tags?' do
context 'when build has tags' do
subject { create(:ci_build, tag_list: ['tag']) }
@@ -348,7 +320,7 @@ describe Ci::Build, models: true do
end
it 'that cannot handle build' do
- expect_any_instance_of(Ci::Build).to receive(:can_be_served?).and_return(false)
+ expect_any_instance_of(Ci::Runner).to receive(:can_pick?).and_return(false)
is_expected.to be_falsey
end
@@ -360,7 +332,9 @@ describe Ci::Build, models: true do
%w(pending).each do |state|
context "if commit_status.status is #{state}" do
- before { build.status = state }
+ before do
+ build.status = state
+ end
it { is_expected.to be_truthy }
@@ -379,7 +353,9 @@ describe Ci::Build, models: true do
%w(success failed canceled running).each do |state|
context "if commit_status.status is #{state}" do
- before { build.status = state }
+ before do
+ build.status = state
+ end
it { is_expected.to be_falsey }
end
@@ -390,7 +366,10 @@ describe Ci::Build, models: true do
subject { build.artifacts? }
context 'artifacts archive does not exist' do
- before { build.update_attributes(artifacts_file: nil) }
+ before do
+ build.update_attributes(artifacts_file: nil)
+ end
+
it { is_expected.to be_falsy }
end
@@ -623,7 +602,9 @@ describe Ci::Build, models: true do
let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
describe '#erase' do
- before { build.erase(erased_by: user) }
+ before do
+ build.erase(erased_by: user)
+ end
context 'erased by user' do
let!(:user) { create(:user, username: 'eraser') }
@@ -660,7 +641,9 @@ describe Ci::Build, models: true do
end
context 'build has been erased' do
- before { build.erase }
+ before do
+ build.erase
+ end
it { is_expected.to be true }
end
@@ -668,7 +651,9 @@ describe Ci::Build, models: true do
context 'metadata and build trace are not available' do
let!(:build) { create(:ci_build, :success, :artifacts) }
- before { build.remove_artifacts_metadata! }
+ before do
+ build.remove_artifacts_metadata!
+ end
describe '#erase' do
it 'should not raise error' do
@@ -678,4 +663,10 @@ describe Ci::Build, models: true do
end
end
end
+
+ describe '#commit' do
+ it 'returns commit pipeline has been created for' do
+ expect(build.commit).to eq project.commit
+ end
+ end
end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 5d04d8ffcff..ef65eb99328 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -20,34 +20,36 @@ describe Ci::Runner, models: true do
end
describe '#display_name' do
- it 'should return the description if it has a value' do
+ it 'returns the description if it has a value' do
runner = FactoryGirl.build(:ci_runner, description: 'Linux/Ruby-1.9.3-p448')
expect(runner.display_name).to eq 'Linux/Ruby-1.9.3-p448'
end
- it 'should return the token if it does not have a description' do
+ it 'returns the token if it does not have a description' do
runner = FactoryGirl.create(:ci_runner)
expect(runner.display_name).to eq runner.description
end
- it 'should return the token if the description is an empty string' do
+ it 'returns the token if the description is an empty string' do
runner = FactoryGirl.build(:ci_runner, description: '', token: 'token')
expect(runner.display_name).to eq runner.token
end
end
- describe :assign_to do
+ describe '#assign_to' do
let!(:project) { FactoryGirl.create :empty_project }
let!(:shared_runner) { FactoryGirl.create(:ci_runner, :shared) }
- before { shared_runner.assign_to(project) }
+ before do
+ shared_runner.assign_to(project)
+ end
it { expect(shared_runner).to be_specific }
it { expect(shared_runner.projects).to eq([project]) }
it { expect(shared_runner.only_for?(project)).to be_truthy }
end
- describe :online do
+ describe '.online' do
subject { Ci::Runner.online }
before do
@@ -58,60 +60,269 @@ describe Ci::Runner, models: true do
it { is_expected.to eq([@runner2])}
end
- describe :online? do
+ describe '#online?' do
let(:runner) { FactoryGirl.create(:ci_runner, :shared) }
subject { runner.online? }
context 'never contacted' do
- before { runner.contacted_at = nil }
+ before do
+ runner.contacted_at = nil
+ end
it { is_expected.to be_falsey }
end
context 'contacted long time ago time' do
- before { runner.contacted_at = 1.year.ago }
+ before do
+ runner.contacted_at = 1.year.ago
+ end
it { is_expected.to be_falsey }
end
context 'contacted 1s ago' do
- before { runner.contacted_at = 1.second.ago }
+ before do
+ runner.contacted_at = 1.second.ago
+ end
it { is_expected.to be_truthy }
end
end
- describe :status do
+ describe '#can_pick?' do
+ let(:project) { create(:project) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+ let(:runner) { create(:ci_runner) }
+
+ before do
+ build.project.runners << runner
+ end
+
+ context 'when runner does not have tags' do
+ it 'can handle builds without tags' do
+ expect(runner.can_pick?(build)).to be_truthy
+ end
+
+ it 'cannot handle build with tags' do
+ build.tag_list = ['aa']
+
+ expect(runner.can_pick?(build)).to be_falsey
+ end
+ end
+
+ context 'when runner has tags' do
+ before do
+ runner.tag_list = ['bb', 'cc']
+ end
+
+ shared_examples 'tagged build picker' do
+ it 'can handle build with matching tags' do
+ build.tag_list = ['bb']
+
+ expect(runner.can_pick?(build)).to be_truthy
+ end
+
+ it 'cannot handle build without matching tags' do
+ build.tag_list = ['aa']
+
+ expect(runner.can_pick?(build)).to be_falsey
+ end
+ end
+
+ context 'when runner can pick untagged jobs' do
+ it 'can handle builds without tags' do
+ expect(runner.can_pick?(build)).to be_truthy
+ end
+
+ it_behaves_like 'tagged build picker'
+ end
+
+ context 'when runner cannot pick untagged jobs' do
+ before do
+ runner.run_untagged = false
+ end
+
+ it 'cannot handle builds without tags' do
+ expect(runner.can_pick?(build)).to be_falsey
+ end
+
+ it_behaves_like 'tagged build picker'
+ end
+ end
+
+ context 'when runner is locked' do
+ before do
+ runner.locked = true
+ end
+
+ shared_examples 'locked build picker' do
+ context 'when runner cannot pick untagged jobs' do
+ before do
+ runner.run_untagged = false
+ end
+
+ it 'cannot handle builds without tags' do
+ expect(runner.can_pick?(build)).to be_falsey
+ end
+ end
+
+ context 'when having runner tags' do
+ before do
+ runner.tag_list = ['bb', 'cc']
+ end
+
+ it 'cannot handle it for builds without matching tags' do
+ build.tag_list = ['aa']
+
+ expect(runner.can_pick?(build)).to be_falsey
+ end
+ end
+ end
+
+ context 'when serving the same project' do
+ it 'can handle it' do
+ expect(runner.can_pick?(build)).to be_truthy
+ end
+
+ it_behaves_like 'locked build picker'
+
+ context 'when having runner tags' do
+ before do
+ runner.tag_list = ['bb', 'cc']
+ build.tag_list = ['bb']
+ end
+
+ it 'can handle it for matching tags' do
+ expect(runner.can_pick?(build)).to be_truthy
+ end
+ end
+ end
+
+ context 'serving a different project' do
+ before do
+ runner.runner_projects.destroy_all
+ end
+
+ it 'cannot handle it' do
+ expect(runner.can_pick?(build)).to be_falsey
+ end
+
+ it_behaves_like 'locked build picker'
+
+ context 'when having runner tags' do
+ before do
+ runner.tag_list = ['bb', 'cc']
+ build.tag_list = ['bb']
+ end
+
+ it 'cannot handle it for matching tags' do
+ expect(runner.can_pick?(build)).to be_falsey
+ end
+ end
+ end
+ end
+ end
+
+ describe '#status' do
let(:runner) { FactoryGirl.create(:ci_runner, :shared, contacted_at: 1.second.ago) }
subject { runner.status }
context 'never connected' do
- before { runner.contacted_at = nil }
+ before do
+ runner.contacted_at = nil
+ end
it { is_expected.to eq(:not_connected) }
end
context 'contacted 1s ago' do
- before { runner.contacted_at = 1.second.ago }
+ before do
+ runner.contacted_at = 1.second.ago
+ end
it { is_expected.to eq(:online) }
end
context 'contacted long time ago' do
- before { runner.contacted_at = 1.year.ago }
+ before do
+ runner.contacted_at = 1.year.ago
+ end
it { is_expected.to eq(:offline) }
end
context 'inactive' do
- before { runner.active = false }
+ before do
+ runner.active = false
+ end
it { is_expected.to eq(:paused) }
end
end
+ describe '.assignable_for' do
+ let(:runner) { create(:ci_runner) }
+ let(:project) { create(:project) }
+ let(:another_project) { create(:project) }
+
+ before do
+ project.runners << runner
+ end
+
+ context 'with shared runners' do
+ before do
+ runner.update(is_shared: true)
+ end
+
+ context 'does not give owned runner' do
+ subject { Ci::Runner.assignable_for(project) }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'does not give shared runner' do
+ subject { Ci::Runner.assignable_for(another_project) }
+
+ it { is_expected.to be_empty }
+ end
+ end
+
+ context 'with unlocked runner' do
+ context 'does not give owned runner' do
+ subject { Ci::Runner.assignable_for(project) }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'does give a specific runner' do
+ subject { Ci::Runner.assignable_for(another_project) }
+
+ it { is_expected.to contain_exactly(runner) }
+ end
+ end
+
+ context 'with locked runner' do
+ before do
+ runner.update(locked: true)
+ end
+
+ context 'does not give owned runner' do
+ subject { Ci::Runner.assignable_for(project) }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'does not give a locked runner' do
+ subject { Ci::Runner.assignable_for(another_project) }
+
+ it { is_expected.to be_empty }
+ end
+ end
+ end
+
describe "belongs_to_one_project?" do
it "returns false if there are two projects runner assigned to" do
runner = FactoryGirl.create(:ci_runner)
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index beca8708c9d..ba02d5fe977 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -207,4 +207,16 @@ eos
expect(commit.participants).to include(note1.author, note2.author)
end
end
+
+ describe '#uri_type' do
+ it 'returns the URI type at the given path' do
+ expect(commit.uri_type('files/html')).to be(:tree)
+ expect(commit.uri_type('files/images/logo-black.png')).to be(:raw)
+ expect(commit.uri_type('files/js/application.js')).to be(:blob)
+ end
+
+ it "returns nil if the path doesn't exists" do
+ expect(commit.uri_type('this/path/doesnt/exist')).to be_nil
+ end
+ end
end
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 8fb605fff8a..96397d7c8a9 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -1,8 +1,13 @@
require 'spec_helper'
describe CommitStatus, models: true do
- let(:pipeline) { FactoryGirl.create :ci_pipeline }
- let(:commit_status) { FactoryGirl.create :commit_status, pipeline: pipeline }
+ let(:project) { create(:project) }
+
+ let(:pipeline) do
+ create(:ci_pipeline, project: project, sha: project.commit.id)
+ end
+
+ let(:commit_status) { create(:commit_status, pipeline: pipeline) }
it { is_expected.to belong_to(:pipeline) }
it { is_expected.to belong_to(:user) }
@@ -13,7 +18,7 @@ describe CommitStatus, models: true do
it { is_expected.to delegate_method(:sha).to(:pipeline) }
it { is_expected.to delegate_method(:short_sha).to(:pipeline) }
-
+
it { is_expected.to respond_to :success? }
it { is_expected.to respond_to :failed? }
it { is_expected.to respond_to :running? }
@@ -116,7 +121,7 @@ describe CommitStatus, models: true do
it { is_expected.to be > 0.0 }
end
end
-
+
describe :latest do
subject { CommitStatus.latest.order(:id) }
@@ -198,4 +203,10 @@ describe CommitStatus, models: true do
end
end
end
+
+ describe '#commit' do
+ it 'returns commit pipeline has been created for' do
+ expect(commit_status.commit).to eq project.commit
+ end
+ end
end
diff --git a/spec/models/concerns/participable_spec.rb b/spec/models/concerns/participable_spec.rb
index 7e4ea0f2d66..a9f4ef9ee5e 100644
--- a/spec/models/concerns/participable_spec.rb
+++ b/spec/models/concerns/participable_spec.rb
@@ -37,6 +37,16 @@ describe Participable, models: true do
expect(participants).to include(user3)
end
+ it 'caches the raw list of participants' do
+ instance = model.new
+ user1 = build(:user)
+
+ expect(instance).to receive(:raw_participants).once
+
+ instance.participants(user1)
+ instance.participants(user1)
+ end
+
it 'supports attributes returning another Participable' do
other_model = Class.new { include Participable }
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index ac85f340922..47e9253a10c 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -9,8 +9,8 @@ describe API::API, api: true do
let!(:project) { create(:project, creator_id: user.id) }
let!(:developer) { create(:project_member, :developer, user: user, project: project) }
let!(:reporter) { create(:project_member, :reporter, user: user2, project: project) }
- let(:pipeline) { create(:ci_pipeline, project: project)}
- let(:build) { create(:ci_build, pipeline: pipeline) }
+ let!(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.id) }
+ let!(:build) { create(:ci_build, pipeline: pipeline) }
describe 'GET /projects/:id/builds ' do
let(:query) { '' }
@@ -23,6 +23,11 @@ describe API::API, api: true do
expect(json_response).to be_an Array
end
+ it 'returns correct values' do
+ expect(json_response).not_to be_empty
+ expect(json_response.first['commit']['id']).to eq project.commit.id
+ end
+
context 'filter project with one scope element' do
let(:query) { 'scope=pending' }
@@ -132,7 +137,7 @@ describe API::API, api: true do
describe 'GET /projects/:id/builds/:build_id/trace' do
let(:build) { create(:ci_build, :trace, pipeline: pipeline) }
-
+
before { get api("/projects/#{project.id}/builds/#{build.id}/trace", api_user) }
context 'authorized user' do
diff --git a/spec/requests/api/gitignores_spec.rb b/spec/requests/api/gitignores_spec.rb
deleted file mode 100644
index aab2d8c81b9..00000000000
--- a/spec/requests/api/gitignores_spec.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-require 'spec_helper'
-
-describe API::Gitignores, api: true do
- include ApiHelpers
-
- describe 'Entity Gitignore' do
- before { get api('/gitignores/Ruby') }
-
- it { expect(json_response['name']).to eq('Ruby') }
- it { expect(json_response['content']).to include('*.gem') }
- end
-
- describe 'Entity GitignoresList' do
- before { get api('/gitignores') }
-
- it { expect(json_response.first['name']).not_to be_nil }
- it { expect(json_response.first['content']).to be_nil }
- end
-
- describe 'GET /gitignores' do
- it 'returns a list of available license templates' do
- get api('/gitignores')
-
- expect(response.status).to eq(200)
- expect(json_response).to be_an Array
- expect(json_response.size).to be > 15
- end
- end
-end
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb
index 73ae8ef631c..b4c826522a5 100644
--- a/spec/requests/api/runners_spec.rb
+++ b/spec/requests/api/runners_spec.rb
@@ -187,14 +187,16 @@ describe API::Runners, api: true do
update_runner(shared_runner.id, admin, description: "#{description}_updated",
active: !active,
tag_list: ['ruby2.1', 'pgsql', 'mysql'],
- run_untagged: 'false')
+ run_untagged: 'false',
+ locked: 'true')
shared_runner.reload
expect(response.status).to eq(200)
expect(shared_runner.description).to eq("#{description}_updated")
expect(shared_runner.active).to eq(!active)
expect(shared_runner.tag_list).to include('ruby2.1', 'pgsql', 'mysql')
- expect(shared_runner.run_untagged?).to be false
+ expect(shared_runner.run_untagged?).to be(false)
+ expect(shared_runner.locked?).to be(true)
end
end
@@ -360,11 +362,13 @@ describe API::Runners, api: true do
describe 'POST /projects/:id/runners' do
context 'authorized user' do
- it 'should enable specific runner' do
- specific_runner2 = create(:ci_runner).tap do |runner|
+ let(:specific_runner2) do
+ create(:ci_runner).tap do |runner|
create(:ci_runner_project, runner: runner, project: project2)
end
+ end
+ it 'should enable specific runner' do
expect do
post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id
end.to change{ project.runners.count }.by(+1)
@@ -375,7 +379,17 @@ describe API::Runners, api: true do
expect do
post api("/projects/#{project.id}/runners", user), runner_id: specific_runner.id
end.to change{ project.runners.count }.by(0)
- expect(response.status).to eq(201)
+ expect(response.status).to eq(409)
+ end
+
+ it 'should not enable locked runner' do
+ specific_runner2.update(locked: true)
+
+ expect do
+ post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id
+ end.to change{ project.runners.count }.by(0)
+
+ expect(response.status).to eq(403)
end
it 'should not enable shared runner' do
diff --git a/spec/requests/api/templates_spec.rb b/spec/requests/api/templates_spec.rb
new file mode 100644
index 00000000000..a6d5ade3013
--- /dev/null
+++ b/spec/requests/api/templates_spec.rb
@@ -0,0 +1,52 @@
+require 'spec_helper'
+
+describe API::Templates, api: true do
+ include ApiHelpers
+
+ describe 'the Template Entity' do
+ before { get api('/gitignores/Ruby') }
+
+ it { expect(json_response['name']).to eq('Ruby') }
+ it { expect(json_response['content']).to include('*.gem') }
+ end
+
+ describe 'the TemplateList Entity' do
+ before { get api('/gitignores') }
+
+ it { expect(json_response.first['name']).not_to be_nil }
+ it { expect(json_response.first['content']).to be_nil }
+ end
+
+ context 'requesting gitignores' do
+ describe 'GET /gitignores' do
+ it 'returns a list of available gitignore templates' do
+ get api('/gitignores')
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to be > 15
+ end
+ end
+ end
+
+ context 'requesting gitlab-ci-ymls' do
+ describe 'GET /gitlab_ci_ymls' do
+ it 'returns a list of available gitlab_ci_ymls' do
+ get api('/gitlab_ci_ymls')
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).not_to be_nil
+ end
+ end
+ end
+
+ describe 'GET /gitlab_ci_ymls/Ruby' do
+ it 'adds a disclaimer on the top' do
+ get api('/gitlab_ci_ymls/Ruby')
+
+ expect(response.status).to eq(200)
+ expect(json_response['content']).to start_with("# This file is a template,")
+ end
+ end
+end
diff --git a/spec/workers/merge_worker_spec.rb b/spec/workers/merge_worker_spec.rb
index 1abd87d7d33..b5e1fdb8ded 100644
--- a/spec/workers/merge_worker_spec.rb
+++ b/spec/workers/merge_worker_spec.rb
@@ -9,7 +9,7 @@ describe MergeWorker do
before do
source_project.team << [author, :master]
- source_project.repository.expire_branch_names
+ source_project.repository.expire_branches_cache
end
it 'clears cache of source repo after removing source branch' do
diff --git a/vendor/gitignore/Android.gitignore b/vendor/gitignore/Android.gitignore
index a8368751267..f6b286cea98 100644
--- a/vendor/gitignore/Android.gitignore
+++ b/vendor/gitignore/Android.gitignore
@@ -2,7 +2,7 @@
*.apk
*.ap_
-# Files for the Dalvik VM
+# Files for the ART/Dalvik VM
*.dex
# Java class files
@@ -34,6 +34,7 @@ captures/
# Intellij
*.iml
+.idea/workspace.xml
# Keystore files
*.jks
diff --git a/vendor/gitignore/C++.gitignore b/vendor/gitignore/C++.gitignore
index b8bd0267bdf..4581ef2eeef 100644
--- a/vendor/gitignore/C++.gitignore
+++ b/vendor/gitignore/C++.gitignore
@@ -15,6 +15,7 @@
# Fortran module files
*.mod
+*.smod
# Compiled Static libraries
*.lai
diff --git a/vendor/gitignore/CMake.gitignore b/vendor/gitignore/CMake.gitignore
index b558e9afa6d..0cc7e4b5275 100644
--- a/vendor/gitignore/CMake.gitignore
+++ b/vendor/gitignore/CMake.gitignore
@@ -4,3 +4,4 @@ CMakeScripts
Makefile
cmake_install.cmake
install_manifest.txt
+CTestTestfile.cmake
diff --git a/vendor/gitignore/D.gitignore b/vendor/gitignore/D.gitignore
index b4433f8a512..74b926fc901 100644
--- a/vendor/gitignore/D.gitignore
+++ b/vendor/gitignore/D.gitignore
@@ -18,3 +18,7 @@
.dub
docs.json
__dummy.html
+docs/
+
+# Code coverage
+*.lst
diff --git a/vendor/gitignore/Global/Bazaar.gitignore b/vendor/gitignore/Global/Bazaar.gitignore
new file mode 100644
index 00000000000..3cbbcbd11ec
--- /dev/null
+++ b/vendor/gitignore/Global/Bazaar.gitignore
@@ -0,0 +1,2 @@
+.bzr/
+.bzrignore
diff --git a/vendor/gitignore/Global/OSX.gitignore b/vendor/gitignore/Global/OSX.gitignore
index 660b31353e8..5972fe50f66 100644
--- a/vendor/gitignore/Global/OSX.gitignore
+++ b/vendor/gitignore/Global/OSX.gitignore
@@ -1,4 +1,4 @@
-.DS_Store
+*.DS_Store
.AppleDouble
.LSOverride
@@ -15,6 +15,7 @@ Icon
.TemporaryItems
.Trashes
.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
diff --git a/vendor/gitignore/Global/README.md b/vendor/gitignore/Global/README.md
new file mode 100644
index 00000000000..06b6649bd9a
--- /dev/null
+++ b/vendor/gitignore/Global/README.md
@@ -0,0 +1,10 @@
+## Globally Useful gitignores
+
+This directory contains globally useful gitignores,
+e.g. OS-specific and editor specific.
+
+For more on global gitignores:
+<https://help.github.com/articles/ignoring-files/#create-a-global-gitignore>
+
+And a good blog post about 'em:
+<http://augustl.com/blog/2009/global_gitignores>
diff --git a/vendor/gitignore/Global/SublimeText.gitignore b/vendor/gitignore/Global/SublimeText.gitignore
index 1d4e6137591..69c8c2b29ce 100644
--- a/vendor/gitignore/Global/SublimeText.gitignore
+++ b/vendor/gitignore/Global/SublimeText.gitignore
@@ -12,3 +12,16 @@
# sftp configuration file
sftp-config.json
+
+# Package control specific files
+Package Control.last-run
+Package Control.ca-list
+Package Control.ca-bundle
+Package Control.system-ca-bundle
+Package Control.cache/
+Package Control.ca-certs/
+bh_unicode_properties.cache
+
+# Sublime-github package stores a github token in this file
+# https://packagecontrol.io/packages/sublime-github
+GitHub.sublime-settings
diff --git a/vendor/gitignore/Haskell.gitignore b/vendor/gitignore/Haskell.gitignore
index 096abdd90b3..a4ee41ab62b 100644
--- a/vendor/gitignore/Haskell.gitignore
+++ b/vendor/gitignore/Haskell.gitignore
@@ -16,3 +16,4 @@ cabal.sandbox.config
*.hp
*.eventlog
.stack-work/
+cabal.project.local
diff --git a/vendor/gitignore/Julia.gitignore b/vendor/gitignore/Julia.gitignore
new file mode 100644
index 00000000000..381e0b6d252
--- /dev/null
+++ b/vendor/gitignore/Julia.gitignore
@@ -0,0 +1,4 @@
+*.jl.cov
+*.jl.*.cov
+*.jl.mem
+deps/deps.jl
diff --git a/vendor/gitignore/LICENSE b/vendor/gitignore/LICENSE
new file mode 100644
index 00000000000..b8a103ac9b1
--- /dev/null
+++ b/vendor/gitignore/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2016 GitHub, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/vendor/gitignore/Laravel.gitignore b/vendor/gitignore/Laravel.gitignore
index c491fa2bc6f..1cd717b6921 100644
--- a/vendor/gitignore/Laravel.gitignore
+++ b/vendor/gitignore/Laravel.gitignore
@@ -7,7 +7,6 @@ app/storage/
# Laravel 5 & Lumen specific
bootstrap/cache/
-storage/
.env.*.php
.env.php
.env
diff --git a/vendor/gitignore/Objective-C.gitignore b/vendor/gitignore/Objective-C.gitignore
index 3020bc327a7..86f21d8e0ff 100644
--- a/vendor/gitignore/Objective-C.gitignore
+++ b/vendor/gitignore/Objective-C.gitignore
@@ -24,6 +24,8 @@ xcuserdata/
## Obj-C/Swift specific
*.hmap
*.ipa
+*.dSYM.zip
+*.dSYM
# CocoaPods
#
@@ -49,3 +51,10 @@ Carthage/Build
fastlane/report.xml
fastlane/screenshots
+
+#Code Injection
+#
+# After new code Injection tools there's a generated folder /iOSInjectionProject
+# https://github.com/johnno1962/injectionforxcode
+
+iOSInjectionProject/
diff --git a/vendor/gitignore/Qt.gitignore b/vendor/gitignore/Qt.gitignore
index fa24b2efee8..c7659c24f38 100644
--- a/vendor/gitignore/Qt.gitignore
+++ b/vendor/gitignore/Qt.gitignore
@@ -34,5 +34,5 @@ Makefile*
*.qmlproject.user.*
# QtCtreator CMake
-CMakeLists.txt.user
+CMakeLists.txt.user*
diff --git a/vendor/gitignore/README.md b/vendor/gitignore/README.md
deleted file mode 100644
index 43131e815cc..00000000000
--- a/vendor/gitignore/README.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# .gitignore templates
-
-This directory contains language-specific .gitignore templates that are used by GitLab.
-
-These files were automatically pulled from [this repository](https://github.com/github/gitignore).
-Please submit pull requests to that repository. There is no need to edit the files in this directory.
-
-## Bulk Update
-
-To update this directory with the latest changes in the repository, run:
-
-```sh
-bundle exec rake gitlab:update_gitignore
-```
diff --git a/vendor/gitignore/Rails.gitignore b/vendor/gitignore/Rails.gitignore
index 2121e0a8038..d8c256c1925 100644
--- a/vendor/gitignore/Rails.gitignore
+++ b/vendor/gitignore/Rails.gitignore
@@ -16,6 +16,10 @@ pickle-email-*.html
config/initializers/secret_token.rb
config/secrets.yml
+# dotenv
+# TODO Comment out this rule if environment variables can be committed
+.env
+
## Environment normalization:
/.bundle
/vendor/bundle
diff --git a/vendor/gitignore/Swift.gitignore b/vendor/gitignore/Swift.gitignore
index 8a29fa52af4..2c22487b5e3 100644
--- a/vendor/gitignore/Swift.gitignore
+++ b/vendor/gitignore/Swift.gitignore
@@ -24,6 +24,8 @@ xcuserdata/
## Obj-C/Swift specific
*.hmap
*.ipa
+*.dSYM.zip
+*.dSYM
## Playgrounds
timeline.xctimeline
diff --git a/vendor/gitignore/UnrealEngine.gitignore b/vendor/gitignore/UnrealEngine.gitignore
index 75b1186b0af..be0e4913c3a 100644
--- a/vendor/gitignore/UnrealEngine.gitignore
+++ b/vendor/gitignore/UnrealEngine.gitignore
@@ -37,6 +37,7 @@
*.suo
*.opensdf
*.sdf
+*.VC.db
*.VC.opendb
# Precompiled Assets
diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore
index f1e3d20e056..67acbf42f5e 100644
--- a/vendor/gitignore/VisualStudio.gitignore
+++ b/vendor/gitignore/VisualStudio.gitignore
@@ -42,6 +42,7 @@ dlldata.c
# DNX
project.lock.json
+project.fragment.lock.json
artifacts/
*_i.c
diff --git a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml
new file mode 100644
index 00000000000..396d3f1b042
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml
@@ -0,0 +1,7 @@
+# Official docker image.
+image: docker:latest
+
+build:
+ stage: build
+ script:
+ - docker build -t test .
diff --git a/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml b/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml
new file mode 100644
index 00000000000..0b329aaf1c4
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml
@@ -0,0 +1,18 @@
+# This template uses the non default language docker image
+# The image already has Hex installed. You might want to consider to use `elixir:latest`
+image: trenpixster/elixir:latest
+
+# Pic zero or more services to be used on all builds.
+# Only needed when using a docker container to run your tests in.
+# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service
+services:
+ - mysql:latest
+ - redis:latest
+ - postgres:latest
+
+before_script:
+ - mix deps.get
+
+mix:
+ script:
+ - mix test
diff --git a/vendor/gitlab-ci-yml/LICENSE b/vendor/gitlab-ci-yml/LICENSE
new file mode 100644
index 00000000000..80f7b87b6c0
--- /dev/null
+++ b/vendor/gitlab-ci-yml/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 GitLab.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml b/vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml
new file mode 100644
index 00000000000..e5bce3503f3
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml
@@ -0,0 +1,27 @@
+# Official framework image. Look for the different tagged releases at:
+# https://hub.docker.com/r/library/node/tags/
+image: node:latest
+
+# Pick zero or more services to be used on all builds.
+# Only needed when using a docker container to run your tests in.
+# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service
+services:
+ - mysql:latest
+ - redis:latest
+ - postgres:latest
+
+# This folder is cached between builds
+# http://docs.gitlab.com/ce/ci/yaml/README.html#cache
+cache:
+ paths:
+ - node_modules/
+
+test_async:
+ script:
+ - npm install
+ - node ./specs/start.js ./specs/async.spec.js
+
+test_db:
+ script:
+ - npm install
+ - node ./specs/start.js ./specs/db-postgres.spec.js
diff --git a/vendor/gitlab-ci-yml/Pages/brunch.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/brunch.gitlab-ci.yml
new file mode 100644
index 00000000000..7fcc0b436b5
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/brunch.gitlab-ci.yml
@@ -0,0 +1,16 @@
+# Full project: https://gitlab.com/pages/brunch
+image: node:4.2.2
+
+pages:
+ cache:
+ paths:
+ - node_modules/
+
+ script:
+ - npm install -g brunch
+ - brunch build --production
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/doxygen.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/doxygen.gitlab-ci.yml
new file mode 100644
index 00000000000..791afdd23f1
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/doxygen.gitlab-ci.yml
@@ -0,0 +1,13 @@
+# Full project: https://gitlab.com/pages/doxygen
+image: alpine
+
+pages:
+ script:
+ - apk update && apk add doxygen
+ - doxygen doxygen/Doxyfile
+ - mv doxygen/documentation/html/ public/
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/harp.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/harp.gitlab-ci.yml
new file mode 100644
index 00000000000..dd3ef149668
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/harp.gitlab-ci.yml
@@ -0,0 +1,16 @@
+# Full project: https://gitlab.com/pages/harp
+image: node:4.2.2
+
+pages:
+ cache:
+ paths:
+ - node_modules
+
+ script:
+ - npm install -g harp
+ - harp compile ./ public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/hexo.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/hexo.gitlab-ci.yml
new file mode 100644
index 00000000000..b468d79bcad
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/hexo.gitlab-ci.yml
@@ -0,0 +1,25 @@
+# Full project: https://gitlab.com/pages/hexo
+image: python:2.7
+
+cache:
+ paths:
+ - vendor/
+
+test:
+ stage: test
+ script:
+ - pip install hyde
+ - hyde gen
+ except:
+ - master
+
+pages:
+ stage: deploy
+ script:
+ - pip install hyde
+ - hyde gen -d public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/html.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/html.gitlab-ci.yml
new file mode 100644
index 00000000000..249a168aa33
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/html.gitlab-ci.yml
@@ -0,0 +1,12 @@
+# Full project: https://gitlab.com/pages/plain-html
+pages:
+ stage: deploy
+ script:
+ - mkdir .public
+ - cp -r * .public
+ - mv .public public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/hugo.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/hugo.gitlab-ci.yml
new file mode 100644
index 00000000000..45df6975259
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/hugo.gitlab-ci.yml
@@ -0,0 +1,11 @@
+# Full project: https://gitlab.com/pages/hugo
+image: publysher/hugo
+
+pages:
+ script:
+ - hugo
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/hyde.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/hyde.gitlab-ci.yml
new file mode 100644
index 00000000000..f5b40f2b9f1
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/hyde.gitlab-ci.yml
@@ -0,0 +1,25 @@
+# Full project: https://gitlab.com/pages/hyde
+image: python:2.7
+
+cache:
+ paths:
+ - vendor/
+
+test:
+ stage: test
+ script:
+ - pip install hyde
+ - hyde gen
+ except:
+ - master
+
+pages:
+ stage: deploy
+ script:
+ - pip install hyde
+ - hyde gen -d public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/jekyll.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/jekyll.gitlab-ci.yml
new file mode 100644
index 00000000000..36918fc005a
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/jekyll.gitlab-ci.yml
@@ -0,0 +1,24 @@
+# Full project: https://gitlab.com/pages/jekyll
+image: ruby:2.3
+
+test:
+ stage: test
+ script:
+ - gem install jekyll
+ - jekyll build -d test
+ artifacts:
+ paths:
+ - test
+ except:
+ - master
+
+pages:
+ stage: deploy
+ script:
+ - gem install jekyll
+ - jekyll build -d public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/lektor.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/lektor.gitlab-ci.yml
new file mode 100644
index 00000000000..c5c44a5d86c
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/lektor.gitlab-ci.yml
@@ -0,0 +1,12 @@
+# Full project: https://gitlab.com/pages/hyde
+image: python:2.7
+
+pages:
+ script:
+ - pip install lektor
+ - lektor build --output-path public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/metalsmith.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/metalsmith.gitlab-ci.yml
new file mode 100644
index 00000000000..50e8b7ccd46
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/metalsmith.gitlab-ci.yml
@@ -0,0 +1,17 @@
+# Full project: https://gitlab.com/pages/metalsmith
+image: node:4.2.2
+
+pages:
+ cache:
+ paths:
+ - node_modules/
+
+ script:
+ - npm install -g metalsmith
+ - npm install
+ - make build
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/middleman.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/middleman.gitlab-ci.yml
new file mode 100644
index 00000000000..9f4cc0574d6
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/middleman.gitlab-ci.yml
@@ -0,0 +1,27 @@
+# Full project: https://gitlab.com/pages/middleman
+image: ruby:2.3
+
+cache:
+ paths:
+ - vendor
+
+test:
+ script:
+ - apt-get update -yqqq
+ - apt-get install -y nodejs
+ - bundle install --path vendor
+ - bundle exec middleman build
+ except:
+ - master
+
+pages:
+ script:
+ - apt-get update -yqqq
+ - apt-get install -y nodejs
+ - bundle install --path vendor
+ - bundle exec middleman build
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/nanoc.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/nanoc.gitlab-ci.yml
new file mode 100644
index 00000000000..b469b316ba5
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/nanoc.gitlab-ci.yml
@@ -0,0 +1,12 @@
+# Full project: https://gitlab.com/pages/nanoc
+image: ruby:2.3
+
+pages:
+ script:
+ - bundle install -j4
+ - nanoc
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/octopress.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/octopress.gitlab-ci.yml
new file mode 100644
index 00000000000..4762ec9acfd
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/octopress.gitlab-ci.yml
@@ -0,0 +1,15 @@
+# Full project: https://gitlab.com/pages/octopress
+image: ruby:2.3
+
+pages:
+ script:
+ - apt-get update -qq && apt-get install -qq nodejs
+ - bundle install -j4
+ - bundle exec rake generate
+ - mv public .public
+ - mv .public/octopress public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/pelican.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/pelican.gitlab-ci.yml
new file mode 100644
index 00000000000..c5f3154f587
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/pelican.gitlab-ci.yml
@@ -0,0 +1,10 @@
+# Full project: https://gitlab.com/pages/pelican
+image: python:2.7-alpine
+
+pages:
+ script:
+ - pip install -r requirements.txt
+ - pelican -s publishconf.py
+ artifacts:
+ paths:
+ - public/
diff --git a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
new file mode 100644
index 00000000000..78f3e39949f
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
@@ -0,0 +1,30 @@
+# Official language image. Look for the different tagged releases at:
+# https://hub.docker.com/r/library/ruby/tags/
+image: "ruby:2.3"
+
+# Pick zero or more services to be used on all builds.
+# Only needed when using a docker container to run your tests in.
+# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service
+services:
+ - mysql:latest
+ - redis:latest
+ - postgres:latest
+
+# This is a basic example for a gem or script which doesn't use
+# services such as redis or postgres
+before_script:
+ - gem install bundler # Bundler is not installed with the image
+ - bundle install -j $(nproc) # Install dependencies
+
+rubocop:
+ script:
+ - rubocop
+
+rspec:
+ script:
+ - rspec spec
+
+rails:
+ script:
+ - rake db:migrate
+ - rspec spec