Commit 9f80a23a authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'ce-to-ee-2017-09-22' into 'master'

CE upstream: Friday

See merge request gitlab-org/gitlab-ee!2999
parents 3cca7d4d 4d8f758e
9.6.0-pre 10.1.0-pre
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
import '../lib/utils/url_utility'; import '../lib/utils/url_utility';
import { HIDDEN_CLASS } from '../lib/utils/constants'; import { HIDDEN_CLASS } from '../lib/utils/constants';
import csrf from '../lib/utils/csrf';
function toggleLoading($el, $icon, loading) { function toggleLoading($el, $icon, loading) {
if (loading) { if (loading) {
...@@ -36,9 +37,7 @@ export default class BlobFileDropzone { ...@@ -36,9 +37,7 @@ export default class BlobFileDropzone {
maxFiles: 1, maxFiles: 1,
addRemoveLinks: true, addRemoveLinks: true,
previewsContainer: '.dropzone-previews', previewsContainer: '.dropzone-previews',
headers: { headers: csrf.headers,
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content'),
},
init: function () { init: function () {
this.on('addedfile', function () { this.on('addedfile', function () {
toggleLoading(submitButton, submitButtonLoadingIcon, false); toggleLoading(submitButton, submitButtonLoadingIcon, false);
......
...@@ -11,15 +11,23 @@ ...@@ -11,15 +11,23 @@
function ImageFile(file) { function ImageFile(file) {
this.file = file; this.file = file;
this.requestImageInfo($('.two-up.view .frame.deleted img', this.file), (function(_this) { this.requestImageInfo($('.two-up.view .frame.deleted img', this.file), (function(_this) {
// Determine if old and new file has same dimensions, if not show 'two-up' view
return function(deletedWidth, deletedHeight) { return function(deletedWidth, deletedHeight) {
return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(width, height) { return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(width, height) {
if (width === deletedWidth && height === deletedHeight) { _this.initViewModes();
return _this.initViewModes();
} else { // Load two-up view after images are loaded
return _this.initView('two-up'); // so that we can display the correct width and height information
const images = $('.two-up.view img', _this.file);
let loadedCount = 0;
images.on('load', () => {
loadedCount += 1;
if (loadedCount === images.length) {
_this.initView('two-up');
} }
}); });
});
}; };
})(this)); })(this));
} }
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
/* global Dropzone */ /* global Dropzone */
import _ from 'underscore'; import _ from 'underscore';
import './preview_markdown'; import './preview_markdown';
import csrf from './lib/utils/csrf';
window.DropzoneInput = (function() { window.DropzoneInput = (function() {
function DropzoneInput(form) { function DropzoneInput(form) {
...@@ -50,9 +51,7 @@ window.DropzoneInput = (function() { ...@@ -50,9 +51,7 @@ window.DropzoneInput = (function() {
paramName: 'file', paramName: 'file',
maxFilesize: maxFileSize, maxFilesize: maxFileSize,
uploadMultiple: false, uploadMultiple: false,
headers: { headers: csrf.headers,
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
},
previewContainer: false, previewContainer: false,
processing: function() { processing: function() {
return $('.div-dropzone-alert').alert('close'); return $('.div-dropzone-alert').alert('close');
...@@ -260,9 +259,7 @@ window.DropzoneInput = (function() { ...@@ -260,9 +259,7 @@ window.DropzoneInput = (function() {
dataType: 'json', dataType: 'json',
processData: false, processData: false,
contentType: false, contentType: false,
headers: { headers: csrf.headers,
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
},
beforeSend: function() { beforeSend: function() {
showSpinner(); showSpinner();
return closeAlertMessage(); return closeAlertMessage();
......
/*
This module provides easy access to the CSRF token and caches
it for re-use. It also exposes some values commonly used in relation
to the CSRF token (header key and headers object).
If you need to refresh the csrfToken for some reason, just call `init` and
then use the accessors as you would normally.
If you need to compose a headers object, use the spread operator:
```
headers: {
...csrf.headers,
someOtherHeader: '12345',
}
```
*/
const csrf = {
init() {
const tokenEl = document.querySelector('meta[name=csrf-token]');
if (tokenEl !== null) {
this.csrfToken = tokenEl.getAttribute('content');
} else {
this.csrfToken = null;
}
},
get token() {
return this.csrfToken;
},
get headerKey() {
return 'X-CSRF-Token';
},
get headers() {
if (this.csrfToken !== null) {
return {
[this.headerKey]: this.token,
};
}
return {};
},
};
csrf.init();
// use our cached token for any $.rails-generated AJAX requests
if ($.rails) {
$.rails.csrfToken = () => csrf.token;
}
export default csrf;
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
/* global Flash, Autosave */ /* global Flash, Autosave */
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import _ from 'underscore'; import _ from 'underscore';
import autosize from 'vendor/autosize';
import '../../autosave'; import '../../autosave';
import TaskList from '../../task_list'; import TaskList from '../../task_list';
import * as constants from '../constants'; import * as constants from '../constants';
...@@ -96,6 +97,8 @@ ...@@ -96,6 +97,8 @@
methods: { methods: {
...mapActions([ ...mapActions([
'saveNote', 'saveNote',
'stopPolling',
'restartPolling',
'removePlaceholderNotes', 'removePlaceholderNotes',
]), ]),
setIsSubmitButtonDisabled(note, isSubmitting) { setIsSubmitButtonDisabled(note, isSubmitting) {
...@@ -124,10 +127,14 @@ ...@@ -124,10 +127,14 @@
} }
this.isSubmitting = true; this.isSubmitting = true;
this.note = ''; // Empty textarea while being requested. Repopulate in catch this.note = ''; // Empty textarea while being requested. Repopulate in catch
this.resizeTextarea();
this.stopPolling();
this.saveNote(noteData) this.saveNote(noteData)
.then((res) => { .then((res) => {
this.isSubmitting = false; this.isSubmitting = false;
this.restartPolling();
if (res.errors) { if (res.errors) {
if (res.errors.commands_only) { if (res.errors.commands_only) {
this.discard(); this.discard();
...@@ -174,6 +181,8 @@ ...@@ -174,6 +181,8 @@
if (shouldClear) { if (shouldClear) {
this.note = ''; this.note = '';
this.resizeTextarea();
this.$refs.markdownField.previewMarkdown = false;
} }
// reset autostave // reset autostave
...@@ -205,6 +214,11 @@ ...@@ -205,6 +214,11 @@
selector: '.notes', selector: '.notes',
}); });
}, },
resizeTextarea() {
this.$nextTick(() => {
autosize.update(this.$refs.textarea);
});
},
}, },
mounted() { mounted() {
// jQuery is needed here because it is a custom event being dispatched with jQuery. // jQuery is needed here because it is a custom event being dispatched with jQuery.
...@@ -247,7 +261,8 @@ ...@@ -247,7 +261,8 @@
:markdown-docs-path="markdownDocsPath" :markdown-docs-path="markdownDocsPath"
:quick-actions-docs-path="quickActionsDocsPath" :quick-actions-docs-path="quickActionsDocsPath"
:add-spacing-classes="false" :add-spacing-classes="false"
:is-confidential-issue="isConfidentialIssue"> :is-confidential-issue="isConfidentialIssue"
ref="markdownField">
<textarea <textarea
id="note-body" id="note-body"
name="note[note]" name="note[note]"
......
...@@ -187,6 +187,14 @@ export const poll = ({ commit, state, getters }) => { ...@@ -187,6 +187,14 @@ export const poll = ({ commit, state, getters }) => {
}); });
}; };
export const stopPolling = () => {
eTagPoll.stop();
};
export const restartPolling = () => {
eTagPoll.restart();
};
export const fetchData = ({ commit, state, getters }) => { export const fetchData = ({ commit, state, getters }) => {
const requestData = { endpoint: state.notesData.notesPath, lastFetchedAt: state.lastFetchedAt }; const requestData = { endpoint: state.notesData.notesPath, lastFetchedAt: state.lastFetchedAt };
......
...@@ -5,6 +5,9 @@ import * as constants from '../constants'; ...@@ -5,6 +5,9 @@ import * as constants from '../constants';
export default { export default {
[types.ADD_NEW_NOTE](state, note) { [types.ADD_NEW_NOTE](state, note) {
const { discussion_id, type } = note; const { discussion_id, type } = note;
const [exists] = state.notes.filter(n => n.id === note.discussion_id);
if (!exists) {
const noteData = { const noteData = {
expanded: true, expanded: true,
id: discussion_id, id: discussion_id,
...@@ -14,6 +17,7 @@ export default { ...@@ -14,6 +17,7 @@ export default {
}; };
state.notes.push(noteData); state.notes.push(noteData);
}
}, },
[types.ADD_NEW_REPLY_TO_DISCUSSION](state, note) { [types.ADD_NEW_REPLY_TO_DISCUSSION](state, note) {
......
import Vue from 'vue'; import Vue from 'vue';
import VueResource from 'vue-resource'; import VueResource from 'vue-resource';
import csrf from '../lib/utils/csrf';
Vue.use(VueResource); Vue.use(VueResource);
...@@ -18,9 +19,7 @@ Vue.http.interceptors.push((request, next) => { ...@@ -18,9 +19,7 @@ Vue.http.interceptors.push((request, next) => {
// New Vue Resource version uses Headers, we are expecting a plain object to render pagination // New Vue Resource version uses Headers, we are expecting a plain object to render pagination
// and polling. // and polling.
Vue.http.interceptors.push((request, next) => { Vue.http.interceptors.push((request, next) => {
if ($.rails) { request.headers.set(csrf.headerKey, csrf.token);
request.headers.set('X-CSRF-Token', $.rails.csrfToken());
}
next((response) => { next((response) => {
// Headers object has a `forEach` property that iterates through all values. // Headers object has a `forEach` property that iterates through all values.
......
...@@ -3,8 +3,13 @@ class HelpController < ApplicationController ...@@ -3,8 +3,13 @@ class HelpController < ApplicationController
layout 'help' layout 'help'
# Taken from Jekyll
# https://github.com/jekyll/jekyll/blob/3.5-stable/lib/jekyll/document.rb#L13
YAML_FRONT_MATTER_REGEXP = %r!\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)!m
def index def index
@help_index = File.read(Rails.root.join('doc', 'README.md')) # Remove YAML frontmatter so that it doesn't look weird
@help_index = File.read(Rails.root.join('doc', 'README.md')).sub(YAML_FRONT_MATTER_REGEXP, '')
# Prefix Markdown links with `help/` unless they are external links # Prefix Markdown links with `help/` unless they are external links
# See http://rubular.com/r/X3baHTbPO2 # See http://rubular.com/r/X3baHTbPO2
...@@ -22,7 +27,8 @@ class HelpController < ApplicationController ...@@ -22,7 +27,8 @@ class HelpController < ApplicationController
path = File.join(Rails.root, 'doc', "#{@path}.md") path = File.join(Rails.root, 'doc', "#{@path}.md")
if File.exist?(path) if File.exist?(path)
@markdown = File.read(path) # Remove YAML frontmatter so that it doesn't look weird
@markdown = File.read(path).gsub(YAML_FRONT_MATTER_REGEXP, '')
render 'show.html.haml' render 'show.html.haml'
else else
......
...@@ -176,13 +176,15 @@ module CommitsHelper ...@@ -176,13 +176,15 @@ module CommitsHelper
end end
end end
def view_file_button(commit_sha, diff_new_path, project) def view_file_button(commit_sha, diff_new_path, project, replaced: false)
title = replaced ? _('View replaced file @ ') : _('View file @ ')
link_to( link_to(
project_blob_path(project, project_blob_path(project,
tree_join(commit_sha, diff_new_path)), tree_join(commit_sha, diff_new_path)),
class: 'btn view-file js-view-file' class: 'btn view-file js-view-file'
) do ) do
raw('View file @ ') + content_tag(:span, Commit.truncate_sha(commit_sha), raw(title) + content_tag(:span, Commit.truncate_sha(commit_sha),
class: 'commit-sha') class: 'commit-sha')
end end
end end
......
- environment = local_assigns.fetch(:environment, nil) - environment = local_assigns.fetch(:environment, nil)
- file_hash = hexdigest(diff_file.file_path) - file_hash = hexdigest(diff_file.file_path)
- image_diff = diff_file.rich_viewer && diff_file.rich_viewer.partial_name == 'image'
- image_replaced = diff_file.old_content_sha && diff_file.old_content_sha != diff_file.content_sha
.diff-file.file-holder{ id: file_hash, data: diff_file_html_data(project, diff_file.file_path, diff_file.content_sha) } .diff-file.file-holder{ id: file_hash, data: diff_file_html_data(project, diff_file.file_path, diff_file.content_sha) }
.js-file-title.file-title-flex-parent .js-file-title.file-title-flex-parent
.file-header-content .file-header-content
...@@ -17,6 +20,9 @@ ...@@ -17,6 +20,9 @@
= edit_blob_link(@merge_request.source_project, @merge_request.source_branch, diff_file.new_path, = edit_blob_link(@merge_request.source_project, @merge_request.source_branch, diff_file.new_path,
blob: blob, link_opts: link_opts) blob: blob, link_opts: link_opts)
- if image_diff && image_replaced
= view_file_button(diff_file.old_content_sha, diff_file.old_path, project, replaced: true)
= view_file_button(diff_file.content_sha, diff_file.file_path, project) = view_file_button(diff_file.content_sha, diff_file.file_path, project)
= view_on_environment_button(diff_file.content_sha, diff_file.file_path, environment) if environment = view_on_environment_button(diff_file.content_sha, diff_file.file_path, environment) if environment
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
.two-up.view .two-up.view
%span.wrap %span.wrap
.frame.deleted .frame.deleted
%a{ href: project_blob_path(@project, tree_join(diff_file.old_content_sha, diff_file.old_path)) }
= image_tag(old_blob_raw_path, alt: diff_file.old_path) = image_tag(old_blob_raw_path, alt: diff_file.old_path)
%p.image-info.hide %p.image-info.hide
%span.meta-filesize= number_to_human_size(old_blob.size) %span.meta-filesize= number_to_human_size(old_blob.size)
...@@ -27,7 +26,6 @@ ...@@ -27,7 +26,6 @@
%span.meta-height %span.meta-height
%span.wrap %span.wrap
.frame.added .frame.added
%a{ href: project_blob_path(@project, tree_join(diff_file.content_sha, diff_file.new_path)) }
= image_tag(blob_raw_path, alt: diff_file.new_path) = image_tag(blob_raw_path, alt: diff_file.new_path)
%p.image-info.hide %p.image-info.hide
%span.meta-filesize= number_to_human_size(blob.size) %span.meta-filesize= number_to_human_size(blob.size)
......
...@@ -43,4 +43,4 @@ ...@@ -43,4 +43,4 @@
"empty-state-svg" => image_path('illustrations/issues.svg'), "empty-state-svg" => image_path('illustrations/issues.svg'),
":issue-link-base" => "issueLinkBase", ":issue-link-base" => "issueLinkBase",
":root-path" => "rootPath", ":root-path" => "rootPath",
":project-id" => @project.try(:id) } ":project-id" => @project.id }
---
title: Add view replaced file link for image diffs
merge_request:
author:
type: changed
---
title: Correctly detect multiple issue URLs after 'Closes...' in MR descriptions
merge_request:
author:
type: fixed
---
title: Display full pre-receive and post-receive hook output in GitLab UI
merge_request: 14222
author: Robin Bobbitt
type: fixed
---
title: Force two up view to load by default for image diffs
merge_request:
author:
type: fixed
---
title: Replace the 'project/service.feature' spinach test with an rspec analog
merge_request: 14432
author: Vitaliy @blackst0ne Klachkov
type: other
---
title: Removed two legacy config options
merge_request:
author: Daniel Voogsgerd
type: deprecated
...@@ -681,12 +681,6 @@ production: &base ...@@ -681,12 +681,6 @@ production: &base
# Use the default values unless you really know what you are doing # Use the default values unless you really know what you are doing
git: git:
bin_path: /usr/bin/git bin_path: /usr/bin/git
# The next value is the maximum memory size grit can use
# Given in number of bytes per git object (e.g. a commit)
# This value can be increased if you have very large commits
max_size: 20971520 # 20.megabytes
# Git timeout to read a commit, in seconds
timeout: 10
## Webpack settings ## Webpack settings
# If enabled, this will tell rails to serve frontend assets from the webpack-dev-server running # If enabled, this will tell rails to serve frontend assets from the webpack-dev-server running
......
...@@ -149,31 +149,19 @@ helm install --name gitlab --set baseDomain=gitlab.io,baseIP=1.1.1.1,gitlab=ee,g ...@@ -149,31 +149,19 @@ helm install --name gitlab --set baseDomain=gitlab.io,baseIP=1.1.1.1,gitlab=ee,g
## Updating GitLab using the Helm Chart ## Updating GitLab using the Helm Chart
Once your GitLab Chart is installed, configuration changes and chart updates Once your GitLab Chart is installed, configuration changes and chart updates
should we done using `helm upgrade` should we done using `helm upgrade`:
```bash ```bash
helm upgrade -f <CONFIG_VALUES_FILE> <RELEASE-NAME> gitlab/gitlab helm upgrade -f values.yaml gitlab gitlab/gitlab-omnibus
``` ```
where:
- `<CONFIG_VALUES_FILE>` is the path to values file containing your custom
[configuration] (#configuring-and-installing-gitlab).
- `<RELEASE-NAME>` is the name you gave the chart when installing it.
In the [Install section](#installing-gitlab-using-the-helm-chart) we called it `gitlab`.
## Uninstalling GitLab using the Helm Chart ## Uninstalling GitLab using the Helm Chart
To uninstall the GitLab Chart, run the following: To uninstall the GitLab Chart, run the following:
```bash ```bash
helm delete <RELEASE-NAME> helm delete gitlab
``` ```
where:
- `<RELEASE-NAME>` is the name you gave the chart when installing it.
In the [Install section](#installing) we called it `gitlab`.
[kube-srv]: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types [kube-srv]: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types
[storageclass]: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#storageclasses [storageclass]: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#storageclasses
Feature: Project Services
Background:
Given I sign in as a user
And I own project "Shop"
Scenario: I should see project services
When I visit project "Shop" services page
Then I should see list of available services
Scenario: Activate hipchat service
When I visit project "Shop" services page
And I click hipchat service link
And I fill hipchat settings
Then I should see the Hipchat success message
Scenario: Activate hipchat service with custom server
When I visit project "Shop" services page
And I click hipchat service link
And I fill hipchat settings with custom server
Then I should see the Hipchat success message
Scenario: Activate pivotaltracker service
When I visit project "Shop" services page
And I click pivotaltracker service link
And I fill pivotaltracker settings
Then I should see the Pivotaltracker success message
Scenario: Activate Flowdock service
When I visit project "Shop" services page
And I click Flowdock service link
And I fill Flowdock settings
Then I should see the Flowdock success message
Scenario: Activate Assembla service
When I visit project "Shop" services page
And I click Assembla service link
And I fill Assembla settings
Then I should see the Assembla success message
Scenario: Activate Slack notifications service
When I visit project "Shop" services page
And I click Slack notifications service link
And I fill Slack notifications settings
Then I should see the Slack notifications success message
Scenario: Activate Pushover service
When I visit project "Shop" services page
And I click Pushover service link
And I fill Pushover settings
Then I should see the Pushover success message
Scenario: Activate email on push service
When I visit project "Shop" services page
And I click email on push service link
And I fill email on push settings
Then I should see the Emails on push success message
Scenario: Activate JIRA service
When I visit project "Shop" services page
And I click jira service link
And I fill jira settings
Then I should see the JIRA success message
Scenario: Activate Irker (IRC Gateway) service
When I visit project "Shop" services page
And I click Irker service link
And I fill Irker settings
Then I should see the Irker success message
Scenario: Activate Atlassian Bamboo CI service
When I visit project "Shop" services page
And I click Atlassian Bamboo CI service link
And I fill Atlassian Bamboo CI settings
Then I should see the Bamboo success message
And I should see empty field Change Password
Scenario: Activate jetBrains TeamCity CI service
When I visit project "Shop" services page
And I click jetBrains TeamCity CI service link
And I fill jetBrains TeamCity CI settings
Then I should see the JetBrains success message
Scenario: Activate Asana service
When I visit project "Shop" services page
And I click Asana service link
And I fill Asana settings
Then I should see the Asana success message
...@@ -139,7 +139,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps ...@@ -139,7 +139,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end end
step 'The diff links to both the previous and current image' do step 'The diff links to both the previous and current image' do
links = page.all('.two-up span div a') links = page.all('.file-actions a')
expect(links[0]['href']).to match %r{blob/#{sample_image_commit.old_blob_id}} expect(links[0]['href']).to match %r{blob/#{sample_image_commit.old_blob_id}}
expect(links[1]['href']).to match %r{blob/#{sample_image_commit.new_blob_id}} expect(links[1]['href']).to match %r{blob/#{sample_image_commit.new_blob_id}}
end end
......
class Spinach::Features::ProjectServices < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
step 'I visit project "Shop" services page' do
visit project_settings_integrations_path(@project)
end
step 'I should see list of available services' do
expect(page).to have_content 'Project services'
expect(page).to have_content 'Campfire'
expect(page).to have_content 'HipChat'
expect(page).to have_content 'Assembla'
expect(page).to have_content 'Pushover'
expect(page).to have_content 'Atlassian Bamboo'
expect(page).to have_content 'JetBrains TeamCity'
expect(page).to have_content 'Asana'
expect(page).to have_content 'Irker (IRC gateway)'
end
step 'I should see service settings saved' do
expect(find_field('Active').value).to eq '1'
end
step 'I click hipchat service link' do
click_link 'HipChat'
end
step 'I fill hipchat settings' do
check 'Active'
fill_in 'Room', with: 'gitlab'
fill_in 'Token', with: 'verySecret'
click_button 'Save'
end
step 'I should see the Hipchat success message' do
expect(page).to have_content 'HipChat activated.'
end
step 'I fill hipchat settings with custom server' do
check 'Active'
fill_in 'Room', with: 'gitlab_custom'
fill_in 'Token', with: 'secretCustom'
fill_in 'Server', with: 'https://chat.example.com'
click_button 'Save'
end
step 'I click pivotaltracker service link' do
click_link 'PivotalTracker'
end
step 'I fill pivotaltracker settings' do
check 'Active'
fill_in 'Token', with: 'verySecret'
click_button 'Save'
end
step 'I should see the Pivotaltracker success message' do
expect(page).to have_content 'PivotalTracker activated.'
end
step 'I click Flowdock service link' do
click_link 'Flowdock'
end
step 'I fill Flowdock settings' do
check 'Active'
fill_in 'Token', with: 'verySecret'
click_button 'Save'
end
step 'I should see the Flowdock success message' do
expect(page).to have_content 'Flowdock activated.'
end
step 'I click Assembla service link' do
click_link 'Assembla'
end
step 'I fill Assembla settings' do
check 'Active'
fill_in 'Token', with: 'verySecret'
click_button 'Save'
end
step 'I should see the Assembla success message' do
expect(page).to have_content 'Assembla activated.'
end
step 'I click Asana service link' do
click_link 'Asana'
end
step 'I fill Asana settings' do
check 'Active'
fill_in 'Api key', with: 'verySecret'
fill_in 'Restrict to branch', with: 'master'
click_button 'Save'
end
step 'I should see the Asana success message' do
expect(page).to have_content 'Asana activated.'
end
step 'I click email on push service link' do
click_link 'Emails on push'
end
step 'I fill email on push settings' do
check 'Active'
fill_in 'Recipients', with: 'qa@company.name'
click_button 'Save'
end
step 'I should see the Emails on push success message' do
expect(page).to have_content 'Emails on push activated.'
end
step 'I click Irker service link' do
click_link 'Irker (IRC gateway)'
end
step 'I fill Irker settings' do
check 'Active'
fill_in 'Recipients', with: 'irc://chat.freenode.net/#commits'
check 'Colorize messages'
click_button 'Save'
end
step 'I should see the Irker success message' do
expect(page).to have_content 'Irker (IRC gateway) activated.'
end
step 'I click Slack notifications service link' do
click_link 'Slack notifications'
end
step 'I fill Slack notifications settings' do
check 'Active'
fill_in 'Webhook', with: 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685'
click_button 'Save'
end
step 'I should see the Slack notifications success message' do
expect(page).to have_content 'Slack notifications activated.'
end
step 'I click Pushover service link' do
click_link 'Pushover'
end
step 'I fill Pushover settings' do
check 'Active'
fill_in 'Api key', with: 'verySecret'
fill_in 'User key', with: 'verySecret'
fill_in 'Device', with: 'myDevice'
select 'High Priority', from: 'Priority'
select 'Bike', from: 'Sound'
click_button 'Save'
end
step 'I should see the Pushover success message' do
expect(page).to have_content 'Pushover activated.'
end
step 'I click jira service link' do
click_link 'JIRA'
end
step 'I fill jira settings' do
check 'Active'
fill_in 'Web URL', with: 'http://jira.example'
fill_in 'JIRA API URL', with: 'http://jira.example/api'
fill_in 'Username', with: 'gitlab'
fill_in 'Password', with: 'gitlab'
click_button 'Save'
end
step 'I should see the JIRA success message' do
expect(page).to have_content 'JIRA activated.'
end
step 'I click Atlassian Bamboo CI service link' do
click_link 'Atlassian Bamboo CI'
end
step 'I fill Atlassian Bamboo CI settings' do
check 'Active'
fill_in 'Bamboo url', with: 'http://bamboo.example.com'
fill_in 'Build key', with: 'KEY'
fill_in 'Username', with: 'user'
fill_in 'Password', with: 'verySecret'
click_button 'Save'
end
step 'I should see the Bamboo success message' do
expect(page).to have_content 'Atlassian Bamboo CI activated.'
end
step 'I should see empty field Change Password' do
click_link 'Atlassian Bamboo CI'
expect(find_field('Enter new password').value).to be_nil
end
step 'I click JetBrains TeamCity CI service link' do
click_link 'JetBrains TeamCity CI'
end
step 'I fill JetBrains TeamCity CI settings' do
check 'Active'
fill_in 'Teamcity url', with: 'http://teamcity.example.com'
fill_in 'Build type', with: 'GitlabTest_Build'
fill_in 'Username', with: 'user'
fill_in 'Password', with: 'verySecret'
click_button 'Save'
end
step 'I should see the JetBrains success message' do
expect(page).to have_content 'JetBrains TeamCity CI activated.'
end
end
...@@ -79,7 +79,7 @@ module Backup ...@@ -79,7 +79,7 @@ module Backup
# - 1495527122_gitlab_backup.tar # - 1495527122_gitlab_backup.tar
# - 1495527068_2017_05_23_gitlab_backup.tar # - 1495527068_2017_05_23_gitlab_backup.tar
# - 1495527097_2017_05_23_9.3.0-pre_gitlab_backup.tar # - 1495527097_2017_05_23_9.3.0-pre_gitlab_backup.tar
next unless file =~ /^(\d{10})(?:_\d{4}_\d{2}_\d{2}(_\d+\.\d+\.\d+((-|\.)(pre|rc\d))?)?)?_gitlab_backup\.tar$/ next unless file =~ /^(\d{10})(?:_\d{4}_\d{2}_\d{2}(_\d+\.\d+\.\d+((-|\.)(pre|rc\d))?(-ee)?)?)?_gitlab_backup\.tar$/
timestamp = $1.to_i timestamp = $1.to_i
......
module Gitlab module Gitlab
class ClosingIssueExtractor class ClosingIssueExtractor
ISSUE_CLOSING_REGEX = begin ISSUE_CLOSING_REGEX = begin
link_pattern = URI.regexp(%w(http https)) link_pattern = Banzai::Filter::AutolinkFilter::LINK_PATTERN
pattern = Gitlab.config.gitlab.issue_closing_pattern pattern = Gitlab.config.gitlab.issue_closing_pattern
pattern = pattern.sub('%{issue_ref}', "(?:(?:#{link_pattern})|(?:#{Issue.reference_pattern}))") pattern = pattern.sub('%{issue_ref}', "(?:(?:#{link_pattern})|(?:#{Issue.reference_pattern}))")
......
...@@ -83,13 +83,14 @@ module Gitlab ...@@ -83,13 +83,14 @@ module Gitlab
def call_update_hook(gl_id, oldrev, newrev, ref) def call_update_hook(gl_id, oldrev, newrev, ref)
Dir.chdir(repo_path) do Dir.chdir(repo_path) do
stdout, stderr, status = Open3.capture3({ 'GL_ID' => gl_id }, path, ref, oldrev, newrev) stdout, stderr, status = Open3.capture3({ 'GL_ID' => gl_id }, path, ref, oldrev, newrev)
[status.success?, stderr.presence || stdout] [status.success?, (stderr.presence || stdout).gsub(/\R/, "<br>").html_safe]
end end
end end
def retrieve_error_message(stderr, stdout) def retrieve_error_message(stderr, stdout)
err_message = stderr.gets err_message = stderr.read
err_message.blank? ? stdout.gets : err_message err_message = err_message.blank? ? stdout.read : err_message
err_message.gsub(/\R/, "<br>").html_safe
end end
end end
end end
......
...@@ -58,11 +58,11 @@ module Gitlab ...@@ -58,11 +58,11 @@ module Gitlab
end end
def repository_storages def repository_storages
@repository_storage ||= storages_paths.keys storages_paths.keys
end end
def storages_paths def storages_paths
@storage_paths ||= Gitlab.config.repositories.storages Gitlab.config.repositories.storages
end end
def exec_with_timeout(cmd_args, *args, &block) def exec_with_timeout(cmd_args, *args, &block)
......
...@@ -62,6 +62,7 @@ feature 'Diff file viewer', :js do ...@@ -62,6 +62,7 @@ feature 'Diff file viewer', :js do
end end
context 'Image file' do context 'Image file' do
context 'Replaced' do
before do before do
visit_commit('2f63565e7aac07bcdadb654e253078b727143ec4') visit_commit('2f63565e7aac07bcdadb654e253078b727143ec4')
end end
...@@ -71,6 +72,35 @@ feature 'Diff file viewer', :js do ...@@ -71,6 +72,35 @@ feature 'Diff file viewer', :js do
expect(page).to have_css('img[alt="files/images/6049019_460s.jpg"]') expect(page).to have_css('img[alt="files/images/6049019_460s.jpg"]')
end end
end end
it 'shows view replaced and view file links' do
expect(page.all('.file-actions a').length).to eq 2
expect(page.all('.file-actions a')[0]).to have_content 'View replaced file @'
expect(page.all('.file-actions a')[1]).to have_content 'View file @'
end
end
context 'Added' do
before do
visit_commit('33f3729a45c02fc67d00adb1b8bca394b0e761d9')
end
it 'shows view file link' do
expect(page.all('.file-actions a').length).to eq 1
expect(page.all('.file-actions a')[0]).to have_content 'View file @'
end
end
context 'Deleted' do
before do
visit_commit('7fd7a459706ee87be6f855fd98ce8c552b15529a')
end
it 'shows view file link' do
expect(page.all('.file-actions a').length).to eq 1
expect(page.all('.file-actions a')[0]).to have_content 'View file @'
end
end
end end
context 'ISO file (stored in LFS)' do context 'ISO file (stored in LFS)' do
......
require 'spec_helper'
feature 'Projects > Slack service > Setup events' do
let(:user) { create(:user) }
let(:service) { SlackService.new }
let(:project) { create(:project, slack_service: service) }
background do
service.fields
service.update_attributes(push_channel: 1, issue_channel: 2, merge_request_channel: 3, note_channel: 4, tag_push_channel: 5, pipeline_channel: 6, wiki_page_channel: 7)
project.team << [user, :master]
sign_in(user)
end
scenario 'user can filter events by channel' do
visit edit_project_service_path(project, service)
expect(page.find_field("service_push_channel").value).to have_content '1'
expect(page.find_field("service_issue_channel").value).to have_content '2'
expect(page.find_field("service_merge_request_channel").value).to have_content '3'
expect(page.find_field("service_note_channel").value).to have_content '4'
expect(page.find_field("service_tag_push_channel").value).to have_content '5'
expect(page.find_field("service_pipeline_channel").value).to have_content '6'
expect(page.find_field("service_wiki_page_channel").value).to have_content '7'
end
end
require 'spec_helper'
describe 'User activates Asana' do
let(:project) { create(:project) }
let(:user) { create(:user) }
before do
project.add_master(user)
sign_in(user)
visit(project_settings_integrations_path(project))
click_link('Asana')
end
it 'activates service' do
check('Active')
fill_in('Api key', with: 'verySecret')
fill_in('Restrict to branch', with: 'verySecret')
click_button('Save')
expect(page).to have_content('Asana activated.')
end
end
require 'spec_helper'
describe 'User activates Assembla' do
let(:project) { create(:project) }
let(:user) { create(:user) }
before do
project.add_master(user)
sign_in(user)
visit(project_settings_integrations_path(project))
click_link('Assembla')
end
it 'activates service' do
check('Active')
fill_in('Token', with: 'verySecret')
click_button('Save')
expect(page).to have_content('Assembla activated.')
end
end
require 'spec_helper'
describe 'User activates Atlassian Bamboo CI' do
let(:project) { create(:project) }
let(:user) { create(:user) }
before do
project.add_master(user)
sign_in(user)
visit(project_settings_integrations_path(project))
click_link('Atlassian Bamboo CI')
end
it 'activates service' do
check('Active')
fill_in('Bamboo url', with: 'http://bamboo.example.com')
fill_in('Build key', with: 'KEY')
fill_in('Username', with: 'user')
fill_in('Password', with: 'verySecret')
click_button('Save')
expect(page).to have_content('Atlassian Bamboo CI activated.')
# Password field should not be filled in.
click_link('Atlassian Bamboo CI')
expect(find_field('Enter new password').value).to be_nil
end
end
require 'spec_helper'
describe 'User activates Emails on push' do
let(:project) { create(:project) }
let(:user) { create(:user) }
before do
project.add_master(user)
sign_in(user)
visit(project_settings_integrations_path(project))
click_link('Emails on push')
end
it 'activates service' do
check('Active')
fill_in('Recipients', with: 'qa@company.name')
click_button('Save')
expect(page).to have_content('Emails on push activated.')
end
end
require 'spec_helper'
describe 'User activates Flowdock' do
let(:project) { create(:project) }
let(:user) { create(:user) }
before do
project.add_master(user)
sign_in(user)
visit(project_settings_integrations_path(project))
click_link('Flowdock')
end
it 'activates service' do
check('Active')
fill_in('Token', with: 'verySecret')
click_button('Save')
expect(page).to have_content('Flowdock activated.')
end
end
require 'spec_helper'
describe 'User activates HipChat' do
let(:project) { create(:project) }
let(:user) { create(:user) }
before do
project.add_master(user)
sign_in(user)
visit(project_settings_integrations_path(project))
click_link('HipChat')
end
context 'with standart settings' do
it 'activates service' do
check('Active')
fill_in('Room', with: 'gitlab')
fill_in('Token', with: 'verySecret')
click_button('Save')
expect(page).to have_content('HipChat activated.')
end
end
context 'with custom settings' do
it 'activates service' do
check('Active')
fill_in('Room', with: 'gitlab_custom')
fill_in('Token', with: 'secretCustom')
fill_in('Server', with: 'https://chat.example.com')
click_button('Save')
expect(page).to have_content('HipChat activated.')
end
end
end
require 'spec_helper'
describe 'User activates Irker (IRC gateway)' do
let(:project) { create(:project) }
let(:user) { create(:user) }
before do
project.add_master(user)
sign_in(user)
visit(project_settings_integrations_path(project))
click_link('Irker (IRC gateway)')
end
it 'activates service' do
check('Active')
check('Colorize messages')
fill_in('Recipients', with: 'irc://chat.freenode.net/#commits')
click_button('Save')
expect(page).to have_content('Irker (IRC gateway) activated.')
end
end
require 'spec_helper'
describe 'User activates JetBrains TeamCity CI' do
let(:project) { create(:project) }
let(:user) { create(:user) }
before do
project.add_master(user)
sign_in(user)
visit(project_settings_integrations_path(project))
click_link('JetBrains TeamCity CI')
end
it 'activates service' do
check('Active')
fill_in('Teamcity url', with: 'http://teamcity.example.com')
fill_in('Build type', with: 'GitlabTest_Build')
fill_in('Username', with: 'user')
fill_in('Password', with: 'verySecret')
click_button('Save')
expect(page).to have_content('JetBrains TeamCity CI activated.')
end
end
require 'spec_helper' require 'spec_helper'
feature 'Setup Jira service', :js do describe 'User activates Jira', :js do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project) } let(:project) { create(:project) }
let(:service) { project.create_jira_service } let(:service) { project.create_jira_service }
......
require 'spec_helper'
describe 'User activates PivotalTracker' do
let(:project) { create(:project) }
let(:user) { create(:user) }
before do
project.add_master(user)
sign_in(user)
visit(project_settings_integrations_path(project))
click_link('PivotalTracker')
end
it 'activates service' do
check('Active')
fill_in('Token', with: 'verySecret')
click_button('Save')
expect(page).to have_content('PivotalTracker activated.')
end
end
require 'spec_helper'
describe 'User activates Pushover' do
let(:project) { create(:project) }
let(:user) { create(:user) }
before do
project.add_master(user)
sign_in(user)
visit(project_settings_integrations_path(project))
click_link('Pushover')
end
it 'activates service' do
check('Active')
fill_in('Api key', with: 'verySecret')
fill_in('User key', with: 'verySecret')
fill_in('Device', with: 'myDevice')
select('High Priority', from: 'Priority')
select('Bike', from: 'Sound')
click_button('Save')
expect(page).to have_content('Pushover activated.')
end
end
require 'spec_helper'
describe 'User activates Slack notifications' do
let(:user) { create(:user) }
let(:service) { SlackService.new }
let(:project) { create(:project, slack_service: service) }
before do
project.add_master(user)
sign_in(user)
end
context 'when service is not configured yet' do
before do
visit(project_settings_integrations_path(project))
click_link('Slack notifications')
end
it 'activates service' do
check('Active')
fill_in('Webhook', with: 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685')
click_button('Save')
expect(page).to have_content('Slack notifications activated.')
end
end
context 'when service is already configured' do
before do
service.fields
service.update_attributes(
push_channel: 1,
issue_channel: 2,
merge_request_channel: 3,
note_channel: 4,
tag_push_channel: 5,
pipeline_channel: 6,
wiki_page_channel: 7)
visit(edit_project_service_path(project, service))
end
it 'filters events by channel' do
expect(page.find_field('service_push_channel').value).to have_content('1')
expect(page.find_field('service_issue_channel').value).to have_content('2')
expect(page.find_field('service_merge_request_channel').value).to have_content('3')
expect(page.find_field('service_note_channel').value).to have_content('4')
expect(page.find_field('service_tag_push_channel').value).to have_content('5')
expect(page.find_field('service_pipeline_channel').value).to have_content('6')
expect(page.find_field('service_wiki_page_channel').value).to have_content('7')
end
end
end
require 'spec_helper'
describe 'User views services' do
let(:project) { create(:project) }
let(:user) { create(:user) }
before do
project.add_master(user)
sign_in(user)
visit(project_settings_integrations_path(project))
end
it 'shows the list of available services' do
expect(page).to have_content('Project services')
expect(page).to have_content('Campfire')
expect(page).to have_content('HipChat')
expect(page).to have_content('Assembla')
expect(page).to have_content('Pushover')
expect(page).to have_content('Atlassian Bamboo')
expect(page).to have_content('JetBrains TeamCity')
expect(page).to have_content('Asana')
expect(page).to have_content('Irker (IRC gateway)')
end
end
import csrf from '~/lib/utils/csrf';
describe('csrf', () => {
beforeEach(() => {
this.tokenKey = 'X-CSRF-Token';
this.token = 'pH1cvjnP9grx2oKlhWEDvUZnJ8x2eXsIs1qzyHkF3DugSG5yTxR76CWeEZRhML2D1IeVB7NEW0t5l/axE4iJpQ==';
});
it('returns the correct headerKey', () => {
expect(csrf.headerKey).toBe(this.tokenKey);
});
describe('when csrf token is in the DOM', () => {
beforeEach(() => {
setFixtures(`
<meta name="csrf-token" content="${this.token}">
`);
csrf.init();
});
it('returns the csrf token', () => {
expect(csrf.token).toBe(this.token);
});
it('returns the csrf headers object', () => {
expect(csrf.headers[this.tokenKey]).toBe(this.token);
});
});
describe('when csrf token is not in the DOM', () => {
beforeEach(() => {
setFixtures(`
<meta name="some-other-token">
`);
csrf.init();
});
it('returns null for token', () => {
expect(csrf.token).toBeNull();
});
it('returns empty object for headers', () => {
expect(typeof csrf.headers).toBe('object');
expect(Object.keys(csrf.headers).length).toBe(0);
});
});
});
import Vue from 'vue'; import Vue from 'vue';
import autosize from 'vendor/autosize';
import store from '~/notes/stores'; import store from '~/notes/stores';
import issueCommentForm from '~/notes/components/issue_comment_form.vue'; import issueCommentForm from '~/notes/components/issue_comment_form.vue';
import { loggedOutIssueData, notesDataMock, userDataMock, issueDataMock } from '../mock_data'; import { loggedOutIssueData, notesDataMock, userDataMock, issueDataMock } from '../mock_data';
...@@ -55,6 +56,19 @@ describe('issue_comment_form component', () => { ...@@ -55,6 +56,19 @@ describe('issue_comment_form component', () => {
expect(vm.$el.querySelector(`a[href="${quickActionsDocsPath}"]`).textContent.trim()).toEqual('quick actions'); expect(vm.$el.querySelector(`a[href="${quickActionsDocsPath}"]`).textContent.trim()).toEqual('quick actions');
}); });
it('should resize textarea after note discarded', (done) => {
spyOn(autosize, 'update');
spyOn(vm, 'discard').and.callThrough();
vm.note = 'foo';
vm.discard();
Vue.nextTick(() => {
expect(autosize.update).toHaveBeenCalled();
done();
});
});
describe('edit mode', () => { describe('edit mode', () => {
it('should enter edit mode when arrow up is pressed', () => { it('should enter edit mode when arrow up is pressed', () => {
spyOn(vm, 'editCurrentUserLastNote').and.callThrough(); spyOn(vm, 'editCurrentUserLastNote').and.callThrough();
......
import * as actions from '~/notes/stores/actions'; import * as actions from '~/notes/stores/actions';
import testAction from './helpers'; import testAction from './helpers';
import { discussionMock, notesDataMock, userDataMock, issueDataMock, individualNote } from '../mock_data'; import { discussionMock, notesDataMock, userDataMock, issueDataMock, individualNote } from '../mock_data';
......
...@@ -3,19 +3,31 @@ import { note, discussionMock, notesDataMock, userDataMock, issueDataMock, indiv ...@@ -3,19 +3,31 @@ import { note, discussionMock, notesDataMock, userDataMock, issueDataMock, indiv
describe('Mutation Notes Store', () => { describe('Mutation Notes Store', () => {
describe('ADD_NEW_NOTE', () => { describe('ADD_NEW_NOTE', () => {
it('should add a new note to an array of notes', () => { let state;
const state = { notes: [] }; let noteData;
mutations.ADD_NEW_NOTE(state, note);
expect(state).toEqual({ beforeEach(() => {
notes: [{ state = { notes: [] };
noteData = {
expanded: true, expanded: true,
id: note.discussion_id, id: note.discussion_id,
individual_note: true, individual_note: true,
notes: [note], notes: [note],
reply_id: note.discussion_id, reply_id: note.discussion_id,
}], };
mutations.ADD_NEW_NOTE(state, note);
});
it('should add a new note to an array of notes', () => {
expect(state).toEqual({
notes: [noteData],
}); });
expect(state.notes.length).toBe(1);
});
it('should not add the same note to the notes array', () => {
mutations.ADD_NEW_NOTE(state, note);
expect(state.notes.length).toBe(1);
}); });
}); });
......
...@@ -28,6 +28,7 @@ describe Backup::Manager do ...@@ -28,6 +28,7 @@ describe Backup::Manager do
'1451520000_2015_12_31_4.5.6_gitlab_backup.tar', '1451520000_2015_12_31_4.5.6_gitlab_backup.tar',
'1451520000_2015_12_31_4.5.6-pre_gitlab_backup.tar', '1451520000_2015_12_31_4.5.6-pre_gitlab_backup.tar',
'1451520000_2015_12_31_4.5.6-rc1_gitlab_backup.tar', '1451520000_2015_12_31_4.5.6-rc1_gitlab_backup.tar',
'1451520000_2015_12_31_4.5.6-pre-ee_gitlab_backup.tar',
'1451510000_2015_12_30_gitlab_backup.tar', '1451510000_2015_12_30_gitlab_backup.tar',
'1450742400_2015_12_22_gitlab_backup.tar', '1450742400_2015_12_22_gitlab_backup.tar',
'1449878400_gitlab_backup.tar', '1449878400_gitlab_backup.tar',
...@@ -114,14 +115,18 @@ describe Backup::Manager do ...@@ -114,14 +115,18 @@ describe Backup::Manager do
expect(FileUtils).to have_received(:rm).with(files[3]) expect(FileUtils).to have_received(:rm).with(files[3])
end end
it 'removes matching files with a human-readable non-versioned timestamp' do it 'removes matching files with a human-readable versioned timestamp with tagged EE' do
expect(FileUtils).to have_received(:rm).with(files[4]) expect(FileUtils).to have_received(:rm).with(files[4])
end
it 'removes matching files with a human-readable non-versioned timestamp' do
expect(FileUtils).to have_received(:rm).with(files[5]) expect(FileUtils).to have_received(:rm).with(files[5])
expect(FileUtils).to have_received(:rm).with(files[6])
end end
it 'removes matching files without a human-readable timestamp' do it 'removes matching files without a human-readable timestamp' do
expect(FileUtils).to have_received(:rm).with(files[6])
expect(FileUtils).to have_received(:rm).with(files[7]) expect(FileUtils).to have_received(:rm).with(files[7])
expect(FileUtils).to have_received(:rm).with(files[8])
end end
it 'does not remove files that are not old enough' do it 'does not remove files that are not old enough' do
...@@ -129,11 +134,11 @@ describe Backup::Manager do ...@@ -129,11 +134,11 @@ describe Backup::Manager do
end end
it 'does not remove non-matching files' do it 'does not remove non-matching files' do
expect(FileUtils).not_to have_received(:rm).with(files[8]) expect(FileUtils).not_to have_received(:rm).with(files[9])
end end
it 'prints a done message' do it 'prints a done message' do
expect(progress).to have_received(:puts).with('done. (7 removed)') expect(progress).to have_received(:puts).with('done. (8 removed)')
end end
end end
...@@ -153,10 +158,11 @@ describe Backup::Manager do ...@@ -153,10 +158,11 @@ describe Backup::Manager do
expect(FileUtils).to have_received(:rm).with(files[5]) expect(FileUtils).to have_received(:rm).with(files[5])
expect(FileUtils).to have_received(:rm).with(files[6]) expect(FileUtils).to have_received(:rm).with(files[6])
expect(FileUtils).to have_received(:rm).with(files[7]) expect(FileUtils).to have_received(:rm).with(files[7])
expect(FileUtils).to have_received(:rm).with(files[8])
end end
it 'sets the correct removed count' do it 'sets the correct removed count' do
expect(progress).to have_received(:puts).with('done. (6 removed)') expect(progress).to have_received(:puts).with('done. (7 removed)')
end end
it 'prints the error from file that could not be removed' do it 'prints the error from file that could not be removed' do
......
...@@ -347,10 +347,10 @@ describe Gitlab::ClosingIssueExtractor do ...@@ -347,10 +347,10 @@ describe Gitlab::ClosingIssueExtractor do
end end
it "fetches cross-project URL references" do it "fetches cross-project URL references" do
message = "Closes #{urls.project_issue_url(issue2.project, issue2)} and #{reference}" message = "Closes #{urls.project_issue_url(issue2.project, issue2)}, #{reference} and #{urls.project_issue_url(other_issue.project, other_issue)}"
expect(subject.closed_by_message(message)) expect(subject.closed_by_message(message))
.to match_array([issue, issue2]) .to match_array([issue, issue2, other_issue])
end end
it "ignores invalid cross-project URL references" do it "ignores invalid cross-project URL references" do
......
...@@ -28,6 +28,7 @@ describe Gitlab::Git::Hook do ...@@ -28,6 +28,7 @@ describe Gitlab::Git::Hook do
f.write(<<-HOOK) f.write(<<-HOOK)
echo 'regular message from the hook' echo 'regular message from the hook'
echo 'error message from the hook' 1>&2 echo 'error message from the hook' 1>&2
echo 'error message from the hook line 2' 1>&2
exit 1 exit 1
HOOK HOOK
end end
...@@ -73,7 +74,7 @@ describe Gitlab::Git::Hook do ...@@ -73,7 +74,7 @@ describe Gitlab::Git::Hook do
status, errors = hook.trigger(gl_id, blank, blank, ref) status, errors = hook.trigger(gl_id, blank, blank, ref)
expect(status).to be false expect(status).to be false
expect(errors).to eq("error message from the hook\n") expect(errors).to eq("error message from the hook<br>error message from the hook line 2<br>")
end end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment