summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG73
-rw-r--r--Gemfile7
-rw-r--r--Gemfile.lock12
-rw-r--r--app/assets/javascripts/gl_dropdown.js.coffee15
-rw-r--r--app/assets/javascripts/notes.js.coffee6
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss27
-rw-r--r--app/assets/stylesheets/pages/builds.scss8
-rw-r--r--app/assets/stylesheets/pages/commit.scss6
-rw-r--r--app/assets/stylesheets/pages/events.scss8
-rw-r--r--app/assets/stylesheets/pages/issuable.scss2
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss1
-rw-r--r--app/assets/stylesheets/pages/notes.scss23
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss20
-rw-r--r--app/assets/stylesheets/pages/status.scss9
-rw-r--r--app/controllers/help_controller.rb2
-rw-r--r--app/controllers/projects/commit_controller.rb4
-rw-r--r--app/controllers/projects/compare_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb8
-rw-r--r--app/controllers/projects/notes_controller.rb65
-rw-r--r--app/controllers/projects/uploads_controller.rb8
-rw-r--r--app/helpers/ci_status_helper.rb4
-rw-r--r--app/helpers/diff_helper.rb16
-rw-r--r--app/helpers/notes_helper.rb37
-rw-r--r--app/helpers/time_helper.rb1
-rw-r--r--app/models/ci/build.rb2
-rw-r--r--app/models/ci/pipeline.rb7
-rw-r--r--app/models/commit_status.rb6
-rw-r--r--app/models/concerns/note_on_diff.rb25
-rw-r--r--app/models/discussion.rb91
-rw-r--r--app/models/note.rb7
-rw-r--r--app/models/project.rb11
-rw-r--r--app/models/repository.rb35
-rw-r--r--app/models/service.rb2
-rw-r--r--app/services/delete_branch_service.rb2
-rw-r--r--app/services/delete_tag_service.rb2
-rw-r--r--app/services/git_tag_push_service.rb4
-rw-r--r--app/services/repository_archive_clean_up_service.rb33
-rw-r--r--app/uploaders/file_uploader.rb5
-rw-r--r--app/uploaders/uploader_helper.rb41
-rw-r--r--app/views/discussions/_diff_discussion.html.haml6
-rw-r--r--app/views/discussions/_diff_with_notes.html.haml14
-rw-r--r--app/views/discussions/_discussion.html.haml45
-rw-r--r--app/views/discussions/_notes.html.haml5
-rw-r--r--app/views/discussions/_parallel_diff_discussion.html.haml22
-rw-r--r--app/views/events/_event_scope.html.haml7
-rw-r--r--app/views/events/event/_common.html.haml9
-rw-r--r--app/views/events/event/_created_project.html.haml2
-rw-r--r--app/views/events/event/_note.html.haml11
-rw-r--r--app/views/events/event/_push.html.haml6
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml2
-rw-r--r--app/views/layouts/project.html.haml2
-rw-r--r--app/views/projects/_activity.html.haml3
-rw-r--r--app/views/projects/branches/_commit.html.haml2
-rw-r--r--app/views/projects/ci/builds/_build.html.haml10
-rw-r--r--app/views/projects/commit/_pipeline.html.haml4
-rw-r--r--app/views/projects/commits/_commit.html.haml5
-rw-r--r--app/views/projects/deployments/_actions.haml2
-rw-r--r--app/views/projects/diffs/_match_line_parallel.html.haml4
-rw-r--r--app/views/projects/diffs/_parallel_view.html.haml51
-rw-r--r--app/views/projects/diffs/_text_file.html.haml6
-rw-r--r--app/views/projects/new.html.haml24
-rw-r--r--app/views/projects/notes/_diff_notes_with_reply.html.haml7
-rw-r--r--app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml25
-rw-r--r--app/views/projects/notes/_discussion.html.haml46
-rw-r--r--app/views/projects/notes/_note.html.haml2
-rw-r--r--app/views/projects/notes/_notes.html.haml12
-rw-r--r--app/views/projects/notes/discussions/_diff_with_notes.html.haml17
-rw-r--r--app/views/projects/notes/discussions/_notes.html.haml6
-rw-r--r--app/views/shared/icons/_icon_fork.svg4
-rw-r--r--app/workers/emails_on_push_worker.rb4
-rw-r--r--app/workers/repository_archive_cache_worker.rb2
-rw-r--r--config/gitlab.yml.example4
-rw-r--r--config/initializers/1_settings.rb16
-rw-r--r--config/initializers/6_validations.rb21
-rw-r--r--config/initializers/mime_types.rb8
-rw-r--r--config/initializers/secure_headers.rb99
-rw-r--r--db/migrate/20160721081015_drop_and_readd_has_external_wiki_in_projects.rb15
-rw-r--r--db/schema.rb2
-rw-r--r--doc/api/award_emoji.md4
-rw-r--r--doc/api/projects.md1
-rw-r--r--doc/ci/yaml/README.md104
-rw-r--r--doc/container_registry/README.md3
-rw-r--r--doc/install/installation.md2
-rw-r--r--doc/markdown/img/video.mp4bin0 -> 383631 bytes
-rw-r--r--doc/markdown/markdown.md15
-rw-r--r--doc/update/8.9-to-8.10.md2
-rw-r--r--features/project/wiki.feature6
-rw-r--r--features/steps/project/wiki.rb10
-rw-r--r--lib/api/builds.rb58
-rw-r--r--lib/banzai/filter/video_link_filter.rb59
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb1
-rw-r--r--lib/banzai/reference_extractor.rb9
-rw-r--r--lib/gitlab/diff/parallel_diff.rb63
-rw-r--r--spec/controllers/projects/uploads_controller_spec.rb12
-rw-r--r--spec/factories/issues.rb10
-rw-r--r--spec/features/environments_spec.rb8
-rw-r--r--spec/features/markdown_spec.rb12
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb15
-rw-r--r--spec/fixtures/markdown.md.erb4
-rw-r--r--spec/fixtures/parallel_diff_result.yml800
-rw-r--r--spec/fixtures/video_sample.mp4bin0 -> 59122 bytes
-rw-r--r--spec/helpers/time_helper_spec.rb1
-rw-r--r--spec/initializers/6_validations_spec.rb37
-rw-r--r--spec/lib/banzai/filter/video_link_filter_spec.rb51
-rw-r--r--spec/lib/gitlab/diff/parallel_diff_spec.rb46
-rw-r--r--spec/lib/gitlab/email/attachment_uploader_spec.rb1
-rw-r--r--spec/lib/gitlab/email/receiver_spec.rb1
-rw-r--r--spec/models/build_spec.rb27
-rw-r--r--spec/models/note_spec.rb40
-rw-r--r--spec/models/project_spec.rb145
-rw-r--r--spec/models/repository_spec.rb50
-rw-r--r--spec/requests/api/builds_spec.rb108
-rw-r--r--spec/requests/api/projects_spec.rb1
-rw-r--r--spec/services/issues/bulk_update_service_spec.rb131
-rw-r--r--spec/services/projects/download_service_spec.rb4
-rw-r--r--spec/services/projects/upload_service_spec.rb8
-rw-r--r--spec/services/repository_archive_clean_up_service_spec.rb81
-rw-r--r--spec/support/matchers/markdown_matchers.rb11
-rw-r--r--spec/uploaders/file_uploader_spec.rb45
-rw-r--r--spec/workers/emails_on_push_worker_spec.rb36
120 files changed, 1525 insertions, 1666 deletions
diff --git a/CHANGELOG b/CHANGELOG
index e1145f36ce3..bc09a51cc1e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,35 +1,40 @@
Please view this file on the master branch, on stable branches it's out of date.
+
v 8.11.0 (unreleased)
- Fix of 'Commits being passed to custom hooks are already reachable when using the UI'
- Limit git rev-list output count to one in forced push check
+ - Retrieve rendered HTML from cache in one request
v 8.10.0 (unreleased)
- Fix profile activity heatmap to show correct day name (eanplatter)
- Speed up ExternalWikiHelper#get_project_wiki_path
- Expose {should,force}_remove_source_branch (Ben Boeckel)
- - Add the functionality to be able to rename a file. !5049 (tiagonbotelho)
+ - Add the functionality to be able to rename a file. !5049
- Disable PostgreSQL statement timeout during migrations
- - Fix projects dropdown loading performance with a simplified api cal. !5113 (tiagonbotelho)
+ - Fix projects dropdown loading performance with a simplified api cal. !5113
- Fix commit builds API, return all builds for all pipelines for given commit. !4849
- Replace Haml with Hamlit to make view rendering faster. !3666
- Refresh the branch cache after `git gc` runs
- Allow to disable request access button on projects/groups
- Refactor repository paths handling to allow multiple git mount points
- - Optimize system note visibility checking by memoizing the visible reference count !5070
+ - Optimize system note visibility checking by memoizing the visible reference count. !5070
- Add Application Setting to configure default Repository Path for new projects
- Delete award emoji when deleting a user
- - Remove pinTo from Flash and make inline flash messages look nicer !4854 (winniehell)
- - Add link to profile to commit avatar !5163 (winniehell)
+ - Remove pinTo from Flash and make inline flash messages look nicer. !4854 (winniehell)
+ - Add an API for downloading latest successful build from a particular branch or tag. !5347
+ - Avoid data-integrity issue when cleaning up repository archive cache.
+ - Add link to profile to commit avatar. !5163 (winniehell)
- Wrap code blocks on Activies and Todos page. !4783 (winniehell)
- - Align flash messages with left side of page content !4959 (winniehell)
- - Display tooltip for "Copy to Clipboard" button !5164 (winniehell)
- - Use default cursor for table header of project files !5165 (winniehell)
+ - Align flash messages with left side of page content. !4959 (winniehell)
+ - Display tooltip for "Copy to Clipboard" button. !5164 (winniehell)
+ - Use default cursor for table header of project files. !5165 (winniehell)
- Store when and yaml variables in builds table
- - Display last commit of deleted branch in push events !4699 (winniehell)
- - Escape file extension when parsing search results !5141 (winniehell)
+ - Display last commit of deleted branch in push events. !4699 (winniehell)
+ - Escape file extension when parsing search results. !5141 (winniehell)
- Add "passing with warnings" to the merge request pipeline possible statuses, this happens when builds that allow failures have failed. !5004
+ - Add image border in Markdown preview. !5162 (winniehell)
- Apply the trusted_proxies config to the rack request object for use with rack_attack
- - Added the ability to block sign ups using a domain blacklist !5259
+ - Added the ability to block sign ups using a domain blacklist. !5259
- Upgrade to Rails 4.2.7. !5236
- Extend exposed environment variables for CI builds
- Deprecate APIs "projects/:id/keys/...". Use "projects/:id/deploy_keys/..." instead
@@ -37,35 +42,36 @@ v 8.10.0 (unreleased)
- Allow to pull code with deploy key from public projects
- Use limit parameter rather than hardcoded value in `ldap:check` rake task (Mike Ricketts)
- Add Sidekiq queue duration to transaction metrics.
- - Add a new column `artifacts_size` to table `ci_builds` !4964
+ - Add a new column `artifacts_size` to table `ci_builds`. !4964
- Let Workhorse serve format-patch diffs
- - Display tooltip for mentioned users and groups !5261 (winniehell)
+ - Display tooltip for mentioned users and groups. !5261 (winniehell)
- Allow build email service to be tested
- Added day name to contribution calendar tooltips
- Refactor user authorization check for a single project to avoid querying all user projects
- - Make images fit to the size of the viewport !4810
- - Fix check for New Branch button on Issue page !4630 (winniehell)
+ - Make images fit to the size of the viewport. !4810
+ - Fix check for New Branch button on Issue page. !4630 (winniehell)
- Fix GFM autocomplete not working on wiki pages
+ - Fixed enter key not triggering click on first row when searching in a dropdown
- Fix MR-auto-close text added to description. !4836
- Support U2F devices in Firefox. !5177
- - Fix issue, preventing users w/o push access to sort tags !5105 (redetection)
+ - Fix issue, preventing users w/o push access to sort tags. !5105 (redetection)
- Add Spring EmojiOne updates.
- - Added Rake task for tracking deployments !5320
+ - Added Rake task for tracking deployments. !5320
- Fix fetching LFS objects for private CI projects
- Add the new 2016 Emoji! Adds 72 new emoji including bacon, facepalm, and selfie. !5237
- - Add syntax for multiline blockquote using `>>>` fence !3954
+ - Add syntax for multiline blockquote using `>>>` fence. !3954
- Fix viewing notification settings when a project is pending deletion
- Updated compare dropdown menus to use GL dropdown
- Redirects back to issue after clicking login link
- Eager load award emoji on notes
- Allow to define manual actions/builds on Pipelines and Environments
- Fix pagination when sorting by columns with lots of ties (like priority)
- - The Markdown reference parsers now re-use query results to prevent running the same queries multiple times !5020
+ - The Markdown reference parsers now re-use query results to prevent running the same queries multiple times. !5020
- Updated project header design
- Issuable collapsed assignee tooltip is now the users name
- Fix compare view not changing code view rendering style
- Exclude email check from the standard health check
- - Updated layout for Projects, Groups, Users on Admin area !4424
+ - Updated layout for Projects, Groups, Users on Admin area. !4424
- Fix changing issue state columns in milestone view
- Update health_check gem to version 2.1.0
- Add notification settings dropdown for groups
@@ -73,22 +79,24 @@ v 8.10.0 (unreleased)
- Wildcards for protected branches. !4665
- Allow importing from Github using Personal Access Tokens. (Eric K Idema)
- API: Expose `due_date` for issues (Robert Schilling)
- - API: Todos !3188 (Robert Schilling)
- - API: Expose shared groups for projects and shared projects for groups !5050 (Robert Schilling)
- - API: Expose `developers_can_push` and `developers_can_merge` for branches !5208 (Robert Schilling)
+ - API: Todos. !3188 (Robert Schilling)
+ - API: Expose shared groups for projects and shared projects for groups. !5050 (Robert Schilling)
+ - API: Expose `developers_can_push` and `developers_can_merge` for branches. !5208 (Robert Schilling)
+ - Update to gitlab_git 10.4.1 and take advantage of preserved Ref objects
- Add "Enabled Git access protocols" to Application Settings
- Diffs will create button/diff form on demand no on server side
- Reduce size of HTML used by diff comment forms
- Protected branches have a "Developers can Merge" setting. !4892 (original implementation by Mathias Vestergaard)
- - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt)
+ - Fix user creation with stronger minimum password requirements. !4054 (nathan-pmt)
- Only show New Snippet button to users that can create snippets.
- PipelinesFinder uses git cache data
- Track a user who created a pipeline
- Actually render old and new sections of parallel diff next to each other
- Throttle the update of `project.pushes_since_gc` to 1 minute.
- - Allow expanding and collapsing files in diff view (!4990)
+ - Allow expanding and collapsing files in diff view. !4990
- Collapse large diffs by default (!4990)
- Fix mentioned users list on diff notes
+ - Add support for inline videos in GitLab Flavored Markdown. !5215 (original implementation by Eric Hayes)
- Fix creation of deployment on build that is retried, redeployed or rollback
- Don't parse Rinku returned value to DocFragment when it didn't change the original html string.
- Check for conflicts with existing Project's wiki path when creating a new project.
@@ -101,8 +109,9 @@ v 8.10.0 (unreleased)
- ObjectRenderer retrieve renderer content using Rails.cache.read_multi
- Better caching of git calls on ProjectsController#show.
- Avoid to retrieve MR closes_issues as much as possible.
- - Add API endpoint for a group issues !4520 (mahcsig)
- - Add Bugzilla integration !4930 (iamtjg)
+ - Hide project name in project activities. !5068 (winniehell)
+ - Add API endpoint for a group issues. !4520 (mahcsig)
+ - Add Bugzilla integration. !4930 (iamtjg)
- Fix new snippet style bug (elliotec)
- Instrument Rinku usage
- Be explicit to define merge request discussion variables
@@ -124,10 +133,10 @@ v 8.10.0 (unreleased)
- Fix importer for GitHub Pull Requests when a branch was reused across Pull Requests
- Add date when user joined the team on the member page
- Fix 404 redirect after validation fails importing a GitLab project
- - Added setting to set new users by default as external !4545 (Dravere)
- - Add min value for project limit field on user's form !3622 (jastkand)
+ - Added setting to set new users by default as external. !4545 (Dravere)
+ - Add min value for project limit field on user's form. !3622 (jastkand)
- Reset project pushes_since_gc when we enqueue the git gc call
- - Add reminder to not paste private SSH keys !4399 (Ingo Blechschmidt)
+ - Add reminder to not paste private SSH keys. !4399 (Ingo Blechschmidt)
- Collapsed diffs lines/size don't acumulate to overflow diffs.
- Remove duplicate `description` field in `MergeRequest` entities (Ben Boeckel)
- Style of import project buttons were fixed in the new project page. !5183 (rdemirbay)
@@ -141,7 +150,7 @@ v 8.10.0 (unreleased)
- Fix last update timestamp on issues not preserved on gitlab.com and project imports
- Fix issues importing projects from EE to CE
- Fix creating group with space in group path
- - Improve cron_jobs loading error messages !5318 / !5360
+ - Improve cron_jobs loading error messages. !5318 / !5360
- Prevent toggling sidebar when clipboard icon clicked
- Create Todos for Issue author when assign or mention himself (Katarzyna Kobierska)
- Limit the number of retries on error to 3 for exporting projects
@@ -153,6 +162,7 @@ v 8.10.0 (unreleased)
- Add builds badge to Pipelines settings page
- Export and import avatar as part of project import/export
- Fix migration corrupting import data for old version upgrades
+ - Show tooltip on GitLab export link in new project page
v 8.9.6
- Fix importing of events under notes for GitLab projects. !5154
@@ -255,6 +265,7 @@ v 8.9.1
- Remove width restriction for logo on sign-in page. !4888
- Bump gitlab_git to 10.2.3 to fix false truncated warnings with ISO-8559 files. !4884
- Apply selected value as label. !4886
+ - Change Retry to Re-deploy on Deployments page
- Fix temp file being deleted after the request while importing a GitLab project. !4894
- Fix pagination when sorting by columns with lots of ties (like priority)
- Implement Subresource Integrity for CSS and JavaScript assets. This prevents malicious assets from loading in the case of a CDN compromise.
diff --git a/Gemfile b/Gemfile
index c5df68839d5..0504e643ed7 100644
--- a/Gemfile
+++ b/Gemfile
@@ -52,7 +52,7 @@ gem 'browser', '~> 2.2'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem 'gitlab_git', '~> 10.3.2'
+gem 'gitlab_git', '~> 10.4.1'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
@@ -347,8 +347,5 @@ gem 'paranoia', '~> 2.0'
gem 'health_check', '~> 2.1.0'
# System information
-gem 'vmstat', '~> 2.1.0'
+gem 'vmstat', '~> 2.1.1'
gem 'sys-filesystem', '~> 1.1.6'
-
-# Secure headers for Content Security Policy
-gem 'secure_headers', '~> 3.3'
diff --git a/Gemfile.lock b/Gemfile.lock
index 363904a4baa..195516d1bf1 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -274,7 +274,7 @@ GEM
diff-lcs (~> 1.1)
mime-types (>= 1.16, < 3)
posix-spawn (~> 0.3)
- gitlab_git (10.3.2)
+ gitlab_git (10.4.1)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
@@ -645,8 +645,6 @@ GEM
sdoc (0.3.20)
json (>= 1.1.3)
rdoc (~> 3.10)
- secure_headers (3.3.2)
- useragent
seed-fu (2.3.6)
activerecord (>= 3.1)
activesupport (>= 3.1)
@@ -769,7 +767,6 @@ GEM
get_process_mem (~> 0)
unicorn (>= 4, < 6)
uniform_notifier (1.9.0)
- useragent (0.16.7)
uuid (2.3.8)
macaddr (~> 1.0)
version_sorter (2.0.0)
@@ -778,7 +775,7 @@ GEM
coercible (~> 1.0)
descendants_tracker (~> 0.0, >= 0.0.3)
equalizer (~> 0.0, >= 0.0.9)
- vmstat (2.1.0)
+ vmstat (2.1.1)
warden (1.2.6)
rack (>= 1.0)
web-console (2.3.0)
@@ -864,7 +861,7 @@ DEPENDENCIES
github-linguist (~> 4.7.0)
github-markup (~> 1.4)
gitlab-flowdock-git-hook (~> 1.0.1)
- gitlab_git (~> 10.3.2)
+ gitlab_git (~> 10.4.1)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.2)
@@ -947,7 +944,6 @@ DEPENDENCIES
sass-rails (~> 5.0.0)
scss_lint (~> 0.47.0)
sdoc (~> 0.3.20)
- secure_headers (~> 3.3)
seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9)
sentry-raven (~> 1.1.0)
@@ -984,7 +980,7 @@ DEPENDENCIES
unicorn-worker-killer (~> 0.4.2)
version_sorter (~> 2.0.0)
virtus (~> 1.0.1)
- vmstat (~> 2.1.0)
+ vmstat (~> 2.1.1)
web-console (~> 2.0)
webmock (~> 1.21.0)
wikicloth (= 0.8.1)
diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee
index 951530e03a5..7086ece29b8 100644
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ b/app/assets/javascripts/gl_dropdown.js.coffee
@@ -210,9 +210,22 @@ class GitLabDropdown
data: =>
return @fullData
callback: (data) =>
- currentIndex = -1
@parseData data
+ unless @filterInput.val() is ''
+ selector = '.dropdown-content li:not(.divider):visible'
+
+ if @dropdown.find('.dropdown-toggle-page').length
+ selector = ".dropdown-page-one #{selector}"
+
+ $(selector, @dropdown)
+ .first()
+ .find('a')
+ .addClass('is-focused')
+
+ currentIndex = 0
+
+
# Event listeners
@dropdown.on "shown.bs.dropdown", @opened
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 0ea54faae1a..d4de712f88c 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -162,7 +162,7 @@ class @Notes
@last_fetched_at = data.last_fetched_at
@setPollingInterval(data.notes.length)
$.each notes, (i, note) =>
- if note.discussion_with_diff_html?
+ if note.discussion_html?
@renderDiscussionNote(note)
else
@renderNote(note)
@@ -251,7 +251,7 @@ class @Notes
discussionContainer = $(".notes[data-discussion-id='" + note.original_discussion_id + "']")
if discussionContainer.length is 0
# insert the note and the reply button after the temp row
- row.after note.discussion_html
+ row.after note.diff_discussion_html
# remove the note (will be added again below)
row.next().find(".note").remove()
@@ -265,7 +265,7 @@ class @Notes
# Init discussion on 'Discussion' page if it is merge request page
if $('body').attr('data-page').indexOf('projects:merge_request') is 0
$('ul.main-notes-list')
- .append(note.discussion_with_diff_html)
+ .append(note.discussion_html)
.syntaxHighlight()
else
# append new note to all matching discussions
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index 5d3273ea64d..96565da1bc9 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -98,13 +98,30 @@
.md {
&.md-preview-holder {
- code {
- white-space: pre-wrap;
- word-break: keep-all;
- }
-
+ // 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;
+ word-break: keep-all;
+ }
+
+ hr {
+ // Darken 'whitesmoke' a bit to make it more visible in note bodies
+ border-color: darken(#f5f5f5, 8%);
+ margin: 10px 0;
+ }
+
+ // Border around images in issue and MR comments.
+ img:not(.emoji) {
+ border: 1px solid $table-border-gray;
+ padding: 5px;
+ margin: 5px 0;
+ // Ensure that image does not exceed viewport
+ max-height: calc(100vh - 100px);
+ }
}
.toolbar-group {
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 99a2cd306cf..e26f8f7080d 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -53,6 +53,14 @@
left: 70px;
}
}
+
+ .nav-links {
+ svg {
+ position: relative;
+ top: 2px;
+ margin-right: 3px;
+ }
+ }
}
.build-header {
diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss
index 35ab28b3fea..bbe0c6c5f1f 100644
--- a/app/assets/stylesheets/pages/commit.scss
+++ b/app/assets/stylesheets/pages/commit.scss
@@ -68,6 +68,12 @@
}
}
+.ci-status-link {
+ svg {
+ overflow: visible;
+ }
+}
+
.commit-box {
border-top: 1px solid $border-color;
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index a2145956eb5..5c336bb1c7e 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -176,3 +176,11 @@
}
}
}
+
+// hide event scope (namespace + project) where it is not necessary
+.project-activity {
+ .event-scope {
+ display: none;
+ }
+}
+
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index ded437625ee..7a50bc9c832 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -269,7 +269,7 @@
.issuable-header-btn {
background: $gray-normal;
border: 1px solid $border-gray-normal;
-
+
&:hover {
background: $gray-dark;
border: 1px solid $border-gray-dark;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 4e806e60e7e..db295935b00 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -64,6 +64,7 @@
margin-right: 4px;
position: relative;
top: 1px;
+ overflow: visible;
}
&.ci-success {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index ac8c02b59dc..a2b5437e503 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -91,34 +91,11 @@ ul.notes {
// 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;
- }
-
ul.task-list {
ul:not(.task-list) {
padding-left: 1.3em;
}
}
-
- hr {
- // Darken 'whitesmoke' a bit to make it more visible in note bodies
- border-color: darken(#f5f5f5, 8%);
- margin: 10px 0;
- }
-
- 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;
- max-height: calc(100vh - 100px);
- }
}
}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index a404f108dc4..c58e2ffe7f5 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -29,9 +29,18 @@
}
}
+.pipeline-holder {
+ width: 100%;
+ overflow: auto;
+}
+
.table.builds {
min-width: 1200px;
+ &.pipeline {
+ min-width: 650px;
+ }
+
tr {
th {
padding: 16px 8px;
@@ -76,7 +85,7 @@
svg {
height: 14px;
- width: auto;
+ width: 14px;
vertical-align: middle;
fill: $table-text-gray;
}
@@ -93,7 +102,7 @@
.commit-title {
margin-top: 4px;
- max-width: 320px;
+ max-width: 300px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
@@ -138,6 +147,11 @@
height: 18px;
width: 18px;
vertical-align: middle;
+ overflow: visible;
+ }
+
+ .light {
+ width: 3px;
}
}
@@ -153,7 +167,7 @@
svg {
width: 12px;
- height: auto;
+ height: 12px;
vertical-align: middle;
margin-right: 4px;
}
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index 99f64b5e4cc..587f2d9f3c1 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -49,6 +49,7 @@
position: relative;
top: 1px;
margin: 0 3px;
+ overflow: visible;
}
}
@@ -74,3 +75,11 @@
color: $gl-gray;
}
}
+
+.visible-xs-inline {
+ .ci-status-link {
+ position: relative;
+ top: 2px;
+ left: 5px;
+ }
+}
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index d3dd98c8a4e..f7b44099b78 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -30,7 +30,7 @@ class HelpController < ApplicationController
end
# Allow access to images in the doc folder
- format.any(:png, :gif, :jpeg) do
+ format.any(:png, :gif, :jpeg, :mp4) do
# Note: We are purposefully NOT using `Rails.root.join`
path = File.join(Rails.root, 'doc', "#{@path}.#{params[:format]}")
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 727e84b40a1..7ae034f9398 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -115,11 +115,11 @@ class Projects::CommitController < Projects::ApplicationController
end
def define_note_vars
- @grouped_diff_notes = commit.notes.grouped_diff_notes
+ @grouped_diff_discussions = commit.notes.grouped_diff_discussions
@notes = commit.notes.non_diff_notes.fresh
Banzai::NoteRenderer.render(
- @grouped_diff_notes.values.flatten + @notes,
+ @grouped_diff_discussions.values.flat_map(&:notes) + @notes,
@project,
current_user,
)
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index 10749d0fef8..8c004724f02 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -54,7 +54,7 @@ class Projects::CompareController < Projects::ApplicationController
)
@diff_notes_disabled = true
- @grouped_diff_notes = {}
+ @grouped_diff_discussions = {}
end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 7beeb7d97d0..594a61464b9 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -97,7 +97,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
else
build_merge_request
@diff_notes_disabled = true
- @grouped_diff_notes = {}
+ @grouped_diff_discussions = {}
end
define_commit_vars
@@ -378,7 +378,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
# This is not executed lazily
@notes = Banzai::NoteRenderer.render(
- @discussions.flatten,
+ @discussions.flat_map(&:notes),
@project,
current_user,
@path,
@@ -404,10 +404,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
}
@use_legacy_diff_notes = !@merge_request.support_new_diff_notes?
- @grouped_diff_notes = @merge_request.notes.grouped_diff_notes
+ @grouped_diff_discussions = @merge_request.notes.grouped_diff_discussions
Banzai::NoteRenderer.render(
- @grouped_diff_notes.values.flatten,
+ @grouped_diff_discussions.values.flat_map(&:notes),
@project,
current_user,
@path,
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 3eacdbbd067..766b7e9cf22 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -73,7 +73,7 @@ class Projects::NotesController < Projects::ApplicationController
end
alias_method :awardable, :note
- def note_to_html(note)
+ def note_html(note)
render_to_string(
"projects/notes/_note",
layout: false,
@@ -82,20 +82,20 @@ class Projects::NotesController < Projects::ApplicationController
)
end
- def note_to_discussion_html(note)
- return unless note.diff_note?
+ def diff_discussion_html(discussion)
+ return unless discussion.diff_discussion?
if params[:view] == 'parallel'
- template = "projects/notes/_diff_notes_with_reply_parallel"
+ template = "discussions/_parallel_diff_discussion"
locals =
if params[:line_type] == 'old'
- { notes_left: [note], notes_right: [] }
+ { discussion_left: discussion, discussion_right: nil }
else
- { notes_left: [], notes_right: [note] }
+ { discussion_left: nil, discussion_right: discussion }
end
else
- template = "projects/notes/_diff_notes_with_reply"
- locals = { notes: [note] }
+ template = "discussions/_diff_discussion"
+ locals = { discussion: discussion }
end
render_to_string(
@@ -106,14 +106,14 @@ class Projects::NotesController < Projects::ApplicationController
)
end
- def note_to_discussion_with_diff_html(note)
- return unless note.diff_note?
+ def discussion_html(discussion)
+ return unless discussion.diff_discussion?
render_to_string(
- "projects/notes/_discussion",
+ "discussions/_discussion",
layout: false,
formats: [:html],
- locals: { discussion_notes: [note] }
+ locals: { discussion: discussion }
)
end
@@ -132,26 +132,33 @@ class Projects::NotesController < Projects::ApplicationController
valid: true,
id: note.id,
discussion_id: note.discussion_id,
- html: note_to_html(note),
+ html: note_html(note),
award: false,
- note: note.note,
- discussion_html: note_to_discussion_html(note),
- discussion_with_diff_html: note_to_discussion_with_diff_html(note)
+ note: note.note
}
- # The discussion_id is used to add the comment to the correct discussion
- # element on the merge request page. Among other things, the discussion_id
- # contains the sha of head commit of the merge request.
- # When new commits are pushed into the merge request after the initial
- # load of the merge request page, the discussion elements will still have
- # the old discussion_ids, with the old head commit sha. The new comment,
- # however, will have the new discussion_id with the new commit sha.
- # To ensure that these new comments will still end up in the correct
- # discussion element, we also send the original discussion_id, with the
- # old commit sha, along, and fall back on this value when no discussion
- # element with the new discussion_id could be found.
- if note.new_diff_note? && note.position != note.original_position
- attrs[:original_discussion_id] = note.original_discussion_id
+ if note.diff_note?
+ discussion = Discussion.new([note])
+
+ attrs.merge!(
+ diff_discussion_html: diff_discussion_html(discussion),
+ discussion_html: discussion_html(discussion)
+ )
+
+ # The discussion_id is used to add the comment to the correct discussion
+ # element on the merge request page. Among other things, the discussion_id
+ # contains the sha of head commit of the merge request.
+ # When new commits are pushed into the merge request after the initial
+ # load of the merge request page, the discussion elements will still have
+ # the old discussion_ids, with the old head commit sha. The new comment,
+ # however, will have the new discussion_id with the new commit sha.
+ # To ensure that these new comments will still end up in the correct
+ # discussion element, we also send the original discussion_id, with the
+ # old commit sha, along, and fall back on this value when no discussion
+ # element with the new discussion_id could be found.
+ if note.new_diff_note? && note.position != note.original_position
+ attrs[:original_discussion_id] = note.original_discussion_id
+ end
end
attrs
diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb
index caed064dfbc..e617be8f9fb 100644
--- a/app/controllers/projects/uploads_controller.rb
+++ b/app/controllers/projects/uploads_controller.rb
@@ -1,6 +1,6 @@
class Projects::UploadsController < Projects::ApplicationController
skip_before_action :reject_blocked!, :project,
- :repository, if: -> { action_name == 'show' && image? }
+ :repository, if: -> { action_name == 'show' && image_or_video? }
before_action :authorize_upload_file!, only: [:create]
@@ -24,7 +24,7 @@ class Projects::UploadsController < Projects::ApplicationController
def show
return render_404 if uploader.nil? || !uploader.file.exists?
- disposition = uploader.image? ? 'inline' : 'attachment'
+ disposition = uploader.image_or_video? ? 'inline' : 'attachment'
send_file uploader.file.path, disposition: disposition
end
@@ -49,7 +49,7 @@ class Projects::UploadsController < Projects::ApplicationController
@uploader
end
- def image?
- uploader && uploader.file.exists? && uploader.image?
+ def image_or_video?
+ uploader && uploader.file.exists? && uploader.image_or_video?
end
end
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index ffac28f78a0..ea2f5f9281a 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -45,10 +45,10 @@ module CiStatusHelper
custom_icon(icon_name)
end
- def render_commit_status(commit, tooltip_placement: 'auto left', cssclass: '')
+ def render_commit_status(commit, tooltip_placement: 'auto left')
project = commit.project
path = builds_namespace_project_commit_path(project.namespace, project, commit)
- render_status_with_link('commit', commit.status, path, tooltip_placement, cssclass: cssclass)
+ render_status_with_link('commit', commit.status, path, tooltip_placement)
end
def render_pipeline_status(pipeline, tooltip_placement: 'auto left')
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 75b029365f9..4c031942793 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -54,18 +54,20 @@ module DiffHelper
end
end
- def organize_comments(left, right)
- notes_left = notes_right = nil
+ def parallel_diff_discussions(left, right, diff_file)
+ discussion_left = discussion_right = nil
- unless left[:type].nil? && right[:type] == 'new'
- notes_left = @grouped_diff_notes[left[:line_code]]
+ if left && (left.unchanged? || left.removed?)
+ line_code = diff_file.line_code(left)
+ discussion_left = @grouped_diff_discussions[line_code]
end
- unless left[:type].nil? && right[:type].nil?
- notes_right = @grouped_diff_notes[right[:line_code]]
+ if right && right.added?
+ line_code = diff_file.line_code(right)
+ discussion_right = @grouped_diff_discussions[line_code]
end
- [notes_left, notes_right]
+ [discussion_left, discussion_right]
end
def inline_diff_btn
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 98143dcee9b..0f60dd828ab 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -1,9 +1,4 @@
module NotesHelper
- # Helps to distinguish e.g. commit notes in mr notes list
- def note_for_main_target?(note)
- @noteable.class.name == note.noteable_type && !note.diff_note?
- end
-
def note_target_fields(note)
if note.noteable
hidden_field_tag(:target_type, note.noteable.class.name.underscore) +
@@ -44,8 +39,8 @@ module NotesHelper
# If we didn't, diff notes that would show for the same line on the changes
# tab, would show in different discussions on the discussion tab.
use_legacy_diff_note ||= begin
- line_diff_notes = @grouped_diff_notes[line_code]
- line_diff_notes && line_diff_notes.any?(&:legacy_diff_note?)
+ discussion = @grouped_diff_discussions[line_code]
+ discussion && discussion.legacy_diff_discussion?
end
data = {
@@ -81,22 +76,10 @@ module NotesHelper
data
end
- def link_to_reply_discussion(note, line_type = nil)
+ def link_to_reply_discussion(discussion, line_type = nil)
return unless current_user
- data = {
- noteable_type: note.noteable_type,
- noteable_id: note.noteable_id,
- commit_id: note.commit_id,
- discussion_id: note.discussion_id,
- line_type: line_type
- }
-
- if note.diff_note?
- data[:note_type] = note.type
-
- data.merge!(note.diff_attributes)
- end
+ data = discussion.reply_attributes.merge(line_type: line_type)
content_tag(:div, class: "discussion-reply-holder") do
button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button',
@@ -114,13 +97,13 @@ module NotesHelper
@max_access_by_user_id[full_key]
end
- def diff_note_path(note)
- return unless note.diff_note?
+ def discussion_diff_path(discussion)
+ return unless discussion.diff_discussion?
- if note.for_merge_request? && note.active?
- diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code)
- elsif note.for_commit?
- namespace_project_commit_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code)
+ if discussion.for_merge_request? && discussion.active?
+ diffs_namespace_project_merge_request_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: discussion.line_code)
+ elsif discussion.for_commit?
+ namespace_project_commit_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: discussion.line_code)
end
end
end
diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb
index 1926f03af07..790001222f1 100644
--- a/app/helpers/time_helper.rb
+++ b/app/helpers/time_helper.rb
@@ -1,5 +1,6 @@
module TimeHelper
def time_interval_in_words(interval_in_seconds)
+ interval_in_seconds = interval_in_seconds.to_i
minutes = interval_in_seconds / 60
seconds = interval_in_seconds - minutes * 60
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 58f25b20acf..cbfa14e81f1 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -12,7 +12,7 @@ module Ci
scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) }
- scope :with_artifacts, ->() { where.not(artifacts_file: nil) }
+ scope :with_artifacts, ->() { where.not(artifacts_file: [nil, '']) }
scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) }
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
scope :manual_actions, ->() { where(when: :manual) }
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 6d3bbb03484..bce6a992af6 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -20,6 +20,11 @@ module Ci
after_touch :update_state
after_save :keep_around_commits
+ # ref can't be HEAD or SHA, can only be branch/tag name
+ scope :latest_successful_for, ->(ref = default_branch) do
+ where(ref: ref).success.order(id: :desc).limit(1)
+ end
+
def self.truncate_sha(sha)
sha[0...8]
end
@@ -237,7 +242,7 @@ module Ci
def keep_around_commits
return unless project
-
+
project.repository.keep_around(self.sha)
project.repository.keep_around(self.before_sha)
end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 535db26240a..2d185c28809 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -16,7 +16,11 @@ class CommitStatus < ActiveRecord::Base
alias_attribute :author, :user
- scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :commit_id)) }
+ scope :latest, -> do
+ max_id = unscope(:select).select("max(#{quoted_table_name}.id)")
+
+ where(id: max_id.group(:name, :commit_id))
+ end
scope :retried, -> { where.not(id: latest) }
scope :ordered, -> { order(:name) }
scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
diff --git a/app/models/concerns/note_on_diff.rb b/app/models/concerns/note_on_diff.rb
index 2785fbb21c9..4be6a2f621b 100644
--- a/app/models/concerns/note_on_diff.rb
+++ b/app/models/concerns/note_on_diff.rb
@@ -1,12 +1,6 @@
module NoteOnDiff
extend ActiveSupport::Concern
- NUMBER_OF_TRUNCATED_DIFF_LINES = 16
-
- included do
- delegate :blob, :highlighted_diff_lines, to: :diff_file, allow_nil: true
- end
-
def diff_note?
true
end
@@ -30,23 +24,4 @@ module NoteOnDiff
def can_be_award_emoji?
false
end
-
- # Returns an array of at most 16 highlighted lines above a diff note
- def truncated_diff_lines
- prev_lines = []
-
- highlighted_diff_lines.each do |line|
- if line.meta?
- prev_lines.clear
- else
- prev_lines << line
-
- break if for_line?(line)
-
- prev_lines.shift if prev_lines.length >= NUMBER_OF_TRUNCATED_DIFF_LINES
- end
- end
-
- prev_lines
- end
end
diff --git a/app/models/discussion.rb b/app/models/discussion.rb
new file mode 100644
index 00000000000..74facfd1c9c
--- /dev/null
+++ b/app/models/discussion.rb
@@ -0,0 +1,91 @@
+class Discussion
+ NUMBER_OF_TRUNCATED_DIFF_LINES = 16
+
+ attr_reader :first_note, :notes
+
+ delegate :created_at,
+ :project,
+ :author,
+
+ :noteable,
+ :for_commit?,
+ :for_merge_request?,
+
+ :line_code,
+ :diff_file,
+ :for_line?,
+ :active?,
+
+ to: :first_note
+
+ delegate :blob, :highlighted_diff_lines, to: :diff_file, allow_nil: true
+
+ def self.for_notes(notes)
+ notes.group_by(&:discussion_id).values.map { |notes| new(notes) }
+ end
+
+ def self.for_diff_notes(notes)
+ notes.group_by(&:line_code).values.map { |notes| new(notes) }
+ end
+
+ def initialize(notes)
+ @first_note = notes.first
+ @notes = notes
+ end
+
+ def id
+ first_note.discussion_id
+ end
+
+ def diff_discussion?
+ first_note.diff_note?
+ end
+
+ def legacy_diff_discussion?
+ notes.any?(&:legacy_diff_note?)
+ end
+
+ def for_target?(target)
+ self.noteable == target && !diff_discussion?
+ end
+
+ def expanded?
+ !diff_discussion? || active?
+ end
+
+ def reply_attributes
+ data = {
+ noteable_type: first_note.noteable_type,
+ noteable_id: first_note.noteable_id,
+ commit_id: first_note.commit_id,
+ discussion_id: self.id,
+ }
+
+ if diff_discussion?
+ data[:note_type] = first_note.type
+
+ data.merge!(first_note.diff_attributes)
+ end
+
+ data
+ end
+
+ # Returns an array of at most 16 highlighted lines above a diff note
+ def truncated_diff_lines
+ prev_lines = []
+
+ highlighted_diff_lines.each do |line|
+ if line.meta?
+ prev_lines.clear
+ else
+ prev_lines << line
+
+ break if for_line?(line)
+
+ prev_lines.shift if prev_lines.length >= NUMBER_OF_TRUNCATED_DIFF_LINES
+ end
+ end
+
+ prev_lines
+ end
+end
diff --git a/app/models/note.rb b/app/models/note.rb
index 0ce10c77de9..9b0a7211b4e 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -82,11 +82,12 @@ class Note < ActiveRecord::Base
end
def discussions
- all.group_by(&:discussion_id).values
+ Discussion.for_notes(all)
end
- def grouped_diff_notes
- diff_notes.select(&:active?).sort_by(&:created_at).group_by(&:line_code)
+ def grouped_diff_discussions
+ notes = diff_notes.fresh.select(&:active?)
+ Discussion.for_diff_notes(notes).map { |d| [d.line_code, d] }.to_h
end
# Searches for notes matching the given query.
diff --git a/app/models/project.rb b/app/models/project.rb
index d6191e80e62..f09d915f20e 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -429,6 +429,17 @@ class Project < ActiveRecord::Base
repository.commit(ref)
end
+ # ref can't be HEAD, can only be branch/tag name or SHA
+ def latest_successful_builds_for(ref = default_branch)
+ pipeline = pipelines.latest_successful_for(ref).to_sql
+ join_sql = "INNER JOIN (#{pipeline}) pipelines" +
+ " ON pipelines.id = #{Ci::Build.quoted_table_name}.commit_id"
+ builds.joins(join_sql).latest.with_artifacts
+ # TODO: Whenever we dropped support for MySQL, we could change to:
+ # pipeline = pipelines.latest_successful_for(ref)
+ # builds.where(pipeline: pipeline).latest.with_artifacts
+ end
+
def merge_base_commit(first_commit_id, second_commit_id)
sha = repository.merge_base(first_commit_id, second_commit_id)
repository.commit(sha) if sha
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 511df2d67c6..46a04eb80cd 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -11,16 +11,6 @@ class Repository
attr_accessor :path_with_namespace, :project
- def self.clean_old_archives
- Gitlab::Metrics.measure(:clean_old_archives) do
- repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
-
- return unless File.directory?(repository_downloads_path)
-
- Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
- end
- end
-
def initialize(path_with_namespace, project)
@path_with_namespace = path_with_namespace
@project = project
@@ -80,7 +70,12 @@ class Repository
def commit(ref = 'HEAD')
return nil unless exists?
- commit = Gitlab::Git::Commit.find(raw_repository, ref)
+ commit =
+ if ref.is_a?(Gitlab::Git::Commit)
+ ref
+ else
+ Gitlab::Git::Commit.find(raw_repository, ref)
+ end
commit = ::Commit.new(commit, @project) if commit
commit
rescue Rugged::OdbError
@@ -257,10 +252,10 @@ class Repository
# Rugged seems to throw a `ReferenceError` when given branch_names rather
# than SHA-1 hashes
number_commits_behind = raw_repository.
- count_commits_between(branch.target, root_ref_hash)
+ count_commits_between(branch.target.sha, root_ref_hash)
number_commits_ahead = raw_repository.
- count_commits_between(root_ref_hash, branch.target)
+ count_commits_between(root_ref_hash, branch.target.sha)
{ behind: number_commits_behind, ahead: number_commits_ahead }
end
@@ -684,9 +679,7 @@ class Repository
end
def local_branches
- @local_branches ||= rugged.branches.each(:local).map do |branch|
- Gitlab::Git::Branch.new(branch.name, branch.target)
- end
+ @local_branches ||= raw_repository.local_branches
end
alias_method :branches, :local_branches
@@ -827,7 +820,7 @@ class Repository
end
def revert(user, commit, base_branch, revert_tree_id = nil)
- source_sha = find_branch(base_branch).target
+ source_sha = find_branch(base_branch).target.sha
revert_tree_id ||= check_revert_content(commit, base_branch)
return false unless revert_tree_id
@@ -844,7 +837,7 @@ class Repository
end
def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil)
- source_sha = find_branch(base_branch).target
+ source_sha = find_branch(base_branch).target.sha
cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch)
return false unless cherry_pick_tree_id
@@ -865,7 +858,7 @@ class Repository
end
def check_revert_content(commit, base_branch)
- source_sha = find_branch(base_branch).target
+ source_sha = find_branch(base_branch).target.sha
args = [commit.id, source_sha]
args << { mainline: 1 } if commit.merge_commit?
@@ -879,7 +872,7 @@ class Repository
end
def check_cherry_pick_content(commit, base_branch)
- source_sha = find_branch(base_branch).target
+ source_sha = find_branch(base_branch).target.sha
args = [commit.id, source_sha]
args << 1 if commit.merge_commit?
@@ -1044,7 +1037,7 @@ class Repository
end
def tags_sorted_by_committed_date
- tags.sort_by { |tag| commit(tag.target).committed_date }
+ tags.sort_by { |tag| tag.target.committed_date }
end
def keep_around_ref_name(sha)
diff --git a/app/models/service.rb b/app/models/service.rb
index a8e1cc2f422..40cd9b861f0 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -26,7 +26,7 @@ class Service < ActiveRecord::Base
scope :visible, -> { where.not(type: ['GitlabIssueTrackerService', 'GitlabCiService']) }
scope :issue_trackers, -> { where(category: 'issue_tracker') }
- scope :external_wikis, -> { where(type: 'ExternalWikiService') }
+ scope :external_wikis, -> { where(type: 'ExternalWikiService').active }
scope :active, -> { where(active: true) }
scope :without_defaults, -> { where(default: false) }
diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb
index 332c55581a1..87f066edb6f 100644
--- a/app/services/delete_branch_service.rb
+++ b/app/services/delete_branch_service.rb
@@ -40,6 +40,6 @@ class DeleteBranchService < BaseService
def build_push_data(branch)
Gitlab::PushDataBuilder
- .build(project, current_user, branch.target, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", [])
+ .build(project, current_user, branch.target.sha, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", [])
end
end
diff --git a/app/services/delete_tag_service.rb b/app/services/delete_tag_service.rb
index 1e41fbe34b6..32e0eed6b63 100644
--- a/app/services/delete_tag_service.rb
+++ b/app/services/delete_tag_service.rb
@@ -34,6 +34,6 @@ class DeleteTagService < BaseService
def build_push_data(tag)
Gitlab::PushDataBuilder
- .build(project, current_user, tag.target, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", [])
+ .build(project, current_user, tag.target.sha, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", [])
end
end
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
index 58573078048..969530c4fdc 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -26,8 +26,8 @@ class GitTagPushService < BaseService
unless Gitlab::Git.blank_ref?(params[:newrev])
tag_name = Gitlab::Git.ref_name(params[:ref])
tag = project.repository.find_tag(tag_name)
-
- if tag && tag.target == params[:newrev]
+
+ if tag && tag.object_sha == params[:newrev]
commit = project.commit(tag.target)
commits = [commit].compact
message = tag.message
diff --git a/app/services/repository_archive_clean_up_service.rb b/app/services/repository_archive_clean_up_service.rb
new file mode 100644
index 00000000000..0b56b09738d
--- /dev/null
+++ b/app/services/repository_archive_clean_up_service.rb
@@ -0,0 +1,33 @@
+class RepositoryArchiveCleanUpService
+ LAST_MODIFIED_TIME_IN_MINUTES = 120
+
+ def initialize(mmin = LAST_MODIFIED_TIME_IN_MINUTES)
+ @mmin = mmin
+ @path = Gitlab.config.gitlab.repository_downloads_path
+ end
+
+ def execute
+ Gitlab::Metrics.measure(:repository_archive_clean_up) do
+ return unless File.directory?(path)
+
+ clean_up_old_archives
+ clean_up_empty_directories
+ end
+ end
+
+ private
+
+ attr_reader :mmin, :path
+
+ def clean_up_old_archives
+ run(%W(find #{path} -not -path #{path} -type f \( -name \*.tar -o -name \*.bz2 -o -name \*.tar.gz -o -name \*.zip \) -maxdepth 2 -mmin +#{mmin} -delete))
+ end
+
+ def clean_up_empty_directories
+ run(%W(find #{path} -not -path #{path} -type d -empty -name \*.git -maxdepth 1 -delete))
+ end
+
+ def run(cmd)
+ Gitlab::Popen.popen(cmd)
+ end
+end
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index 1af9e9b0edb..2f5f49f7de7 100644
--- a/app/uploaders/file_uploader.rb
+++ b/app/uploaders/file_uploader.rb
@@ -33,16 +33,15 @@ class FileUploader < CarrierWave::Uploader::Base
end
def to_h
- filename = image? ? self.file.basename : self.file.filename
+ filename = image_or_video? ? self.file.basename : self.file.filename
escaped_filename = filename.gsub("]", "\\]")
markdown = "[#{escaped_filename}](#{self.secure_url})"
- markdown.prepend("!") if image?
+ markdown.prepend("!") if image_or_video?
{
alt: filename,
url: self.secure_url,
- is_image: image?,
markdown: markdown
}
end
diff --git a/app/uploaders/uploader_helper.rb b/app/uploaders/uploader_helper.rb
index 5ef440f3367..b10ad71d052 100644
--- a/app/uploaders/uploader_helper.rb
+++ b/app/uploaders/uploader_helper.rb
@@ -1,16 +1,37 @@
# Extra methods for uploader
module UploaderHelper
+ IMAGE_EXT = %w[png jpg jpeg gif bmp tiff]
+ # We recommend using the .mp4 format over .mov. Videos in .mov format can
+ # still be used but you really need to make sure they are served with the
+ # proper MIME type video/mp4 and not video/quicktime or your videos won't play
+ # on IE >= 9.
+ # http://archive.sublimevideo.info/20150912/docs.sublimevideo.net/troubleshooting.html
+ VIDEO_EXT = %w[mp4 m4v mov webm ogv]
+
def image?
- img_ext = %w(png jpg jpeg gif bmp tiff)
- if file.respond_to?(:extension)
- img_ext.include?(file.extension.downcase)
- else
- # Not all CarrierWave storages respond to :extension
- ext = file.path.split('.').last.downcase
- img_ext.include?(ext)
- end
- rescue
- false
+ extension_match?(IMAGE_EXT)
+ end
+
+ def video?
+ extension_match?(VIDEO_EXT)
+ end
+
+ def image_or_video?
+ image? || video?
+ end
+
+ def extension_match?(extensions)
+ return false unless file
+
+ extension =
+ if file.respond_to?(:extension)
+ file.extension
+ else
+ # Not all CarrierWave storages respond to :extension
+ File.extname(file.path).delete('.')
+ end
+
+ extensions.include?(extension.downcase)
end
def file_storage?
diff --git a/app/views/discussions/_diff_discussion.html.haml b/app/views/discussions/_diff_discussion.html.haml
new file mode 100644
index 00000000000..fa1ad9efa73
--- /dev/null
+++ b/app/views/discussions/_diff_discussion.html.haml
@@ -0,0 +1,6 @@
+%tr.notes_holder
+ %td.notes_line{ colspan: 2 }
+ %td.notes_content
+ %ul.notes{ data: { discussion_id: discussion.id } }
+ = render partial: "projects/notes/note", collection: discussion.notes, as: :note
+ = link_to_reply_discussion(discussion)
diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml
new file mode 100644
index 00000000000..02b159ffd45
--- /dev/null
+++ b/app/views/discussions/_diff_with_notes.html.haml
@@ -0,0 +1,14 @@
+- diff_file = discussion.diff_file
+- blob = discussion.blob
+
+.diff-file.file-holder
+ .file-title
+ = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_file.content_commit, project: discussion.project, url: discussion_diff_path(discussion)
+
+ .diff-content.code.js-syntax-highlight
+ %table
+ - discussion.truncated_diff_lines.each do |line|
+ = render "projects/diffs/line", line: line, diff_file: diff_file, plain: true
+
+ - if discussion.for_line?(line)
+ = render "discussions/diff_discussion", discussion: discussion
diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml
new file mode 100644
index 00000000000..49702e048aa
--- /dev/null
+++ b/app/views/discussions/_discussion.html.haml
@@ -0,0 +1,45 @@
+- expanded = discussion.expanded?
+%li.note.note-discussion.timeline-entry
+ .timeline-entry-inner
+ .timeline-icon
+ = link_to user_path(discussion.author) do
+ = image_tag avatar_icon(discussion.author), class: "avatar s40"
+ .timeline-content
+ .discussion.js-toggle-container{ class: discussion.id }
+ .discussion-header
+ = link_to_member(@project, discussion.author, avatar: false)
+
+ .inline.discussion-headline-light
+ = discussion.author.to_reference
+ started a discussion on
+
+ - if discussion.for_commit?
+ - commit = discussion.noteable
+ - if commit
+ commit
+ = link_to commit.short_id, namespace_project_commit_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: discussion.line_code), class: 'monospace'
+ - else
+ a deleted commit
+ - else
+ - if discussion.active?
+ = link_to diffs_namespace_project_merge_request_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: discussion.line_code) do
+ the diff
+ - else
+ an outdated diff
+
+ = time_ago_with_tooltip(discussion.created_at, placement: "bottom", html_class: "note-created-ago")
+
+ .discussion-actions
+ = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
+ - if expanded
+ = icon("chevron-up")
+ - else
+ = icon("chevron-down")
+
+ Toggle discussion
+
+ .discussion-body.js-toggle-content{ class: ("hide" unless expanded) }
+ - if discussion.diff_discussion? && discussion.diff_file
+ = render "discussions/diff_with_notes", discussion: discussion
+ - else
+ = render "discussions/notes", discussion: discussion
diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml
new file mode 100644
index 00000000000..a2642b839f6
--- /dev/null
+++ b/app/views/discussions/_notes.html.haml
@@ -0,0 +1,5 @@
+.panel.panel-default
+ .notes{ data: { discussion_id: discussion.id } }
+ %ul.notes.timeline
+ = render partial: "projects/notes/note", collection: discussion.notes, as: :note
+ = link_to_reply_discussion(discussion)
diff --git a/app/views/discussions/_parallel_diff_discussion.html.haml b/app/views/discussions/_parallel_diff_discussion.html.haml
new file mode 100644
index 00000000000..a798c438ea0
--- /dev/null
+++ b/app/views/discussions/_parallel_diff_discussion.html.haml
@@ -0,0 +1,22 @@
+%tr.notes_holder
+ - if discussion_left
+ %td.notes_line.old
+ %td.notes_content.parallel.old
+ %ul.notes{ data: { discussion_id: discussion_left.id } }
+ = render partial: "projects/notes/note", collection: discussion_left.notes, as: :note
+
+ = link_to_reply_discussion(discussion_left, 'old')
+ - else
+ %td.notes_line.old= ""
+ %td.notes_content.parallel.old= ""
+
+ - if discussion_right
+ %td.notes_line.new
+ %td.notes_content.parallel.new
+ %ul.notes{ data: { discussion_id: discussion_right.id } }
+ = render partial: "projects/notes/note", collection: discussion_right.notes, as: :note
+
+ = link_to_reply_discussion(discussion_right, 'new')
+ - else
+ %td.notes_line.new= ""
+ %td.notes_content.parallel.new= ""
diff --git a/app/views/events/_event_scope.html.haml b/app/views/events/_event_scope.html.haml
new file mode 100644
index 00000000000..8f7da7d8c4f
--- /dev/null
+++ b/app/views/events/_event_scope.html.haml
@@ -0,0 +1,7 @@
+%span.event-scope
+ = event_preposition(event)
+ - if event.project
+ = link_to_project event.project
+ - else
+ = event.project_name
+
diff --git a/app/views/events/event/_common.html.haml b/app/views/events/event/_common.html.haml
index 2e2403347c1..bba6e0d2c20 100644
--- a/app/views/events/event/_common.html.haml
+++ b/app/views/events/event/_common.html.haml
@@ -1,6 +1,6 @@
.event-title
%span.author_name= link_to_author event
- %span.event_label{class: event.action_name}
+ %span{class: event.action_name}
- if event.target
= event.action_name
%strong
@@ -10,12 +10,7 @@
- else
= event_action_name(event)
- = event_preposition(event)
-
- - if event.project
- = link_to_project event.project
- - else
- = event.project_name
+ = render "events/event_scope", event: event
- if event.target.respond_to?(:title)
.event-body
diff --git a/app/views/events/event/_created_project.html.haml b/app/views/events/event/_created_project.html.haml
index 5a2a469ba62..aba64dd17d0 100644
--- a/app/views/events/event/_created_project.html.haml
+++ b/app/views/events/event/_created_project.html.haml
@@ -1,6 +1,6 @@
.event-title
%span.author_name= link_to_author event
- %span.event_label{class: event.action_name}
+ %span{class: event.action_name}
= event_action_name(event)
- if event.project
diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml
index 830fec0b4ab..f08c96df309 100644
--- a/app/views/events/event/_note.html.haml
+++ b/app/views/events/event/_note.html.haml
@@ -1,14 +1,9 @@
.event-title
%span.author_name= link_to_author event
- %span.event_label
- = event.action_name
- = event_note_title_html(event)
- at
+ = event.action_name
+ = event_note_title_html(event)
- - if event.project
- = link_to_project event.project
- - else
- = event.project_name
+ = render "events/event_scope", event: event
.event-body
.event-note
diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml
index ea54ef226ec..44fff49d99c 100644
--- a/app/views/events/event/_push.html.haml
+++ b/app/views/events/event/_push.html.haml
@@ -2,14 +2,14 @@
.event-title
%span.author_name= link_to_author event
- %span.event_label.pushed #{event.action_name} #{event.ref_type}
+ %span.pushed #{event.action_name} #{event.ref_type}
- if event.rm_ref?
%strong= event.ref_name
- else
%strong
= link_to event.ref_name, namespace_project_commits_path(project.namespace, project, event.ref_name), title: h(event.target_title)
- at
- = link_to_project project
+
+ = render "events/event_scope", event: event
- if event.push_with_commits?
.event-body
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 21668698814..3a14751ea8e 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -30,7 +30,7 @@
%span
Merge Requests
%span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count)
- = nav_link(controller: :snippets) do
+ = nav_link(controller: 'dashboard/snippets') do
= link_to dashboard_snippets_path, title: 'Snippets' do
%span
Snippets
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index 2049b204956..d03d5e2ca6a 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -6,7 +6,7 @@
- content_for :scripts_body_top do
- project = @target_project || @project
- if @project_wiki && @page
- - markdown_preview_path = namespace_project_wiki_markdown_preview_path(project.namespace, project, params[:id])
+ - markdown_preview_path = namespace_project_wiki_markdown_preview_path(project.namespace, project, @page.title)
- else
- markdown_preview_path = markdown_preview_namespace_project_path(project.namespace, project)
- if current_user
diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml
index 48b0dd6b121..ac50ce83f6a 100644
--- a/app/views/projects/_activity.html.haml
+++ b/app/views/projects/_activity.html.haml
@@ -5,7 +5,8 @@
%i.fa.fa-rss
= render 'shared/event_filter'
-.content_list{:"data-href" => activity_project_path(@project)}
+
+.content_list.project-activity{:"data-href" => activity_project_path(@project)}
= spinner
:javascript
diff --git a/app/views/projects/branches/_commit.html.haml b/app/views/projects/branches/_commit.html.haml
index 9fe65cbb104..d54c76ff9c8 100644
--- a/app/views/projects/branches/_commit.html.haml
+++ b/app/views/projects/branches/_commit.html.haml
@@ -1,5 +1,5 @@
.branch-commit
- = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-id monospace"
+ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-id monospace"
&middot;
%span.str-truncated
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 9264289987d..a9fb3c58431 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -14,16 +14,19 @@
%span ##{build.id}
- if build.stuck?
- = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.')
+ .icon-container
+ = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.')
- if defined?(retried) && retried
- = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.')
+ .icon-container
+ = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.')
- if defined?(ref) && ref
- if build.ref
= link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name"
- else
.light none
- = custom_icon("icon_commit")
+ .icon-container
+ = custom_icon("icon_commit")
- if defined?(commit_sha) && commit_sha
= link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace"
@@ -88,4 +91,3 @@
- elsif build.playable?
= link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
= icon('play')
-
diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml
index 41fd5459429..540689f4a61 100644
--- a/app/views/projects/commit/_pipeline.html.haml
+++ b/app/views/projects/commit/_pipeline.html.haml
@@ -35,8 +35,8 @@
.bs-callout.bs-callout-warning
\.gitlab-ci.yml not found in this commit
-.table-holder
- %table.table.builds
+.table-holder.pipeline-holder
+ %table.table.builds.pipeline
%thead
%tr
%th Status
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index ab9afb06afb..fd888f41b1e 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -19,13 +19,14 @@
&middot;
= commit.short_id
- if commit.status
- = render_commit_status(commit, cssclass: 'visible-xs-inline')
+ .visible-xs-inline
+ = render_commit_status(commit)
- 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')
+ = render_commit_status(commit)
= clipboard_button(clipboard_text: commit.id)
= 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)
diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml
index 65d68aa2985..f70dba224fa 100644
--- a/app/views/projects/deployments/_actions.haml
+++ b/app/views/projects/deployments/_actions.haml
@@ -17,6 +17,6 @@
- if local_assigns.fetch(:allow_rollback, false)
= link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build' do
- if deployment.last?
- Retry
+ Re-deploy
- else
Rollback
diff --git a/app/views/projects/diffs/_match_line_parallel.html.haml b/app/views/projects/diffs/_match_line_parallel.html.haml
deleted file mode 100644
index b9c0d9dcdfd..00000000000
--- a/app/views/projects/diffs/_match_line_parallel.html.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-%td.old_line.diff-line-num.empty-cell
-%td.line_content.parallel.match= line
-%td.new_line.diff-line-num.empty-cell
-%td.line_content.parallel.match= line
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index d208fcee10b..7f30faa20d8 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -5,32 +5,35 @@
- left = line[:left]
- right = line[:right]
%tr.line_holder.parallel
- - if left[:type] == 'match'
- = render "projects/diffs/match_line_parallel", { line: left[:text] }
- - elsif left[:type] == 'nonewline'
- %td.old_line.diff-line-num.empty-cell
- %td.line_content.parallel.match= left[:text]
- %td.new_line.diff-line-num.empty-cell
- %td.line_content.parallel.match= left[:text]
+ - if left
+ - if left.meta?
+ %td.old_line.diff-line-num.empty-cell
+ %td.line_content.parallel.match= left.text
+ - else
+ - left_line_code = diff_file.line_code(left)
+ - left_position = diff_file.position(left)
+ %td.old_line.diff-line-num{id: left_line_code, class: left.type, data: { linenumber: left.old_pos }}
+ %a{href: "##{left_line_code}" }= raw(left.old_pos)
+ %td.line_content.parallel.noteable_line{class: left.type, data: diff_view_line_data(left_line_code, left_position, 'old')}= diff_line_content(left.text)
- else
- %td.old_line.diff-line-num{id: left[:line_code], class: [left[:type], ('empty-cell' unless left[:number])], data: { linenumber: left[:number] }}
- %a{href: "##{left[:line_code]}" }= raw(left[:number])
- %td.line_content.parallel.noteable_line{class: [left[:type], ('empty-cell' if left[:text].empty?)], data: diff_view_line_data(left[:line_code], left[:position], 'old')}= diff_line_content(left[:text])
+ %td.old_line.diff-line-num.empty-cell
+ %td.line_content.parallel
- - if right[:type] == 'new'
- - new_line_type = 'new'
- - new_line_code = right[:line_code]
- - new_position = right[:position]
+ - if right
+ - if right.meta?
+ %td.old_line.diff-line-num.empty-cell
+ %td.line_content.parallel.match= left.text
- else
- - new_line_type = nil
- - new_line_code = left[:line_code]
- - new_position = left[:position]
-
- %td.new_line.diff-line-num{id: new_line_code, class: [new_line_type, ('empty-cell' unless right[:number])], data: { linenumber: right[:number] }}
- %a{href: "##{new_line_code}" }= raw(right[:number])
- %td.line_content.parallel.noteable_line{class: [new_line_type, ('empty-cell' if right[:text].empty?)], data: diff_view_line_data(new_line_code, new_position, 'new')}= diff_line_content(right[:text])
+ - right_line_code = diff_file.line_code(right)
+ - right_position = diff_file.position(right)
+ %td.new_line.diff-line-num{id: right_line_code, class: right.type, data: { linenumber: right.new_pos }}
+ %a{href: "##{right_line_code}" }= raw(right.new_pos)
+ %td.line_content.parallel.noteable_line{class: right.type, data: diff_view_line_data(right_line_code, right_position, 'new')}= diff_line_content(right.text)
+ - else
+ %td.old_line.diff-line-num.empty-cell
+ %td.line_content.parallel
- unless @diff_notes_disabled
- - notes_left, notes_right = organize_comments(left, right)
- - if notes_left.present? || notes_right.present?
- = render "projects/notes/diff_notes_with_reply_parallel", notes_left: notes_left, notes_right: notes_right
+ - discussion_left, discussion_right = parallel_diff_discussions(left, right, diff_file)
+ - if discussion_left || discussion_right
+ = render "discussions/parallel_diff_discussion", discussion_left: discussion_left, discussion_right: discussion_right
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index 196f8122db3..5970b9abf2b 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -11,9 +11,9 @@
- unless @diff_notes_disabled
- line_code = diff_file.line_code(line)
- - diff_notes = @grouped_diff_notes[line_code] if line_code
- - if diff_notes
- = render "projects/notes/diff_notes_with_reply", notes: diff_notes
+ - discussion = @grouped_diff_discussions[line_code] if line_code
+ - if discussion
+ = render "discussions/diff_discussion", discussion: discussion
- if last_line > 0
= render "projects/diffs/match_line", { line: "",
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index c72d0140bb9..facdfcc9447 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -89,9 +89,9 @@
= link_to "#", class: 'btn js-toggle-button import_git' do
%i.fa.fa-git
%span Repo by URL
- %div
+ %div{ class: 'import_gitlab_project' }
- if gitlab_project_import_enabled?
- = link_to new_import_gitlab_project_path, class: 'btn import_gitlab_project project-submit' do
+ = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do
%i.fa.fa-gitlab
%span GitLab export
@@ -130,29 +130,29 @@
$(".modal").hide();
});
- $('.import_gitlab_project').bind('click', function() {
- var _href = $("a.import_gitlab_project").attr("href");
- $(".import_gitlab_project").attr("href", _href + '?namespace_id=' + $("#project_namespace_id").val() + '&path=' + $("#project_path").val());
+ $('.btn_import_gitlab_project').bind('click', function() {
+ var _href = $("a.btn_import_gitlab_project").attr("href");
+ $(".btn_import_gitlab_project").attr("href", _href + '?namespace_id=' + $("#project_namespace_id").val() + '&path=' + $("#project_path").val());
});
- $('.import_gitlab_project').attr('disabled',true)
- $('.import_gitlab_project').attr('title', 'Project path required.');
+ $('.btn_import_gitlab_project').attr('disabled',true)
+ $('.import_gitlab_project').attr('title', 'Project path and name required.');
$('.import_gitlab_project').click(function( event ) {
- if($('.import_gitlab_project').attr('disabled')) {
+ if($('.btn_import_gitlab_project').attr('disabled')) {
event.preventDefault();
- new Flash("Please enter a path for the project to be imported to.");
+ new Flash("Please enter path and name for the project to be imported to.");
}
});
$('#project_path').keyup(function(){
if($(this).val().length !=0) {
- $('.import_gitlab_project').attr('disabled', false);
+ $('.btn_import_gitlab_project').attr('disabled', false);
$('.import_gitlab_project').attr('title','');
$(".flash-container").html("")
} else {
- $('.import_gitlab_project').attr('disabled',true);
- $('.import_gitlab_project').attr('title', 'Project path required.');
+ $('.btn_import_gitlab_project').attr('disabled',true);
+ $('.import_gitlab_project').attr('title', 'Project path and name required.');
}
});
diff --git a/app/views/projects/notes/_diff_notes_with_reply.html.haml b/app/views/projects/notes/_diff_notes_with_reply.html.haml
deleted file mode 100644
index ec6c4938efc..00000000000
--- a/app/views/projects/notes/_diff_notes_with_reply.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-- note = notes.first
-%tr.notes_holder
- %td.notes_line{ colspan: 2 }
- %td.notes_content
- %ul.notes{ data: { discussion_id: note.discussion_id } }
- = render partial: "projects/notes/note", collection: notes, as: :note
- = link_to_reply_discussion(note)
diff --git a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
deleted file mode 100644
index e50a4f86d03..00000000000
--- a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
+++ /dev/null
@@ -1,25 +0,0 @@
-- note_left = notes_left.present? ? notes_left.first : nil
-- note_right = notes_right.present? ? notes_right.first : nil
-
-%tr.notes_holder
- - if note_left
- %td.notes_line.old
- %td.notes_content.parallel.old
- %ul.notes{ data: { discussion_id: note_left.discussion_id } }
- = render partial: "projects/notes/note", collection: notes_left, as: :note
-
- = link_to_reply_discussion(note_left, 'old')
- - else
- %td.notes_line.old= ""
- %td.notes_content.parallel.old= ""
-
- - if note_right
- %td.notes_line.new
- %td.notes_content.parallel.new
- %ul.notes{ data: { discussion_id: note_right.discussion_id } }
- = render partial: "projects/notes/note", collection: notes_right, as: :note
-
- = link_to_reply_discussion(note_right, 'new')
- - else
- %td.notes_line.new= ""
- %td.notes_content.parallel.new= ""
diff --git a/app/views/projects/notes/_discussion.html.haml b/app/views/projects/notes/_discussion.html.haml
deleted file mode 100644
index 7869d6413d8..00000000000
--- a/app/views/projects/notes/_discussion.html.haml
+++ /dev/null
@@ -1,46 +0,0 @@
-- note = discussion_notes.first
-- expanded = !note.diff_note? || note.active?
-%li.note.note-discussion.timeline-entry
- .timeline-entry-inner
- .timeline-icon
- = link_to user_path(note.author) do
- = image_tag avatar_icon(note.author), class: "avatar s40"
- .timeline-content
- .discussion.js-toggle-container{ class: note.discussion_id }
- .discussion-header
- = link_to_member(@project, note.author, avatar: false)
-
- .inline.discussion-headline-light
- = note.author.to_reference
- started a discussion on
-
- - if note.for_commit?
- - commit = note.noteable
- - if commit
- commit
- = link_to commit.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code), class: 'monospace'
- - else
- a deleted commit
- - else
- - if note.active?
- = link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do
- the diff
- - else
- an outdated diff
-
- = time_ago_with_tooltip(note.created_at, placement: "bottom", html_class: "note-created-ago")
-
- .discussion-actions
- = link_to "#", class: "note-action-button discussion-toggle-button js-toggle-button" do
- - if expanded
- = icon("chevron-up")
- - else
- = icon("chevron-down")
-
- Toggle discussion
-
- .discussion-body.js-toggle-content{ class: ("hide" unless expanded) }
- - if note.diff_note?
- = render "projects/notes/discussions/diff_with_notes", discussion_notes: discussion_notes
- - else
- = render "projects/notes/discussions/notes", discussion_notes: discussion_notes
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index af0046886fb..71da8ac9d7c 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -30,7 +30,7 @@
= link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button hidden-xs js-note-delete danger' do
= icon('trash-o')
.note-body{class: note_editable ? 'js-task-list-container' : ''}
- .note-text
+ .note-text.md
= preserve do
= note.note_html
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
diff --git a/app/views/projects/notes/_notes.html.haml b/app/views/projects/notes/_notes.html.haml
index ebf7e8a9cb3..022578bd6db 100644
--- a/app/views/projects/notes/_notes.html.haml
+++ b/app/views/projects/notes/_notes.html.haml
@@ -1,10 +1,8 @@
- if @discussions.present?
- - @discussions.each do |discussion_notes|
- - note = discussion_notes.first
- - if note_for_main_target?(note)
- = render partial: "projects/notes/note", object: note, as: :note
+ - @discussions.each do |discussion|
+ - if discussion.for_target?(@noteable)
+ = render partial: "projects/notes/note", object: discussion.first_note, as: :note
- else
- = render 'projects/notes/discussion', discussion_notes: discussion_notes
+ = render 'discussions/discussion', discussion: discussion
- else
- - @notes.each do |note|
- = render partial: "projects/notes/note", object: note, as: :note
+ = render partial: "projects/notes/note", collection: @notes, as: :note
diff --git a/app/views/projects/notes/discussions/_diff_with_notes.html.haml b/app/views/projects/notes/discussions/_diff_with_notes.html.haml
deleted file mode 100644
index 4a69b8f8840..00000000000
--- a/app/views/projects/notes/discussions/_diff_with_notes.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-- note = discussion_notes.first
-- diff_file = note.diff_file
-- return unless diff_file
-
-- blob = note.blob
-
-.diff-file.file-holder
- .file-title
- = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_file.content_commit, project: note.project, url: diff_note_path(note)
-
- .diff-content.code.js-syntax-highlight
- %table
- - note.truncated_diff_lines.each do |line|
- = render "projects/diffs/line", line: line, diff_file: diff_file, plain: true
-
- - if note.for_line?(line)
- = render "projects/notes/diff_notes_with_reply", notes: discussion_notes
diff --git a/app/views/projects/notes/discussions/_notes.html.haml b/app/views/projects/notes/discussions/_notes.html.haml
deleted file mode 100644
index a785149549d..00000000000
--- a/app/views/projects/notes/discussions/_notes.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-- note = discussion_notes.first
-.panel.panel-default
- .notes{ data: { discussion_id: note.discussion_id } }
- %ul.notes.timeline
- = render partial: "projects/notes/note", collection: discussion_notes, as: :note
- = link_to_reply_discussion(note)
diff --git a/app/views/shared/icons/_icon_fork.svg b/app/views/shared/icons/_icon_fork.svg
index 420ffe3a55b..a21f8f3a951 100644
--- a/app/views/shared/icons/_icon_fork.svg
+++ b/app/views/shared/icons/_icon_fork.svg
@@ -1 +1,3 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><circle id="a" cx="4" cy="4" r="4"/><mask id="d" width="8" height="8" x="0" y="0" fill="#fff"><use xlink:href="#a"/></mask><circle id="b" cx="20" cy="4" r="4"/><mask id="e" width="8" height="8" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask><circle id="c" cx="12" cy="30" r="4"/><mask id="f" width="8" height="8" x="0" y="0" fill="#fff"><use xlink:href="#c"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(8 3)"><path fill="#7E7E7E" d="M10 19.667c-4.14-1.29-7.389-5.878-7.389-5.878C2.274 13.353 2 12.545 2 12.01V6h4v5.509c0 .276.166.65.367.831 0 0 1.136 1.028 1.746 1.574C9.617 15.261 11.048 16 12.09 16c1.028 0 2.41-.723 3.858-2.048.588-.54 1.84-1.742 1.84-1.742a.784.784 0 0 0 .211-.502V6h4v6.008c0 .548-.259 1.349-.601 1.795 0 0-3.21 4.707-7.399 5.916V27h-4v-7.333z"/><use stroke="#7E7E7E" stroke-width="4" mask="url(#d)" xlink:href="#a"/><use stroke="#7E7E7E" stroke-width="4" mask="url(#e)" xlink:href="#b"/><use stroke="#7E7E7E" stroke-width="4" mask="url(#f)" xlink:href="#c"/></g></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40">
+ <path fill="#7E7E7E" fill-rule="evenodd" d="M22,29.5351288 L22,22.7193602 C26.1888699,21.5098039 29.3985457,16.802989 29.3985457,16.802989 C29.740988,16.3567547 30,15.5559546 30,15.0081969 L30,10.4648712 C31.1956027,9.77325238 32,8.48056471 32,7 C32,4.790861 30.209139,3 28,3 C25.790861,3 24,4.790861 24,7 C24,8.48056471 24.8043973,9.77325238 26,10.4648712 L26,14.7083871 C26,14.8784435 25.9055559,15.0987329 25.7890533,15.2104147 C25.7890533,15.2104147 24.5373893,16.4126202 23.9488702,16.9515733 C22.5015398,18.2770075 21.1191354,19 20.090554,19 C19.0477772,19 17.6172728,18.2608988 16.1128852,16.9142923 C15.5030182,16.3683886 14.3672121,15.3403307 14.3672121,15.3403307 C14.1659605,15.1583364 14.0000086,14.7846305 14.0000192,14.5088473 C14.0000192,14.5088473 14.0000932,12.7539451 14.0001308,10.4647956 C15.1956614,9.77315812 16,8.48051074 16,7 C16,4.790861 14.209139,3 12,3 C9.790861,3 8,4.790861 8,7 C8,8.48056471 8.80439726,9.77325238 10,10.4648712 L10,15.0081969 C10,15.5446944 10.2736352,16.3534183 10.6111812,16.7893819 C10.6111812,16.7893819 13.8599776,21.3779363 18,22.6668724 L18,29.5351288 C16.8043973,30.2267476 16,31.5194353 16,33 C16,35.209139 17.790861,37 20,37 C22.209139,37 24,35.209139 24,33 C24,31.5194353 23.1956027,30.2267476 22,29.5351288 Z M14,7 C14,5.8954305 13.1045695,5 12,5 C10.8954305,5 10,5.8954305 10,7 C10,8.1045695 10.8954305,9 12,9 C13.1045695,9 14,8.1045695 14,7 Z M30,7 C30,5.8954305 29.1045695,5 28,5 C26.8954305,5 26,5.8954305 26,7 C26,8.1045695 26.8954305,9 28,9 C29.1045695,9 30,8.1045695 30,7 Z M22,33 C22,31.8954305 21.1045695,31 20,31 C18.8954305,31 18,31.8954305 18,33 C18,34.1045695 18.8954305,35 20,35 C21.1045695,35 22,34.1045695 22,33 Z"/>
+</svg>
diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb
index 8551288e2f2..0b6a01a3200 100644
--- a/app/workers/emails_on_push_worker.rb
+++ b/app/workers/emails_on_push_worker.rb
@@ -28,12 +28,12 @@ class EmailsOnPushWorker
:push
end
- merge_base_sha = project.merge_base_commit(before_sha, after_sha).try(:sha)
-
diff_refs = nil
compare = nil
reverse_compare = false
+
if action == :push
+ merge_base_sha = project.merge_base_commit(before_sha, after_sha).try(:sha)
compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha)
diff_refs = Gitlab::Diff::DiffRefs.new(
diff --git a/app/workers/repository_archive_cache_worker.rb b/app/workers/repository_archive_cache_worker.rb
index 47c5a670ed4..a2e49c61f59 100644
--- a/app/workers/repository_archive_cache_worker.rb
+++ b/app/workers/repository_archive_cache_worker.rb
@@ -4,6 +4,6 @@ class RepositoryArchiveCacheWorker
sidekiq_options queue: :default
def perform
- Repository.clean_old_archives
+ RepositoryArchiveCleanUpService.new.execute
end
end
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 325eca72862..1470a6e2550 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -106,8 +106,8 @@ production: &base
## Repository downloads directory
# When a user clicks e.g. 'Download zip' on a project, a temporary zip file is created in the following directory.
- # The default is 'tmp/repositories' relative to the root of the Rails app.
- # repository_downloads_path: tmp/repositories
+ # The default is 'shared/cache/archive/' relative to the root of the Rails app.
+ # repository_downloads_path: shared/cache/archive/
## Reply by email
# Allow users to comment on issues and merge requests by replying to notification emails.
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 693507e0bec..86f55210487 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -211,7 +211,6 @@ Settings.gitlab.default_projects_features['snippets'] = false if Setti
Settings.gitlab.default_projects_features['builds'] = true if Settings.gitlab.default_projects_features['builds'].nil?
Settings.gitlab.default_projects_features['container_registry'] = true if Settings.gitlab.default_projects_features['container_registry'].nil?
Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
-Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive') if Settings.gitlab['repository_downloads_path'].nil?
Settings.gitlab['domain_whitelist'] ||= []
Settings.gitlab['import_sources'] ||= %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project]
Settings.gitlab['trusted_proxies'] ||= []
@@ -316,6 +315,21 @@ Settings.repositories['storages'] ||= {}
Settings.repositories.storages['default'] ||= Settings.gitlab_shell['repos_path'] || Settings.gitlab['user_home'] + '/repositories/'
#
+# The repository_downloads_path is used to remove outdated repository
+# archives, if someone has it configured incorrectly, and it points
+# to the path where repositories are stored this can cause some
+# data-integrity issue. In this case, we sets it to the default
+# repository_downloads_path value.
+#
+repositories_storages_path = Settings.repositories.storages.values
+repository_downloads_path = Settings.gitlab['repository_downloads_path'].to_s.gsub(/\/$/, '')
+repository_downloads_full_path = File.expand_path(repository_downloads_path, Settings.gitlab['user_home'])
+
+if repository_downloads_path.blank? || repositories_storages_path.any? { |path| [repository_downloads_path, repository_downloads_full_path].include?(path.gsub(/\/$/, '')) }
+ Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive')
+end
+
+#
# Backup
#
Settings['backup'] ||= Settingslogic.new({})
diff --git a/config/initializers/6_validations.rb b/config/initializers/6_validations.rb
index 3ba9e36c567..37746968675 100644
--- a/config/initializers/6_validations.rb
+++ b/config/initializers/6_validations.rb
@@ -3,22 +3,27 @@ def storage_name_valid?(name)
end
def find_parent_path(name, path)
+ parent = Pathname.new(path).realpath.parent
Gitlab.config.repositories.storages.detect do |n, p|
- name != n && path.chomp('/').start_with?(p.chomp('/'))
+ name != n && Pathname.new(p).realpath == parent
end
end
-def error(message)
+def storage_validation_error(message)
raise "#{message}. Please fix this in your gitlab.yml before starting GitLab."
end
-error('No repository storage path defined') if Gitlab.config.repositories.storages.empty?
+def validate_storages
+ storage_validation_error('No repository storage path defined') if Gitlab.config.repositories.storages.empty?
-Gitlab.config.repositories.storages.each do |name, path|
- error("\"#{name}\" is not a valid storage name") unless storage_name_valid?(name)
+ Gitlab.config.repositories.storages.each do |name, path|
+ storage_validation_error("\"#{name}\" is not a valid storage name") unless storage_name_valid?(name)
- parent_name, _parent_path = find_parent_path(name, path)
- if parent_name
- error("#{name} is a nested path of #{parent_name}. Nested paths are not supported for repository storages")
+ parent_name, _parent_path = find_parent_path(name, path)
+ if parent_name
+ storage_validation_error("#{name} is a nested path of #{parent_name}. Nested paths are not supported for repository storages")
+ end
end
end
+
+validate_storages unless Rails.env.test?
diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb
index ca58ae92d1b..3e553120205 100644
--- a/config/initializers/mime_types.rb
+++ b/config/initializers/mime_types.rb
@@ -6,5 +6,9 @@
Mime::Type.register_alias "text/plain", :diff
Mime::Type.register_alias "text/plain", :patch
-Mime::Type.register_alias 'text/html', :markdown
-Mime::Type.register_alias 'text/html', :md
+Mime::Type.register_alias "text/html", :markdown
+Mime::Type.register_alias "text/html", :md
+
+Mime::Type.register "video/mp4", :mp4, [], [:m4v, :mov]
+Mime::Type.register "video/webm", :webm
+Mime::Type.register "video/ogg", :ogv
diff --git a/config/initializers/secure_headers.rb b/config/initializers/secure_headers.rb
deleted file mode 100644
index 253e3cf7410..00000000000
--- a/config/initializers/secure_headers.rb
+++ /dev/null
@@ -1,99 +0,0 @@
-# CSP headers have to have single quotes, so failures relating to quotes
-# inside Ruby string arrays are irrelevant.
-# rubocop:disable Lint/PercentStringArray
-require 'gitlab/current_settings'
-include Gitlab::CurrentSettings
-
-CSP_REPORT_URI = ''
-
-# Content Security Policy Headers
-# For more information on CSP see:
-# - https://gitlab.com/gitlab-org/gitlab-ce/issues/18231
-# - https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives
-SecureHeaders::Configuration.default do |config|
- # Mark all cookies as "Secure", "HttpOnly", and "SameSite=Strict".
- config.cookies = {
- secure: true,
- httponly: true,
- samesite: {
- strict: true
- }
- }
- config.x_content_type_options = "nosniff"
- config.x_xss_protection = "1; mode=block"
- config.x_download_options = "noopen"
- config.x_permitted_cross_domain_policies = "none"
- config.referrer_policy = "origin-when-cross-origin"
- config.csp = {
- # "Meta" values.
- report_only: true,
- preserve_schemes: true,
-
- # "Directive" values.
- # Default source allows nothing, more permissive values are set per-policy.
- default_src: %w('none'),
- # (Deprecated) Don't allow iframes.
- frame_src: %w('none'),
- # Only allow XMLHTTPRequests from the GitLab instance itself.
- connect_src: %w('self'),
- # Only load local fonts.
- font_src: %w('self'),
- # Load local images, any external image available over HTTPS.
- img_src: %w(* 'self' data:),
- # Audio and video can't be played on GitLab currently, so it's disabled.
- media_src: %w('none'),
- # Don't allow <object>, <embed>, or <applet> elements.
- object_src: %w('none'),
- # Allow local scripts and inline scripts.
- script_src: %w('unsafe-inline' 'unsafe-eval' 'self'),
- # Allow local stylesheets and inline styles.
- style_src: %w('unsafe-inline' 'self'),
- # The URIs that a user agent may use as the document base URL.
- base_uri: %w('self'),
- # Only allow local iframes and service workers
- child_src: %w('self'),
- # Only submit form information to the GitLab instance.
- form_action: %w('self'),
- # Disallow any parents from embedding a page in an iframe.
- frame_ancestors: %w('none'),
- # Don't allow any plugins (Flash, Shockwave, etc.)
- plugin_types: %w(),
- # Blocks all mixed (HTTP) content.
- block_all_mixed_content: true,
- # Upgrades insecure requests to HTTPS when possible.
- upgrade_insecure_requests: true
- }
-
- config.csp[:report_uri] = %W(#{CSP_REPORT_URI})
-
- # Allow Bootstrap Linter in development mode.
- if Rails.env.development?
- config.csp[:script_src] << "maxcdn.bootstrapcdn.com"
- end
-
- # reCAPTCHA
- if current_application_settings.recaptcha_enabled
- config.csp[:script_src] << "https://www.google.com/recaptcha/"
- config.csp[:script_src] << "https://www.gstatic.com/recaptcha/"
- config.csp[:frame_src] << "https://www.google.com/recaptcha/"
- config.x_frame_options = "SAMEORIGIN"
- end
-
- # Gravatar
- if current_application_settings.gravatar_enabled?
- config.csp[:img_src] << "www.gravatar.com"
- config.csp[:img_src] << "secure.gravatar.com"
- config.csp[:img_src] << Gitlab.config.gravatar.host
- end
-
- # Piwik
- if Gitlab.config.extra.has_key?('piwik_url') && Gitlab.config.extra.has_key?('piwik_site_id')
- config.csp[:script_src] << Gitlab.config.extra.piwik_url
- config.csp[:img_src] << Gitlab.config.extra.piwik_url
- end
-
- # Google Analytics
- if Gitlab.config.extra.has_key?('google_analytics_id')
- config.csp[:script_src] << "https://www.google-analytics.com"
- end
-end
diff --git a/db/migrate/20160721081015_drop_and_readd_has_external_wiki_in_projects.rb b/db/migrate/20160721081015_drop_and_readd_has_external_wiki_in_projects.rb
new file mode 100644
index 00000000000..1eb99feb40c
--- /dev/null
+++ b/db/migrate/20160721081015_drop_and_readd_has_external_wiki_in_projects.rb
@@ -0,0 +1,15 @@
+class DropAndReaddHasExternalWikiInProjects < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def up
+ update_column_in_batches(:projects, :has_external_wiki, nil) do |table, query|
+ query.where(table[:has_external_wiki].not_eq(nil))
+ end
+ end
+
+ def down
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index c7876426424..d541e1cccb7 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: 20160718153603) do
+ActiveRecord::Schema.define(version: 20160721081015) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
diff --git a/doc/api/award_emoji.md b/doc/api/award_emoji.md
index b44f8cfd628..796b3680a75 100644
--- a/doc/api/award_emoji.md
+++ b/doc/api/award_emoji.md
@@ -67,9 +67,9 @@ Example Response:
]
```
-### Get single issue note
+### Get single award emoji
-Gets a single award emoji
+Gets a single award emoji from an issue or merge request.
```
GET /projects/:id/issues/:issue_id/award_emoji/:award_id
diff --git a/doc/api/projects.md b/doc/api/projects.md
index dceee7b4ea7..0ba0bffb4ac 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -850,7 +850,6 @@ Parameters:
{
"alt": "dk",
"url": "/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png",
- "is_image": true,
"markdown": "![dk](/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png)"
}
```
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 31b4fd2669e..ea3fff1596e 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -13,34 +13,36 @@ If you want a quick introduction to GitLab CI, follow our
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [.gitlab-ci.yml](#gitlab-ci-yml)
- - [image and services](#image-and-services)
- - [before_script](#before_script)
- - [after_script](#after_script)
- - [stages](#stages)
- - [types](#types)
- - [variables](#variables)
- - [cache](#cache)
- - [cache:key](#cache-key)
+ - [image and services](#image-and-services)
+ - [before_script](#before_script)
+ - [after_script](#after_script)
+ - [stages](#stages)
+ - [types](#types)
+ - [variables](#variables)
+ - [cache](#cache)
+ - [cache:key](#cache-key)
- [Jobs](#jobs)
- - [script](#script)
- - [stage](#stage)
- - [only and except](#only-and-except)
- - [job variables](#job-variables)
- - [tags](#tags)
- - [when](#when)
- - [environment](#environment)
- - [artifacts](#artifacts)
- - [artifacts:name](#artifactsname)
- - [artifacts:when](#artifactswhen)
- - [artifacts:expire_in](#artifactsexpire_in)
- - [dependencies](#dependencies)
- - [before_script and after_script](#before_script-and-after_script)
+ - [script](#script)
+ - [stage](#stage)
+ - [only and except](#only-and-except)
+ - [job variables](#job-variables)
+ - [tags](#tags)
+ - [allow_failure](#allow_failure)
+ - [when](#when)
+ - [Manual actions](#manual-actions)
+ - [environment](#environment)
+ - [artifacts](#artifacts)
+ - [artifacts:name](#artifacts-name)
+ - [artifacts:when](#artifacts-when)
+ - [artifacts:expire_in](#artifacts-expire_in)
+ - [dependencies](#dependencies)
+ - [before_script and after_script](#before_script-and-after_script)
- [Git Strategy](#git-strategy)
- [Shallow cloning](#shallow-cloning)
- [Hidden jobs](#hidden-jobs)
- [Special YAML features](#special-yaml-features)
- - [Anchors](#anchors)
-- [Validate the .gitlab-ci.yml](#validate-the-gitlab-ciyml)
+ - [Anchors](#anchors)
+- [Validate the .gitlab-ci.yml](#validate-the-gitlab-ci-yml)
- [Skipping builds](#skipping-builds)
- [Examples](#examples)
@@ -473,6 +475,39 @@ job:
The specification above, will make sure that `job` is built by a Runner that
has both `ruby` AND `postgres` tags defined.
+### allow_failure
+
+`allow_failure` is used when you want to allow a build to fail without impacting
+the rest of the CI suite. Failed builds don't contribute to the commit status.
+
+When enabled and the build fails, the pipeline will be successful/green for all
+intents and purposes, but a "CI build passed with warnings" message will be
+displayed on the merge request or commit or build page. This is to be used by
+builds that are allowed to fail, but where failure indicates some other (manual)
+steps should be taken elsewhere.
+
+In the example below, `job1` and `job2` will run in parallel, but if `job1`
+fails, it will not stop the next stage from running, since it's marked with
+`allow_failure: true`:
+
+```yaml
+job1:
+ stage: test
+ script:
+ - execute_script_that_will_fail
+ allow_failure: true
+
+job2:
+ stage: test
+ script:
+ - execute_script_that_will_succeed
+
+job3:
+ stage: deploy
+ script:
+ - deploy_to_staging
+```
+
### when
`when` is used to implement jobs that are run in case of failure or despite the
@@ -485,7 +520,8 @@ failure.
1. `on_failure` - execute build only when at least one build from prior stages
fails.
1. `always` - execute build regardless of the status of builds from prior stages.
-1. `manual` - execute build manually.
+1. `manual` - execute build manually (added in GitLab 8.10). Read about
+ [manual actions](#manual-actions) below.
For example:
@@ -528,21 +564,22 @@ cleanup_job:
The above script will:
-1. Execute `cleanup_build_job` only when `build_job` fails
-2. Always execute `cleanup_job` as the last step in pipeline
-3. Allow you to manually execute `deploy_job` from GitLab
+1. Execute `cleanup_build_job` only when `build_job` fails.
+2. Always execute `cleanup_job` as the last step in pipeline regardless of
+ success or failure.
+3. Allow you to manually execute `deploy_job` from GitLab's UI.
#### Manual actions
>**Note:**
Introduced in GitLab 8.10.
-Manual actions are special type of jobs that are not executed automatically in pipeline.
-They need to be explicitly started by the user.
-Manual actions can be started from pipelines, builds, environments and deployments views.
-You can execute the same manual action multiple times.
+Manual actions are a special type of job that are not executed automatically;
+they need to be explicitly started by a user. Manual actions can be started
+from pipeline, build, environment, and deployment views. You can execute the
+same manual action multiple times.
-Example usage of manual actions is deployment, ex. promote a staging environment to production.
+An example usage of manual actions is deployment to production.
### environment
@@ -645,9 +682,10 @@ be available for download in the GitLab UI.
Introduced in GitLab 8.6 and GitLab Runner v1.1.0.
The `name` directive allows you to define the name of the created artifacts
-archive. That way, you can have a unique name of every archive which could be
+archive. That way, you can have a unique name for every archive which could be
useful when you'd like to download the archive from GitLab. The `artifacts:name`
variable can make use of any of the [predefined variables](../variables/README.md).
+The default name is `artifacts`, which becomes `artifacts.zip` when downloaded.
---
diff --git a/doc/container_registry/README.md b/doc/container_registry/README.md
index 1b465434498..55077197ff9 100644
--- a/doc/container_registry/README.md
+++ b/doc/container_registry/README.md
@@ -1,7 +1,8 @@
# GitLab Container Registry
> **Note:**
-This feature was [introduced][ce-4040] in GitLab 8.8.
+This feature was [introduced][ce-4040] in GitLab 8.8. Docker Registry manifest
+v1 support was added in GitLab 8.9 to support Docker versions earlier than 1.10.
> **Note:**
This document is about the user guide. To learn how to enable GitLab Container
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 19d083d580d..9bc0dbb5e2a 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -398,7 +398,7 @@ If you are not using Linux you may have to run `gmake` instead of
cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
cd gitlab-workhorse
- sudo -u git -H git checkout v0.7.7
+ sudo -u git -H git checkout v0.7.8
sudo -u git -H make
### Initialize Database and Activate Advanced Features
diff --git a/doc/markdown/img/video.mp4 b/doc/markdown/img/video.mp4
new file mode 100644
index 00000000000..1fc478842f5
--- /dev/null
+++ b/doc/markdown/img/video.mp4
Binary files differ
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index fb2dd582754..2bbe4b2a36e 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -13,6 +13,7 @@
* [Emoji](#emoji)
* [Special GitLab references](#special-gitlab-references)
* [Task Lists](#task-lists)
+* [Videos](#videos)
**[Standard Markdown](#standard-markdown)**
@@ -281,6 +282,20 @@ You can add task lists to issues, merge requests and comments. To create a task
Task lists can only be created in descriptions, not in titles. Task item state can be managed by editing the description's Markdown or by toggling the rendered check boxes.
+## Videos
+
+Image tags with a video extension are automatically converted to a video player.
+
+The valid video extensions are `.mp4`, `.m4v`, `.mov`, `.webm`, and `.ogv`.
+
+ Here's a sample video:
+
+ ![Sample Video](img/video.mp4)
+
+Here's a sample video:
+
+![Sample Video](img/video.mp4)
+
# Standard Markdown
## Headers
diff --git a/doc/update/8.9-to-8.10.md b/doc/update/8.9-to-8.10.md
index 84065a84e50..71cbe5c8ac6 100644
--- a/doc/update/8.9-to-8.10.md
+++ b/doc/update/8.9-to-8.10.md
@@ -58,7 +58,7 @@ GitLab 8.1.
```bash
cd /home/git/gitlab-workhorse
sudo -u git -H git fetch --all
-sudo -u git -H git checkout v0.7.7
+sudo -u git -H git checkout v0.7.8
sudo -u git -H make
```
diff --git a/features/project/wiki.feature b/features/project/wiki.feature
index d4811b1ff54..63ce3ccb536 100644
--- a/features/project/wiki.feature
+++ b/features/project/wiki.feature
@@ -8,6 +8,12 @@ Feature: Project Wiki
Given I create the Wiki Home page
Then I should see the newly created wiki page
+ Scenario: Add new page with errors
+ Given I create the Wiki Home page with no content
+ Then I should see a "Content can't be blank" error message
+ When I create the Wiki Home page
+ Then I should see the newly created wiki page
+
Scenario: Pressing Cancel while editing a brand new Wiki
Given I click on the Cancel button
Then I should be redirected back to the Edit Home Wiki page
diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb
index 3cbf832c728..5ad82a9b3c1 100644
--- a/features/steps/project/wiki.rb
+++ b/features/steps/project/wiki.rb
@@ -19,6 +19,11 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
click_on "Create page"
end
+ step 'I create the Wiki Home page with no content' do
+ fill_in "wiki_content", with: ''
+ click_on "Create page"
+ end
+
step 'I should see the newly created wiki page' do
expect(page).to have_content "Home"
expect(page).to have_content "link test"
@@ -173,6 +178,11 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
find('a[href*="?version_id"]')
end
+ step 'I should see a "Content can\'t be blank" error message' do
+ expect(page).to have_content('The form contains the following error:')
+ expect(page).to have_content('Content can\'t be blank')
+ end
+
def wiki
@project_wiki = ProjectWiki.new(project, current_user)
end
diff --git a/lib/api/builds.rb b/lib/api/builds.rb
index d36047acd1f..657d421fe97 100644
--- a/lib/api/builds.rb
+++ b/lib/api/builds.rb
@@ -52,8 +52,7 @@ module API
get ':id/builds/:build_id' do
authorize_read_builds!
- build = get_build(params[:build_id])
- return not_found!(build) unless build
+ build = get_build!(params[:build_id])
present build, with: Entities::Build,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
@@ -69,18 +68,27 @@ module API
get ':id/builds/:build_id/artifacts' do
authorize_read_builds!
- build = get_build(params[:build_id])
- return not_found!(build) unless build
+ build = get_build!(params[:build_id])
- artifacts_file = build.artifacts_file
+ present_artifacts!(build.artifacts_file)
+ end
- unless artifacts_file.file_storage?
- return redirect_to build.artifacts_file.url
- end
+ # Download the artifacts file from ref_name and job
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # ref_name (required) - The ref from repository
+ # job (required) - The name for the build
+ # Example Request:
+ # GET /projects/:id/artifacts/:ref_name/download?job=name
+ get ':id/builds/artifacts/:ref_name/download',
+ requirements: { ref_name: /.+/ } do
+ authorize_read_builds!
- return not_found! unless artifacts_file.exists?
+ builds = user_project.latest_successful_builds_for(params[:ref_name])
+ latest_build = builds.find_by!(name: params[:job])
- present_file!(artifacts_file.path, artifacts_file.filename)
+ present_artifacts!(latest_build.artifacts_file)
end
# Get a trace of a specific build of a project
@@ -97,8 +105,7 @@ module API
get ':id/builds/:build_id/trace' do
authorize_read_builds!
- build = get_build(params[:build_id])
- return not_found!(build) unless build
+ build = get_build!(params[:build_id])
header 'Content-Disposition', "infile; filename=\"#{build.id}.log\""
content_type 'text/plain'
@@ -118,8 +125,7 @@ module API
post ':id/builds/:build_id/cancel' do
authorize_update_builds!
- build = get_build(params[:build_id])
- return not_found!(build) unless build
+ build = get_build!(params[:build_id])
build.cancel
@@ -137,8 +143,7 @@ module API
post ':id/builds/:build_id/retry' do
authorize_update_builds!
- build = get_build(params[:build_id])
- return not_found!(build) unless build
+ build = get_build!(params[:build_id])
return forbidden!('Build is not retryable') unless build.retryable?
build = Ci::Build.retry(build, current_user)
@@ -157,8 +162,7 @@ module API
post ':id/builds/:build_id/erase' do
authorize_update_builds!
- build = get_build(params[:build_id])
- return not_found!(build) unless build
+ build = get_build!(params[:build_id])
return forbidden!('Build is not erasable!') unless build.erasable?
build.erase(erased_by: current_user)
@@ -176,8 +180,8 @@ module API
post ':id/builds/:build_id/artifacts/keep' do
authorize_update_builds!
- build = get_build(params[:build_id])
- return not_found!(build) unless build && build.artifacts?
+ build = get_build!(params[:build_id])
+ return not_found!(build) unless build.artifacts?
build.keep_artifacts!
@@ -192,6 +196,20 @@ module API
user_project.builds.find_by(id: id.to_i)
end
+ def get_build!(id)
+ get_build(id) || not_found!
+ end
+
+ def present_artifacts!(artifacts_file)
+ if !artifacts_file.file_storage?
+ redirect_to(build.artifacts_file.url)
+ elsif artifacts_file.exists?
+ present_file!(artifacts_file.path, artifacts_file.filename)
+ else
+ not_found!
+ end
+ end
+
def filter_builds(builds, scope)
return builds if scope.nil? || scope.empty?
diff --git a/lib/banzai/filter/video_link_filter.rb b/lib/banzai/filter/video_link_filter.rb
new file mode 100644
index 00000000000..fd8b9a6f0cc
--- /dev/null
+++ b/lib/banzai/filter/video_link_filter.rb
@@ -0,0 +1,59 @@
+module Banzai
+ module Filter
+
+ # Find every image that isn't already wrapped in an `a` tag, and that has
+ # a `src` attribute ending with a video extension, add a new video node and
+ # a "Download" link in the case the video cannot be played.
+ class VideoLinkFilter < HTML::Pipeline::Filter
+
+ def call
+ doc.xpath(query).each do |el|
+ el.replace(video_node(doc, el))
+ end
+
+ doc
+ end
+
+ private
+
+ def query
+ @query ||= begin
+ src_query = UploaderHelper::VIDEO_EXT.map do |ext|
+ "'.#{ext}' = substring(@src, string-length(@src) - #{ext.size})"
+ end
+
+ "descendant-or-self::img[not(ancestor::a) and (#{src_query.join(' or ')})]"
+ end
+ end
+
+ def video_node(doc, element)
+ container = doc.document.create_element(
+ 'div',
+ class: 'video-container'
+ )
+
+ video = doc.document.create_element(
+ 'video',
+ src: element['src'],
+ width: '400',
+ controls: true,
+ 'data-setup' => '{}')
+
+ link = doc.document.create_element(
+ 'a',
+ element['title'] || element['alt'],
+ href: element['src'],
+ target: '_blank',
+ title: "Download '#{element['title'] || element['alt']}'")
+ download_paragraph = doc.document.create_element('p')
+ download_paragraph.children = link
+
+ container.add_child(video)
+ container.add_child(download_paragraph)
+
+ container
+ end
+ end
+
+ end
+end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index b27ecf3c923..8d94b199c66 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -7,6 +7,7 @@ module Banzai
Filter::SanitizationFilter,
Filter::UploadLinkFilter,
+ Filter::VideoLinkFilter,
Filter::ImageLinkFilter,
Filter::EmojiFilter,
Filter::TableOfContentsFilter,
diff --git a/lib/banzai/reference_extractor.rb b/lib/banzai/reference_extractor.rb
index bf366962aef..b26a41a1f3b 100644
--- a/lib/banzai/reference_extractor.rb
+++ b/lib/banzai/reference_extractor.rb
@@ -2,11 +2,11 @@ module Banzai
# Extract possible GFM references from an arbitrary String for further processing.
class ReferenceExtractor
def initialize
- @texts = []
+ @texts_and_contexts = []
end
def analyze(text, context = {})
- @texts << Renderer.render(text, context)
+ @texts_and_contexts << { text: text, context: context }
end
def references(type, project, current_user = nil)
@@ -21,9 +21,10 @@ module Banzai
def html_documents
# This ensures that we don't memoize anything until we have a number of
# text blobs to parse.
- return [] if @texts.empty?
+ return [] if @texts_and_contexts.empty?
- @html_documents ||= @texts.map { |html| Nokogiri::HTML.fragment(html) }
+ @html_documents ||= Renderer.cache_collection_render(@texts_and_contexts)
+ .map { |html| Nokogiri::HTML.fragment(html) }
end
end
end
diff --git a/lib/gitlab/diff/parallel_diff.rb b/lib/gitlab/diff/parallel_diff.rb
index b069afdd28c..481536a380b 100644
--- a/lib/gitlab/diff/parallel_diff.rb
+++ b/lib/gitlab/diff/parallel_diff.rb
@@ -8,72 +8,35 @@ module Gitlab
end
def parallelize
-
i = 0
free_right_index = nil
lines = []
highlighted_diff_lines = diff_file.highlighted_diff_lines
highlighted_diff_lines.each do |line|
- line_code = diff_file.line_code(line)
- position = diff_file.position(line)
-
- case line.type
- when 'match', nil
+ if line.meta? || line.unchanged?
# line in the right panel is the same as in the left one
lines << {
- left: {
- type: line.type,
- number: line.old_pos,
- text: line.text,
- line_code: line_code,
- position: position
- },
- right: {
- type: line.type,
- number: line.new_pos,
- text: line.text,
- line_code: line_code,
- position: position
- }
+ left: line,
+ right: line
}
free_right_index = nil
i += 1
- when 'old'
+ elsif line.removed?
lines << {
- left: {
- type: line.type,
- number: line.old_pos,
- text: line.text,
- line_code: line_code,
- position: position
- },
- right: {
- type: nil,
- number: nil,
- text: "",
- line_code: line_code,
- position: position
- }
+ left: line,
+ right: nil
}
# Once we come upon a new line it can be put on the right of this old line
free_right_index ||= i
i += 1
- when 'new'
- data = {
- type: line.type,
- number: line.new_pos,
- text: line.text,
- line_code: line_code,
- position: position
- }
-
+ elsif line.added?
if free_right_index
# If an old line came before this without a line on the right, this
# line can be put to the right of it.
- lines[free_right_index][:right] = data
+ lines[free_right_index][:right] = line
# If there are any other old lines on the left that don't yet have
# a new counterpart on the right, update the free_right_index
@@ -81,14 +44,8 @@ module Gitlab
free_right_index = next_free_right_index < i ? next_free_right_index : nil
else
lines << {
- left: {
- type: nil,
- number: nil,
- text: "",
- line_code: line_code,
- position: position
- },
- right: data
+ left: nil,
+ right: line
}
free_right_index = nil
diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb
index 0893ee89f6a..71d0e4be834 100644
--- a/spec/controllers/projects/uploads_controller_spec.rb
+++ b/spec/controllers/projects/uploads_controller_spec.rb
@@ -14,9 +14,9 @@ describe Projects::UploadsController do
context "without params['file']" do
it "returns an error" do
- post :create,
+ post :create,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
+ project_id: project.to_param,
format: :json
expect(response).to have_http_status(422)
end
@@ -34,23 +34,21 @@ describe Projects::UploadsController do
it 'returns a content with original filename, new link, and correct type.' do
expect(response.body).to match '\"alt\":\"rails_sample\"'
expect(response.body).to match "\"url\":\"/uploads"
- expect(response.body).to match '\"is_image\":true'
end
end
context 'with valid non-image file' do
before do
- post :create,
+ post :create,
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- file: txt,
+ project_id: project.to_param,
+ file: txt,
format: :json
end
it 'returns a content with original filename, new link, and correct type.' do
expect(response.body).to match '\"alt\":\"doc_sample.txt\"'
expect(response.body).to match "\"url\":\"/uploads"
- expect(response.body).to match '\"is_image\":false'
end
end
end
diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb
index e72aa9479b7..2c0a2dd94ca 100644
--- a/spec/factories/issues.rb
+++ b/spec/factories/issues.rb
@@ -18,5 +18,15 @@ FactoryGirl.define do
factory :closed_issue, traits: [:closed]
factory :reopened_issue, traits: [:reopened]
+
+ factory :labeled_issue do
+ transient do
+ labels []
+ end
+
+ after(:create) do |issue, evaluator|
+ issue.update_attributes(labels: evaluator.labels)
+ end
+ end
end
end
diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb
index 9c018be14b7..a7d9f2a0c72 100644
--- a/spec/features/environments_spec.rb
+++ b/spec/features/environments_spec.rb
@@ -92,8 +92,8 @@ feature 'Environments', feature: true do
expect(page).to have_link(deployment.short_sha)
end
- scenario 'does not show a retry button for deployment without build' do
- expect(page).not_to have_link('Retry')
+ scenario 'does not show a re-deploy button for deployment without build' do
+ expect(page).not_to have_link('Re-deploy')
end
context 'with build' do
@@ -105,8 +105,8 @@ feature 'Environments', feature: true do
expect(page).to have_link("#{build.name} (##{build.id})")
end
- scenario 'does show retry button' do
- expect(page).to have_link('Retry')
+ scenario 'does show re-deploy button' do
+ expect(page).to have_link('Re-deploy')
end
context 'with manual action' do
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index 09ccc77c101..32159559c37 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -236,6 +236,14 @@ describe 'GitLab Markdown', feature: true do
it 'includes TaskListFilter' do
expect(doc).to parse_task_lists
end
+
+ it 'includes InlineDiffFilter' do
+ expect(doc).to parse_inline_diffs
+ end
+
+ it 'includes VideoLinkFilter' do
+ expect(doc).to parse_video_links
+ end
end
context 'wiki pipeline' do
@@ -293,6 +301,10 @@ describe 'GitLab Markdown', feature: true do
it 'includes InlineDiffFilter' do
expect(doc).to parse_inline_diffs
end
+
+ it 'includes VideoLinkFilter' do
+ expect(doc).to parse_video_links
+ end
end
# Fake a `current_user` helper
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index bc3bf53fe9d..2d1e3bbebe5 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -59,6 +59,21 @@ feature 'project import', feature: true, js: true do
end
end
+ scenario 'project with no name' do
+ create(:project, namespace_id: 2)
+
+ visit new_project_path
+
+ select2('2', from: '#project_namespace_id')
+
+ # click on disabled element
+ find(:link, 'GitLab export').trigger('click')
+
+ page.within('.flash-container') do
+ expect(page).to have_content('Please enter path and name')
+ end
+ end
+
def wiki_exists?
wiki = ProjectWiki.new(project)
File.exist?(wiki.repository.path_to_repo) && !wiki.repository.empty?
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index c75d28d9801..f3e7c2d1a9f 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -256,3 +256,7 @@ However the wrapping tags can not be mixed as such -
- [+ additions +}
- {- delletions -]
- [- delletions -}
+
+### Videos
+
+![My Video](/assets/videos/gitlab-demo.mp4)
diff --git a/spec/fixtures/parallel_diff_result.yml b/spec/fixtures/parallel_diff_result.yml
deleted file mode 100644
index 37066c8e930..00000000000
--- a/spec/fixtures/parallel_diff_result.yml
+++ /dev/null
@@ -1,800 +0,0 @@
----
-- :left:
- :type: match
- :number: 6
- :text: "@@ -6,12 +6,18 @@ module Popen"
- :line_code:
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line:
- :new_line:
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type: match
- :number: 6
- :text: "@@ -6,12 +6,18 @@ module Popen"
- :line_code:
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line:
- :new_line:
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type:
- :number: 6
- :text: |2
- <span id="LC6" class="line"></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 6
- :new_line: 6
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type:
- :number: 6
- :text: |2
- <span id="LC6" class="line"></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 6
- :new_line: 6
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type:
- :number: 7
- :text: |2
- <span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 7
- :new_line: 7
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type:
- :number: 7
- :text: |2
- <span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 7
- :new_line: 7
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type:
- :number: 8
- :text: |2
- <span id="LC8" class="line"> <span class="k">unless</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 8
- :new_line: 8
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type:
- :number: 8
- :text: |2
- <span id="LC8" class="line"> <span class="k">unless</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 8
- :new_line: 8
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type: old
- :number: 9
- :text: |
- -<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 9
- :new_line:
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type: new
- :number: 9
- :text: |
- +<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">"System commands must be given as an array of strings"</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line:
- :new_line: 9
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type:
- :number: 10
- :text: |2
- <span id="LC10" class="line"> <span class="k">end</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 10
- :new_line: 10
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type:
- :number: 10
- :text: |2
- <span id="LC10" class="line"> <span class="k">end</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 10
- :new_line: 10
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type:
- :number: 11
- :text: |2
- <span id="LC11" class="line"></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 11
- :new_line: 11
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type:
- :number: 11
- :text: |2
- <span id="LC11" class="line"></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 11
- :new_line: 11
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type:
- :number: 12
- :text: |2
- <span id="LC12" class="line"> <span class="n">path</span> <span class="o">||=</span> <span class="no">Dir</span><span class="p">.</span><span class="nf">pwd</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 12
- :new_line: 12
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type:
- :number: 12
- :text: |2
- <span id="LC12" class="line"> <span class="n">path</span> <span class="o">||=</span> <span class="no">Dir</span><span class="p">.</span><span class="nf">pwd</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 12
- :new_line: 12
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type: old
- :number: 13
- :text: |
- -<span id="LC13" class="line"> <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"PWD"</span> <span class="o">=&gt;</span> <span class="n">path</span> <span class="p">}</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_13_13
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 13
- :new_line:
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type: new
- :number: 13
- :text: |
- +<span id="LC13" class="line"></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_13
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line:
- :new_line: 13
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type: old
- :number: 14
- :text: |
- -<span id="LC14" class="line"> <span class="n">options</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">chdir: </span><span class="n">path</span> <span class="p">}</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_13
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 14
- :new_line:
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type: new
- :number: 14
- :text: |
- +<span id="LC14" class="line"> <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line:
- :new_line: 14
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type:
- :number:
- :text: ''
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line:
- :new_line: 15
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type: new
- :number: 15
- :text: |
- +<span id="LC15" class="line"> <span class="s2">"PWD"</span> <span class="o">=&gt;</span> <span class="n">path</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line:
- :new_line: 15
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type:
- :number:
- :text: ''
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line:
- :new_line: 16
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type: new
- :number: 16
- :text: |
- +<span id="LC16" class="line"> <span class="p">}</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line:
- :new_line: 16
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type:
- :number:
- :text: ''
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line:
- :new_line: 17
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type: new
- :number: 17
- :text: |
- +<span id="LC17" class="line"></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line:
- :new_line: 17
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type:
- :number:
- :text: ''
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line:
- :new_line: 18
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type: new
- :number: 18
- :text: |
- +<span id="LC18" class="line"> <span class="n">options</span> <span class="o">=</span> <span class="p">{</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line:
- :new_line: 18
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type:
- :number:
- :text: ''
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line:
- :new_line: 19
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type: new
- :number: 19
- :text: |
- +<span id="LC19" class="line"> <span class="ss">chdir: </span><span class="n">path</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line:
- :new_line: 19
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type:
- :number:
- :text: ''
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line:
- :new_line: 20
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type: new
- :number: 20
- :text: |
- +<span id="LC20" class="line"> <span class="p">}</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line:
- :new_line: 20
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type:
- :number: 15
- :text: |2
- <span id="LC21" class="line"></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 15
- :new_line: 21
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type:
- :number: 21
- :text: |2
- <span id="LC21" class="line"></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 15
- :new_line: 21
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type:
- :number: 16
- :text: |2
- <span id="LC22" class="line"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 16
- :new_line: 22
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type:
- :number: 22
- :text: |2
- <span id="LC22" class="line"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 16
- :new_line: 22
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type:
- :number: 17
- :text: |2
- <span id="LC23" class="line"> <span class="no">FileUtils</span><span class="p">.</span><span class="nf">mkdir_p</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 17
- :new_line: 23
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type:
- :number: 23
- :text: |2
- <span id="LC23" class="line"> <span class="no">FileUtils</span><span class="p">.</span><span class="nf">mkdir_p</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 17
- :new_line: 23
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type: match
- :number: 19
- :text: "@@ -19,6 +25,7 @@ module Popen"
- :line_code:
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line:
- :new_line:
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type: match
- :number: 25
- :text: "@@ -19,6 +25,7 @@ module Popen"
- :line_code:
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line:
- :new_line:
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type:
- :number: 19
- :text: |2
- <span id="LC25" class="line"></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 19
- :new_line: 25
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type:
- :number: 25
- :text: |2
- <span id="LC25" class="line"></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 19
- :new_line: 25
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type:
- :number: 20
- :text: |2
- <span id="LC26" class="line"> <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">""</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 20
- :new_line: 26
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type:
- :number: 26
- :text: |2
- <span id="LC26" class="line"> <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">""</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 20
- :new_line: 26
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type:
- :number: 21
- :text: |2
- <span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 21
- :new_line: 27
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type:
- :number: 27
- :text: |2
- <span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 21
- :new_line: 27
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type:
- :number:
- :text: ''
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line:
- :new_line: 28
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type: new
- :number: 28
- :text: |
- +<span id="LC28" class="line"></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line:
- :new_line: 28
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type:
- :number: 22
- :text: |2
- <span id="LC29" class="line"> <span class="no">Open3</span><span class="p">.</span><span class="nf">popen3</span><span class="p">(</span><span class="n">vars</span><span class="p">,</span> <span class="o">*</span><span class="n">cmd</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">stdin</span><span class="p">,</span> <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span><span class="p">,</span> <span class="n">wait_thr</span><span class="o">|</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 22
- :new_line: 29
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type:
- :number: 29
- :text: |2
- <span id="LC29" class="line"> <span class="no">Open3</span><span class="p">.</span><span class="nf">popen3</span><span class="p">(</span><span class="n">vars</span><span class="p">,</span> <span class="o">*</span><span class="n">cmd</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">stdin</span><span class="p">,</span> <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span><span class="p">,</span> <span class="n">wait_thr</span><span class="o">|</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 22
- :new_line: 29
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type:
- :number: 23
- :text: |2
- <span id="LC30" class="line"> <span class="vi">@cmd_output</span> <span class="o">&lt;&lt;</span> <span class="n">stdout</span><span class="p">.</span><span class="nf">read</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 23
- :new_line: 30
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type:
- :number: 30
- :text: |2
- <span id="LC30" class="line"> <span class="vi">@cmd_output</span> <span class="o">&lt;&lt;</span> <span class="n">stdout</span><span class="p">.</span><span class="nf">read</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 23
- :new_line: 30
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
-- :left:
- :type:
- :number: 24
- :text: |2
- <span id="LC31" class="line"> <span class="vi">@cmd_output</span> <span class="o">&lt;&lt;</span> <span class="n">stderr</span><span class="p">.</span><span class="nf">read</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 24
- :new_line: 31
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :right:
- :type:
- :number: 31
- :text: |2
- <span id="LC31" class="line"> <span class="vi">@cmd_output</span> <span class="o">&lt;&lt;</span> <span class="n">stderr</span><span class="p">.</span><span class="nf">read</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31
- :position: !ruby/object:Gitlab::Diff::Position
- attributes:
- :old_path: files/ruby/popen.rb
- :new_path: files/ruby/popen.rb
- :old_line: 24
- :new_line: 31
- :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
- :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
diff --git a/spec/fixtures/video_sample.mp4 b/spec/fixtures/video_sample.mp4
new file mode 100644
index 00000000000..acd45190998
--- /dev/null
+++ b/spec/fixtures/video_sample.mp4
Binary files differ
diff --git a/spec/helpers/time_helper_spec.rb b/spec/helpers/time_helper_spec.rb
index 413ead944b9..bf3ed5c094c 100644
--- a/spec/helpers/time_helper_spec.rb
+++ b/spec/helpers/time_helper_spec.rb
@@ -5,6 +5,7 @@ describe TimeHelper do
it "returns minutes and seconds" do
intervals_in_words = {
100 => "1 minute 40 seconds",
+ 100.32 => "1 minute 40 seconds",
121 => "2 minutes 1 second",
3721 => "62 minutes 1 second",
0 => "0 seconds"
diff --git a/spec/initializers/6_validations_spec.rb b/spec/initializers/6_validations_spec.rb
index 5178bd130f4..baab30f482f 100644
--- a/spec/initializers/6_validations_spec.rb
+++ b/spec/initializers/6_validations_spec.rb
@@ -1,41 +1,58 @@
require 'spec_helper'
+require_relative '../../config/initializers/6_validations.rb'
describe '6_validations', lib: true do
+ before :all do
+ FileUtils.mkdir_p('tmp/tests/paths/a/b/c/d')
+ FileUtils.mkdir_p('tmp/tests/paths/a/b/c2')
+ FileUtils.mkdir_p('tmp/tests/paths/a/b/d')
+ end
+
+ after :all do
+ FileUtils.rm_rf('tmp/tests/paths')
+ end
+
context 'with correct settings' do
before do
- mock_storages('foo' => '/a/b/c', 'bar' => 'a/b/d')
+ mock_storages('foo' => 'tmp/tests/paths/a/b/c', 'bar' => 'tmp/tests/paths/a/b/d')
end
it 'passes through' do
- expect { load_validations }.not_to raise_error
+ expect { validate_storages }.not_to raise_error
end
end
context 'with invalid storage names' do
before do
- mock_storages('name with spaces' => '/a/b/c')
+ mock_storages('name with spaces' => 'tmp/tests/paths/a/b/c')
end
it 'throws an error' do
- expect { load_validations }.to raise_error('"name with spaces" is not a valid storage name. Please fix this in your gitlab.yml before starting GitLab.')
+ expect { validate_storages }.to raise_error('"name with spaces" is not a valid storage name. Please fix this in your gitlab.yml before starting GitLab.')
end
end
context 'with nested storage paths' do
before do
- mock_storages('foo' => '/a/b/c', 'bar' => '/a/b/c/d')
+ mock_storages('foo' => 'tmp/tests/paths/a/b/c', 'bar' => 'tmp/tests/paths/a/b/c/d')
end
it 'throws an error' do
- expect { load_validations }.to raise_error('bar is a nested path of foo. Nested paths are not supported for repository storages. Please fix this in your gitlab.yml before starting GitLab.')
+ expect { validate_storages }.to raise_error('bar is a nested path of foo. Nested paths are not supported for repository storages. Please fix this in your gitlab.yml before starting GitLab.')
end
end
- def mock_storages(storages)
- allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
+ context 'with similar but un-nested storage paths' do
+ before do
+ mock_storages('foo' => 'tmp/tests/paths/a/b/c', 'bar' => 'tmp/tests/paths/a/b/c2')
+ end
+
+ it 'passes through' do
+ expect { validate_storages }.not_to raise_error
+ end
end
- def load_validations
- load File.join(__dir__, '../../config/initializers/6_validations.rb')
+ def mock_storages(storages)
+ allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
end
end
diff --git a/spec/lib/banzai/filter/video_link_filter_spec.rb b/spec/lib/banzai/filter/video_link_filter_spec.rb
new file mode 100644
index 00000000000..cc4349f80ba
--- /dev/null
+++ b/spec/lib/banzai/filter/video_link_filter_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe Banzai::Filter::VideoLinkFilter, lib: true do
+ def filter(doc, contexts = {})
+ contexts.reverse_merge!({
+ project: project
+ })
+
+ described_class.call(doc, contexts)
+ end
+
+ def link_to_image(path)
+ %(<img src="#{path}" />)
+ end
+
+ let(:project) { create(:project) }
+
+ context 'when the element src has a video extension' do
+ UploaderHelper::VIDEO_EXT.each do |ext|
+ it "replaces the image tag 'path/video.#{ext}' with a video tag" do
+ container = filter(link_to_image("/path/video.#{ext}")).children.first
+
+ expect(container.name).to eq 'div'
+ expect(container['class']).to eq 'video-container'
+
+ video, paragraph = container.children
+
+ expect(video.name).to eq 'video'
+ expect(video['src']).to eq "/path/video.#{ext}"
+
+ expect(paragraph.name).to eq 'p'
+
+ link = paragraph.children.first
+
+ expect(link.name).to eq 'a'
+ expect(link['href']).to eq "/path/video.#{ext}"
+ expect(link['target']).to eq '_blank'
+ end
+ end
+ end
+
+ context 'when the element src is an image' do
+ it 'leaves the document unchanged' do
+ element = filter(link_to_image('/path/my_image.jpg')).children.first
+
+ expect(element.name).to eq 'img'
+ expect(element['src']).to eq '/path/my_image.jpg'
+ end
+ end
+
+end
diff --git a/spec/lib/gitlab/diff/parallel_diff_spec.rb b/spec/lib/gitlab/diff/parallel_diff_spec.rb
index 5f76b70c6f5..2aa5ae44f54 100644
--- a/spec/lib/gitlab/diff/parallel_diff_spec.rb
+++ b/spec/lib/gitlab/diff/parallel_diff_spec.rb
@@ -11,11 +11,51 @@ describe Gitlab::Diff::ParallelDiff, lib: true do
let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: repository) }
subject { described_class.new(diff_file) }
- let(:parallel_diff_result_array) { YAML.load_file("#{Rails.root}/spec/fixtures/parallel_diff_result.yml") }
-
describe '#parallelize' do
it 'should return an array of arrays containing the parsed diff' do
- expect(subject.parallelize).to match_array(parallel_diff_result_array)
+ diff_lines = diff_file.highlighted_diff_lines
+ expected = [
+ # Unchanged lines
+ { left: diff_lines[0], right: diff_lines[0] },
+ { left: diff_lines[1], right: diff_lines[1] },
+ { left: diff_lines[2], right: diff_lines[2] },
+ { left: diff_lines[3], right: diff_lines[3] },
+ { left: diff_lines[4], right: diff_lines[5] },
+ { left: diff_lines[6], right: diff_lines[6] },
+ { left: diff_lines[7], right: diff_lines[7] },
+ { left: diff_lines[8], right: diff_lines[8] },
+
+ # Changed lines
+ { left: diff_lines[9], right: diff_lines[11] },
+ { left: diff_lines[10], right: diff_lines[12] },
+
+ # Added lines
+ { left: nil, right: diff_lines[13] },
+ { left: nil, right: diff_lines[14] },
+ { left: nil, right: diff_lines[15] },
+ { left: nil, right: diff_lines[16] },
+ { left: nil, right: diff_lines[17] },
+ { left: nil, right: diff_lines[18] },
+
+ # Unchanged lines
+ { left: diff_lines[19], right: diff_lines[19] },
+ { left: diff_lines[20], right: diff_lines[20] },
+ { left: diff_lines[21], right: diff_lines[21] },
+ { left: diff_lines[22], right: diff_lines[22] },
+ { left: diff_lines[23], right: diff_lines[23] },
+ { left: diff_lines[24], right: diff_lines[24] },
+ { left: diff_lines[25], right: diff_lines[25] },
+
+ # Added line
+ { left: nil, right: diff_lines[26] },
+
+ # Unchanged lines
+ { left: diff_lines[27], right: diff_lines[27] },
+ { left: diff_lines[28], right: diff_lines[28] },
+ { left: diff_lines[29], right: diff_lines[29] }
+ ]
+
+ expect(subject.parallelize).to eq(expected)
end
end
end
diff --git a/spec/lib/gitlab/email/attachment_uploader_spec.rb b/spec/lib/gitlab/email/attachment_uploader_spec.rb
index 476a21bf996..08b2577ecc4 100644
--- a/spec/lib/gitlab/email/attachment_uploader_spec.rb
+++ b/spec/lib/gitlab/email/attachment_uploader_spec.rb
@@ -11,7 +11,6 @@ describe Gitlab::Email::AttachmentUploader, lib: true do
link = links.first
expect(link).not_to be_nil
- expect(link[:is_image]).to be_truthy
expect(link[:alt]).to eq("bricks")
expect(link[:url]).to include("bricks.png")
end
diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb
index 36267faeb93..84d2584a791 100644
--- a/spec/lib/gitlab/email/receiver_spec.rb
+++ b/spec/lib/gitlab/email/receiver_spec.rb
@@ -115,7 +115,6 @@ describe Gitlab::Email::Receiver, lib: true do
[
{
url: "uploads/image.png",
- is_image: true,
alt: "image",
markdown: markdown
}
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index 2de305738cc..978ad9c52d5 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -5,7 +5,9 @@ describe Ci::Build, models: true do
let(:pipeline) do
create(:ci_pipeline, project: project,
- sha: project.commit.id)
+ sha: project.commit.id,
+ ref: project.default_branch,
+ status: 'success')
end
let(:build) { create(:ci_build, pipeline: pipeline) }
@@ -720,7 +722,7 @@ describe Ci::Build, models: true do
describe '#erasable?' do
subject { build.erasable? }
- it { is_expected.to eq true }
+ it { is_expected.to be_truthy }
end
describe '#erased?' do
@@ -728,7 +730,7 @@ describe Ci::Build, models: true do
subject { build.erased? }
context 'build has not been erased' do
- it { is_expected.to be false }
+ it { is_expected.to be_falsey }
end
context 'build has been erased' do
@@ -736,12 +738,13 @@ describe Ci::Build, models: true do
build.erase
end
- it { is_expected.to be true }
+ it { is_expected.to be_truthy }
end
end
context 'metadata and build trace are not available' do
let!(:build) { create(:ci_build, :success, :artifacts) }
+
before do
build.remove_artifacts_metadata!
end
@@ -763,19 +766,19 @@ describe Ci::Build, models: true do
describe '#retryable?' do
context 'when build is running' do
- before { build.run! }
-
- it 'should return false' do
- expect(build.retryable?).to be false
+ before do
+ build.run!
end
+
+ it { expect(build).not_to be_retryable }
end
context 'when build is finished' do
- before { build.success! }
-
- it 'should return true' do
- expect(build.retryable?).to be true
+ before do
+ build.success!
end
+
+ it { expect(build).to be_retryable }
end
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 7d0697dab42..1243f5420a7 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -135,22 +135,30 @@ describe Note, models: true do
let!(:note2) { create(:note_on_issue) }
it "reads the rendered note body from the cache" do
- expect(Banzai::Renderer).to receive(:render).
- with(note1.note,
- pipeline: :note,
- cache_key: [note1, "note"],
- project: note1.project,
- author: note1.author)
-
- expect(Banzai::Renderer).to receive(:render).
- with(note2.note,
- pipeline: :note,
- cache_key: [note2, "note"],
- project: note2.project,
- author: note2.author)
-
- note1.all_references
- note2.all_references
+ expect(Banzai::Renderer).to receive(:cache_collection_render).
+ with([{
+ text: note1.note,
+ context: {
+ pipeline: :note,
+ cache_key: [note1, "note"],
+ project: note1.project,
+ author: note1.author
+ }
+ }]).and_call_original
+
+ expect(Banzai::Renderer).to receive(:cache_collection_render).
+ with([{
+ text: note2.note,
+ context: {
+ pipeline: :note,
+ cache_key: [note2, "note"],
+ project: note2.project,
+ author: note2.author
+ }
+ }]).and_call_original
+
+ note1.all_references.users
+ note2.all_references.users
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index a2b089c51e2..9b017288488 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -377,7 +377,7 @@ describe Project, models: true do
describe '#repository' do
let(:project) { create(:project) }
- it 'should return valid repo' do
+ it 'returns valid repo' do
expect(project.repository).to be_kind_of(Repository)
end
end
@@ -458,44 +458,54 @@ describe Project, models: true do
end
end
- describe "#cache_has_external_wiki" do
+ describe '#external_wiki' do
let(:project) { create(:project) }
- it "stores true if there is an external wiki" do
- services = double(:service, external_wikis: [ExternalWikiService.new])
- expect(project).to receive(:services).and_return(services)
+ context 'with an active external wiki' do
+ before do
+ create(:service, project: project, type: 'ExternalWikiService', active: true)
+ project.external_wiki
+ end
- expect do
- project.cache_has_external_wiki
- end.to change { project.has_external_wiki }.to(true)
- end
+ it 'sets :has_external_wiki as true' do
+ expect(project.has_external_wiki).to be(true)
+ end
- it "stores false if there is no external wiki" do
- services = double(:service, external_wikis: [])
- expect(project).to receive(:services).and_return(services)
+ it 'sets :has_external_wiki as false if an external wiki service is destroyed later' do
+ expect(project.has_external_wiki).to be(true)
- expect do
- project.cache_has_external_wiki
- end.to change { project.has_external_wiki }.to(false)
+ project.services.external_wikis.first.destroy
+
+ expect(project.has_external_wiki).to be(false)
+ end
end
- it "changes to true if an external wiki service is created later" do
- expect do
- project.cache_has_external_wiki
- end.to change { project.has_external_wiki }.to(false)
+ context 'with an inactive external wiki' do
+ before do
+ create(:service, project: project, type: 'ExternalWikiService', active: false)
+ end
- expect do
- create(:service, type: "ExternalWikiService", project: project)
- end.to change { project.has_external_wiki }.to(true)
+ it 'sets :has_external_wiki as false' do
+ expect(project.has_external_wiki).to be(false)
+ end
end
- it "changes to false if an external wiki service is destroyed later" do
- service = create(:service, type: "ExternalWikiService", project: project)
- expect(project.has_external_wiki).to be_truthy
+ context 'with no external wiki' do
+ before do
+ project.external_wiki
+ end
- expect do
- service.destroy
- end.to change { project.has_external_wiki }.to(false)
+ it 'sets :has_external_wiki as false' do
+ expect(project.has_external_wiki).to be(false)
+ end
+
+ it 'sets :has_external_wiki as true if an external wiki service is created later' do
+ expect(project.has_external_wiki).to be(false)
+
+ create(:service, project: project, type: 'ExternalWikiService', active: true)
+
+ expect(project.has_external_wiki).to be(true)
+ end
end
end
@@ -1155,6 +1165,85 @@ describe Project, models: true do
end
end
+ describe '#latest_successful_builds_for' do
+ def create_pipeline(status = 'success')
+ create(:ci_pipeline, project: project,
+ sha: project.commit.sha,
+ ref: project.default_branch,
+ status: status)
+ end
+
+ def create_build(new_pipeline = pipeline, name = 'test')
+ create(:ci_build, :success, :artifacts,
+ pipeline: new_pipeline,
+ status: new_pipeline.status,
+ name: name)
+ end
+
+ let(:project) { create(:project) }
+ let(:pipeline) { create_pipeline }
+
+ context 'with many builds' do
+ it 'gives the latest builds from latest pipeline' do
+ pipeline1 = create_pipeline
+ pipeline2 = create_pipeline
+ build1_p2 = create_build(pipeline2, 'test')
+ create_build(pipeline1, 'test')
+ create_build(pipeline1, 'test2')
+ build2_p2 = create_build(pipeline2, 'test2')
+
+ latest_builds = project.latest_successful_builds_for
+
+ expect(latest_builds).to contain_exactly(build2_p2, build1_p2)
+ end
+ end
+
+ context 'with succeeded pipeline' do
+ let!(:build) { create_build }
+
+ context 'standalone pipeline' do
+ it 'returns builds for ref for default_branch' do
+ builds = project.latest_successful_builds_for
+
+ expect(builds).to contain_exactly(build)
+ end
+
+ it 'returns empty relation if the build cannot be found' do
+ builds = project.latest_successful_builds_for('TAIL')
+
+ expect(builds).to be_kind_of(ActiveRecord::Relation)
+ expect(builds).to be_empty
+ end
+ end
+
+ context 'with some pending pipeline' do
+ before do
+ create_build(create_pipeline('pending'))
+ end
+
+ it 'gives the latest build from latest pipeline' do
+ latest_build = project.latest_successful_builds_for
+
+ expect(latest_build).to contain_exactly(build)
+ end
+ end
+ end
+
+ context 'with pending pipeline' do
+ before do
+ pipeline.update(status: 'pending')
+ create_build(pipeline)
+ end
+
+ it 'returns empty relation' do
+ builds = project.latest_successful_builds_for
+
+ expect(builds).to be_kind_of(ActiveRecord::Relation)
+ expect(builds).to be_empty
+ end
+ end
+ end
+
describe '.where_paths_in' do
context 'without any paths' do
it 'returns an empty relation' do
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 59c5732c075..3e133143bbc 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -50,8 +50,9 @@ describe Repository, models: true do
double_first = double(committed_date: Time.now)
double_last = double(committed_date: Time.now - 1.second)
- allow(repository).to receive(:commit).with(tag_a.target).and_return(double_first)
- allow(repository).to receive(:commit).with(tag_b.target).and_return(double_last)
+ allow(tag_a).to receive(:target).and_return(double_first)
+ allow(tag_b).to receive(:target).and_return(double_last)
+ allow(repository).to receive(:tags).and_return([tag_a, tag_b])
end
it { is_expected.to eq(['v1.0.0', 'v1.1.0']) }
@@ -64,8 +65,9 @@ describe Repository, models: true do
double_first = double(committed_date: Time.now - 1.second)
double_last = double(committed_date: Time.now)
- allow(repository).to receive(:commit).with(tag_a.target).and_return(double_last)
- allow(repository).to receive(:commit).with(tag_b.target).and_return(double_first)
+ allow(tag_a).to receive(:target).and_return(double_last)
+ allow(tag_b).to receive(:target).and_return(double_first)
+ allow(repository).to receive(:tags).and_return([tag_a, tag_b])
end
it { is_expected.to eq(['v1.1.0', 'v1.0.0']) }
@@ -1161,41 +1163,6 @@ describe Repository, models: true do
end
end
- describe '#local_branches' do
- it 'returns the local branches' do
- masterrev = repository.find_branch('master').target
- create_remote_branch('joe', 'remote_branch', masterrev)
- repository.add_branch(user, 'local_branch', masterrev)
-
- expect(repository.local_branches.any? { |branch| branch.name == 'remote_branch' }).to eq(false)
- expect(repository.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true)
- end
- end
-
- describe '.clean_old_archives' do
- let(:path) { Gitlab.config.gitlab.repository_downloads_path }
-
- context 'when the downloads directory does not exist' do
- it 'does not remove any archives' do
- expect(File).to receive(:directory?).with(path).and_return(false)
-
- expect(Gitlab::Popen).not_to receive(:popen)
-
- described_class.clean_old_archives
- end
- end
-
- context 'when the downloads directory exists' do
- it 'removes old archives' do
- expect(File).to receive(:directory?).with(path).and_return(true)
-
- expect(Gitlab::Popen).to receive(:popen)
-
- described_class.clean_old_archives
- end
- end
- end
-
describe "#keep_around" do
it "stores a reference to the specified commit sha so it isn't garbage collected" do
repository.keep_around(sample_commit.id)
@@ -1203,9 +1170,4 @@ describe Repository, models: true do
expect(repository.kept_around?(sample_commit.id)).to be_truthy
end
end
-
- def create_remote_branch(remote_name, branch_name, target)
- rugged = repository.rugged
- rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target)
- end
end
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index f5b39c3d698..86a7b242fbe 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -1,15 +1,15 @@
require 'spec_helper'
-describe API::API, api: true do
+describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
let(:api_user) { user }
- let(:user2) { create(:user) }
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, sha: project.commit.id) }
+ let(:reporter) { create(:project_member, :reporter, project: project) }
+ let(:guest) { create(:project_member, :guest, project: project) }
+ let!(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.id, ref: project.default_branch) }
let!(:build) { create(:ci_build, pipeline: pipeline) }
describe 'GET /projects/:id/builds ' do
@@ -172,10 +172,104 @@ describe API::API, api: true do
end
end
+ describe 'GET /projects/:id/artifacts/:ref_name/download?job=name' do
+ let(:api_user) { reporter.user }
+ let(:build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
+
+ def path_for_ref(ref = pipeline.ref, job = build.name)
+ api("/projects/#{project.id}/builds/artifacts/#{ref}/download?job=#{job}", api_user)
+ end
+
+ context 'when not logged in' do
+ let(:api_user) { nil }
+
+ before do
+ get path_for_ref
+ end
+
+ it 'gives 401' do
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'when logging as guest' do
+ let(:api_user) { guest.user }
+
+ before do
+ get path_for_ref
+ end
+
+ it 'gives 403' do
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'non-existing build' do
+ shared_examples 'not found' do
+ it { expect(response).to have_http_status(:not_found) }
+ end
+
+ context 'has no such ref' do
+ before do
+ get path_for_ref('TAIL', build.name)
+ end
+
+ it_behaves_like 'not found'
+ end
+
+ context 'has no such build' do
+ before do
+ get path_for_ref(pipeline.ref, 'NOBUILD')
+ end
+
+ it_behaves_like 'not found'
+ end
+ end
+
+ context 'find proper build' do
+ shared_examples 'a valid file' do
+ let(:download_headers) do
+ { 'Content-Transfer-Encoding' => 'binary',
+ 'Content-Disposition' =>
+ "attachment; filename=#{build.artifacts_file.filename}" }
+ end
+
+ it { expect(response).to have_http_status(200) }
+ it { expect(response.headers).to include(download_headers) }
+ end
+
+ context 'with regular branch' do
+ before do
+ pipeline.update(ref: 'master',
+ sha: project.commit('master').sha)
+
+ get path_for_ref('master')
+ end
+
+ it_behaves_like 'a valid file'
+ end
+
+ context 'with branch name containing slash' do
+ before do
+ pipeline.update(ref: 'improve/awesome',
+ sha: project.commit('improve/awesome').sha)
+ end
+
+ before do
+ get path_for_ref('improve/awesome')
+ end
+
+ it_behaves_like 'a valid file'
+ end
+ end
+ end
+
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) }
+ before do
+ get api("/projects/#{project.id}/builds/#{build.id}/trace", api_user)
+ end
context 'authorized user' do
it 'should return specific build trace' do
@@ -205,7 +299,7 @@ describe API::API, api: true do
end
context 'user without :update_build permission' do
- let(:api_user) { user2 }
+ let(:api_user) { reporter.user }
it 'should not cancel build' do
expect(response).to have_http_status(403)
@@ -237,7 +331,7 @@ describe API::API, api: true do
end
context 'user without :update_build permission' do
- let(:api_user) { user2 }
+ let(:api_user) { reporter.user }
it 'should not retry build' do
expect(response).to have_http_status(403)
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index afa0599807f..8c6a7e6529d 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -396,7 +396,6 @@ describe API::API, api: true do
expect(json_response['alt']).to eq("dk")
expect(json_response['url']).to start_with("/uploads/")
expect(json_response['url']).to end_with("/dk.png")
- expect(json_response['is_image']).to eq(true)
end
end
diff --git a/spec/services/issues/bulk_update_service_spec.rb b/spec/services/issues/bulk_update_service_spec.rb
index ba3a4dfc048..321b54ac39d 100644
--- a/spec/services/issues/bulk_update_service_spec.rb
+++ b/spec/services/issues/bulk_update_service_spec.rb
@@ -1,118 +1,106 @@
require 'spec_helper'
describe Issues::BulkUpdateService, services: true do
- let(:user) { create(:user) }
- let(:project) { Projects::CreateService.new(user, namespace: user.namespace, name: 'test').execute }
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project, namespace: user.namespace) }
- let!(:result) { Issues::BulkUpdateService.new(project, user, params).execute }
+ def bulk_update(issues, extra_params = {})
+ bulk_update_params = extra_params
+ .reverse_merge(issues_ids: Array(issues).map(&:id).join(','))
- describe :close_issue do
- let(:issues) { create_list(:issue, 5, project: project) }
- let(:params) do
- {
- state_event: 'close',
- issues_ids: issues.map(&:id).join(',')
- }
- end
+ Issues::BulkUpdateService.new(project, user, bulk_update_params).execute
+ end
+
+ describe 'close issues' do
+ let(:issues) { create_list(:issue, 2, project: project) }
it 'succeeds and returns the correct number of issues updated' do
+ result = bulk_update(issues, state_event: 'close')
+
expect(result[:success]).to be_truthy
expect(result[:count]).to eq(issues.count)
end
it 'closes all the issues passed' do
+ bulk_update(issues, state_event: 'close')
+
expect(project.issues.opened).to be_empty
expect(project.issues.closed).not_to be_empty
end
end
- describe :reopen_issues do
- let(:issues) { create_list(:closed_issue, 5, project: project) }
- let(:params) do
- {
- state_event: 'reopen',
- issues_ids: issues.map(&:id).join(',')
- }
- end
+ describe 'reopen issues' do
+ let(:issues) { create_list(:closed_issue, 2, project: project) }
it 'succeeds and returns the correct number of issues updated' do
+ result = bulk_update(issues, state_event: 'reopen')
+
expect(result[:success]).to be_truthy
expect(result[:count]).to eq(issues.count)
end
it 'reopens all the issues passed' do
+ bulk_update(issues, state_event: 'reopen')
+
expect(project.issues.closed).to be_empty
expect(project.issues.opened).not_to be_empty
end
end
describe 'updating assignee' do
- let(:issue) do
- create(:issue, project: project) { |issue| issue.update_attributes(assignee: user) }
- end
-
- let(:params) do
- {
- assignee_id: assignee_id,
- issues_ids: issue.id.to_s
- }
- end
+ let(:issue) { create(:issue, project: project, assignee: user) }
context 'when the new assignee ID is a valid user' do
- let(:new_assignee) { create(:user) }
- let(:assignee_id) { new_assignee.id }
-
it 'succeeds' do
+ result = bulk_update(issue, assignee_id: create(:user).id)
+
expect(result[:success]).to be_truthy
expect(result[:count]).to eq(1)
end
it 'updates the assignee to the use ID passed' do
- expect(issue.reload.assignee).to eq(new_assignee)
+ assignee = create(:user)
+
+ expect { bulk_update(issue, assignee_id: assignee.id) }
+ .to change { issue.reload.assignee }.from(user).to(assignee)
end
end
context 'when the new assignee ID is -1' do
- let(:assignee_id) { -1 }
-
it 'unassigns the issues' do
- expect(issue.reload.assignee).to be_nil
+ expect { bulk_update(issue, assignee_id: -1) }
+ .to change { issue.reload.assignee }.to(nil)
end
end
context 'when the new assignee ID is not present' do
- let(:assignee_id) { nil }
-
it 'does not unassign' do
- expect(issue.reload.assignee).to eq(user)
+ expect { bulk_update(issue, assignee_id: nil) }
+ .not_to change { issue.reload.assignee }
end
end
end
describe 'updating milestones' do
- let(:issue) { create(:issue, project: project) }
+ let(:issue) { create(:issue, project: project) }
let(:milestone) { create(:milestone, project: project) }
- let(:params) do
- {
- issues_ids: issue.id.to_s,
- milestone_id: milestone.id
- }
- end
-
it 'succeeds' do
+ result = bulk_update(issue, milestone_id: milestone.id)
+
expect(result[:success]).to be_truthy
expect(result[:count]).to eq(1)
end
it 'updates the issue milestone' do
- expect(project.issues.first.milestone).to eq(milestone)
+ expect { bulk_update(issue, milestone_id: milestone.id) }
+ .to change { issue.reload.milestone }.from(nil).to(milestone)
end
end
describe 'updating labels' do
def create_issue_with_labels(labels)
- create(:issue, project: project) { |issue| issue.update_attributes(labels: labels) }
+ create(:labeled_issue, project: project, labels: labels)
end
let(:bug) { create(:label, project: project) }
@@ -129,15 +117,18 @@ describe Issues::BulkUpdateService, services: true do
let(:add_labels) { [] }
let(:remove_labels) { [] }
- let(:params) do
+ let(:bulk_update_params) do
{
- label_ids: labels.map(&:id),
- add_label_ids: add_labels.map(&:id),
+ label_ids: labels.map(&:id),
+ add_label_ids: add_labels.map(&:id),
remove_label_ids: remove_labels.map(&:id),
- issues_ids: issues.map(&:id).join(',')
}
end
+ before do
+ bulk_update(issues, bulk_update_params)
+ end
+
context 'when label_ids are passed' do
let(:issues) { [issue_all_labels, issue_no_labels] }
let(:labels) { [bug, regression] }
@@ -263,40 +254,28 @@ describe Issues::BulkUpdateService, services: true do
end
end
- describe :subscribe_issues do
- let(:issues) { create_list(:issue, 5, project: project) }
- let(:params) do
- {
- subscription_event: 'subscribe',
- issues_ids: issues.map(&:id).join(',')
- }
- end
+ describe 'subscribe to issues' do
+ let(:issues) { create_list(:issue, 2, project: project) }
it 'subscribes the given user' do
- issues.each do |issue|
- expect(issue.subscribed?(user)).to be_truthy
- end
- end
- end
+ bulk_update(issues, subscription_event: 'subscribe')
- describe :unsubscribe_issues do
- let(:issues) { create_list(:closed_issue, 5, project: project) }
- let(:params) do
- {
- subscription_event: 'unsubscribe',
- issues_ids: issues.map(&:id).join(',')
- }
+ expect(issues).to all(be_subscribed(user))
end
+ end
- before do
- issues.each do |issue|
+ describe 'unsubscribe from issues' do
+ let(:issues) do
+ create_list(:closed_issue, 2, project: project) do |issue|
issue.subscriptions.create(user: user, subscribed: true)
end
end
it 'unsubscribes the given user' do
+ bulk_update(issues, subscription_event: 'unsubscribe')
+
issues.each do |issue|
- expect(issue.subscribed?(user)).to be_falsey
+ expect(issue).not_to be_subscribed(user)
end
end
end
diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb
index f252e2c5902..122a7cea2a1 100644
--- a/spec/services/projects/download_service_spec.rb
+++ b/spec/services/projects/download_service_spec.rb
@@ -35,8 +35,6 @@ describe Projects::DownloadService, services: true do
it { expect(@link_to_file).to have_key(:alt) }
it { expect(@link_to_file).to have_key(:url) }
- it { expect(@link_to_file).to have_key(:is_image) }
- it { expect(@link_to_file[:is_image]).to be true }
it { expect(@link_to_file[:url]).to match('rails_sample.jpg') }
it { expect(@link_to_file[:alt]).to eq('rails_sample') }
end
@@ -49,8 +47,6 @@ describe Projects::DownloadService, services: true do
it { expect(@link_to_file).to have_key(:alt) }
it { expect(@link_to_file).to have_key(:url) }
- it { expect(@link_to_file).to have_key(:is_image) }
- it { expect(@link_to_file[:is_image]).to be false }
it { expect(@link_to_file[:url]).to match('doc_sample.txt') }
it { expect(@link_to_file[:alt]).to eq('doc_sample.txt') }
end
diff --git a/spec/services/projects/upload_service_spec.rb b/spec/services/projects/upload_service_spec.rb
index 9268a9fb1a2..c42eeba4b9c 100644
--- a/spec/services/projects/upload_service_spec.rb
+++ b/spec/services/projects/upload_service_spec.rb
@@ -15,9 +15,7 @@ describe Projects::UploadService, services: true do
it { expect(@link_to_file).to have_key(:alt) }
it { expect(@link_to_file).to have_key(:url) }
- it { expect(@link_to_file).to have_key(:is_image) }
it { expect(@link_to_file).to have_value('banana_sample') }
- it { expect(@link_to_file[:is_image]).to equal(true) }
it { expect(@link_to_file[:url]).to match('banana_sample.gif') }
end
@@ -31,8 +29,6 @@ describe Projects::UploadService, services: true do
it { expect(@link_to_file).to have_key(:alt) }
it { expect(@link_to_file).to have_key(:url) }
it { expect(@link_to_file).to have_value('dk') }
- it { expect(@link_to_file).to have_key(:is_image) }
- it { expect(@link_to_file[:is_image]).to equal(true) }
it { expect(@link_to_file[:url]).to match('dk.png') }
end
@@ -44,9 +40,7 @@ describe Projects::UploadService, services: true do
it { expect(@link_to_file).to have_key(:alt) }
it { expect(@link_to_file).to have_key(:url) }
- it { expect(@link_to_file).to have_key(:is_image) }
it { expect(@link_to_file).to have_value('rails_sample') }
- it { expect(@link_to_file[:is_image]).to equal(true) }
it { expect(@link_to_file[:url]).to match('rails_sample.jpg') }
end
@@ -58,9 +52,7 @@ describe Projects::UploadService, services: true do
it { expect(@link_to_file).to have_key(:alt) }
it { expect(@link_to_file).to have_key(:url) }
- it { expect(@link_to_file).to have_key(:is_image) }
it { expect(@link_to_file).to have_value('doc_sample.txt') }
- it { expect(@link_to_file[:is_image]).to equal(false) }
it { expect(@link_to_file[:url]).to match('doc_sample.txt') }
end
diff --git a/spec/services/repository_archive_clean_up_service_spec.rb b/spec/services/repository_archive_clean_up_service_spec.rb
new file mode 100644
index 00000000000..842585f9e54
--- /dev/null
+++ b/spec/services/repository_archive_clean_up_service_spec.rb
@@ -0,0 +1,81 @@
+require 'spec_helper'
+
+describe RepositoryArchiveCleanUpService, services: true do
+ describe '#execute' do
+ subject(:service) { described_class.new }
+
+ context 'when the downloads directory does not exist' do
+ it 'does not remove any archives' do
+ path = '/invalid/path/'
+ stub_repository_downloads_path(path)
+
+ expect(File).to receive(:directory?).with(path).and_return(false)
+ expect(service).not_to receive(:clean_up_old_archives)
+ expect(service).not_to receive(:clean_up_empty_directories)
+
+ service.execute
+ end
+ end
+
+ context 'when the downloads directory exists' do
+ shared_examples 'invalid archive files' do |dirname, extensions, mtime|
+ it 'does not remove files and directoy' do
+ in_directory_with_files(dirname, extensions, mtime) do |dir, files|
+ service.execute
+
+ files.each { |file| expect(File.exist?(file)).to eq true }
+ expect(File.directory?(dir)).to eq true
+ end
+ end
+ end
+
+ it 'removes files older than 2 hours that matches valid archive extensions' do
+ in_directory_with_files('sample.git', %w[tar tar.bz2 tar.gz zip], 2.hours) do |dir, files|
+ service.execute
+
+ files.each { |file| expect(File.exist?(file)).to eq false }
+ expect(File.directory?(dir)).to eq false
+ end
+ end
+
+ context 'with files older than 2 hours that does not matches valid archive extensions' do
+ it_behaves_like 'invalid archive files', 'sample.git', %w[conf rb], 2.hours
+ end
+
+ context 'with files older than 2 hours inside invalid directories' do
+ it_behaves_like 'invalid archive files', 'john_doe/sample.git', %w[conf rb tar tar.gz], 2.hours
+ end
+
+ context 'with files newer than 2 hours that matches valid archive extensions' do
+ it_behaves_like 'invalid archive files', 'sample.git', %w[tar tar.bz2 tar.gz zip], 1.hour
+ end
+
+ context 'with files newer than 2 hours that does not matches valid archive extensions' do
+ it_behaves_like 'invalid archive files', 'sample.git', %w[conf rb], 1.hour
+ end
+
+ context 'with files newer than 2 hours inside invalid directories' do
+ it_behaves_like 'invalid archive files', 'sample.git', %w[conf rb tar tar.gz], 1.hour
+ end
+ end
+
+ def in_directory_with_files(dirname, extensions, mtime)
+ Dir.mktmpdir do |tmpdir|
+ stub_repository_downloads_path(tmpdir)
+ dir = File.join(tmpdir, dirname)
+ files = create_temporary_files(dir, extensions, mtime)
+
+ yield(dir, files)
+ end
+ end
+
+ def stub_repository_downloads_path(path)
+ allow(Gitlab.config.gitlab).to receive(:repository_downloads_path).and_return(path)
+ end
+
+ def create_temporary_files(dir, extensions, mtime)
+ FileUtils.mkdir_p(dir)
+ FileUtils.touch(extensions.map { |ext| File.join(dir, "sample.#{ext}") }, mtime: Time.now - mtime)
+ end
+ end
+end
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index e005058ba5b..8c98b1f988c 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -178,6 +178,17 @@ module MarkdownMatchers
expect(actual).to have_selector('span.idiff.deletion', count: 2)
end
end
+
+ # VideoLinkFilter
+ matcher :parse_video_links do
+ set_default_markdown_messages
+
+ match do |actual|
+ video = actual.at_css('video')
+
+ expect(video['src']).to end_with('/assets/videos/gitlab-demo.mp4')
+ end
+ end
end
# Monkeypatch the matcher DSL so that we can reduce some noisy duplication for
diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb
new file mode 100644
index 00000000000..e8300abed5d
--- /dev/null
+++ b/spec/uploaders/file_uploader_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe FileUploader do
+ let(:project) { create(:project) }
+
+ before do
+ @previous_enable_processing = FileUploader.enable_processing
+ FileUploader.enable_processing = false
+ @uploader = FileUploader.new(project)
+ end
+
+ after do
+ FileUploader.enable_processing = @previous_enable_processing
+ @uploader.remove!
+ end
+
+ describe '#image_or_video?' do
+ context 'given an image file' do
+ before do
+ @uploader.store!(File.new(Rails.root.join('spec', 'fixtures', 'rails_sample.jpg')))
+ end
+
+ it 'detects an image based on file extension' do
+ expect(@uploader.image_or_video?).to be true
+ end
+ end
+
+ context 'given an video file' do
+ before do
+ video_file = File.new(Rails.root.join('spec', 'fixtures', 'video_sample.mp4'))
+ @uploader.store!(video_file)
+ end
+
+ it 'detects a video based on file extension' do
+ expect(@uploader.image_or_video?).to be true
+ end
+ end
+
+ it 'does not return image_or_video? for other types' do
+ @uploader.store!(File.new(Rails.root.join('spec', 'fixtures', 'doc_sample.txt')))
+
+ expect(@uploader.image_or_video?).to be false
+ end
+ end
+end
diff --git a/spec/workers/emails_on_push_worker_spec.rb b/spec/workers/emails_on_push_worker_spec.rb
index 439da765c2c..796751efe8d 100644
--- a/spec/workers/emails_on_push_worker_spec.rb
+++ b/spec/workers/emails_on_push_worker_spec.rb
@@ -12,6 +12,42 @@ describe EmailsOnPushWorker do
subject { EmailsOnPushWorker.new }
describe "#perform" do
+ context "when push is a new branch" do
+ let(:email) { ActionMailer::Base.deliveries.last }
+
+ before do
+ data_new_branch = data.stringify_keys.merge("before" => Gitlab::Git::BLANK_SHA)
+
+ subject.perform(project.id, recipients, data_new_branch)
+ end
+
+ it "sends a mail with the correct subject" do
+ expect(email.subject).to include("Pushed new branch")
+ end
+
+ it "sends the mail to the correct recipient" do
+ expect(email.to).to eq([user.email])
+ end
+ end
+
+ context "when push is a deleted branch" do
+ let(:email) { ActionMailer::Base.deliveries.last }
+
+ before do
+ data_deleted_branch = data.stringify_keys.merge("after" => Gitlab::Git::BLANK_SHA)
+
+ subject.perform(project.id, recipients, data_deleted_branch)
+ end
+
+ it "sends a mail with the correct subject" do
+ expect(email.subject).to include("Deleted branch")
+ end
+
+ it "sends the mail to the correct recipient" do
+ expect(email.to).to eq([user.email])
+ end
+ end
+
context "when there are no errors in sending" do
let(:email) { ActionMailer::Base.deliveries.last }