Commit 63cf8881 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge branch 'master' into 'port-i18n-for-mirrors-instructions'

# Conflicts:
#   app/views/projects/mirrors/_instructions.html.haml
parents f8f699ab 52646106
...@@ -453,6 +453,7 @@ danger-review: ...@@ -453,6 +453,7 @@ danger-review:
- master - master
variables: variables:
- $CI_COMMIT_REF_NAME =~ /^ce-to-ee-.*/ - $CI_COMMIT_REF_NAME =~ /^ce-to-ee-.*/
- $CI_COMMIT_REF_NAME =~ /.*-stable(-ee)?-prepare-.*/
script: script:
- git version - git version
- danger --fail-on-errors=true - danger --fail-on-errors=true
......
This diff is collapsed.
...@@ -123,7 +123,7 @@ GEM ...@@ -123,7 +123,7 @@ GEM
numerizer (~> 0.1.1) numerizer (~> 0.1.1)
chunky_png (1.3.5) chunky_png (1.3.5)
citrus (3.0.2) citrus (3.0.2)
coderay (1.1.1) coderay (1.1.2)
coercible (1.0.0) coercible (1.0.0)
descendants_tracker (~> 0.0.1) descendants_tracker (~> 0.0.1)
commonmarker (0.17.8) commonmarker (0.17.8)
...@@ -494,7 +494,7 @@ GEM ...@@ -494,7 +494,7 @@ GEM
memoist (0.16.0) memoist (0.16.0)
memoizable (0.4.2) memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
method_source (0.8.2) method_source (0.9.0)
mime-types (3.1) mime-types (3.1)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521) mime-types-data (3.2016.0521)
...@@ -636,12 +636,11 @@ GEM ...@@ -636,12 +636,11 @@ GEM
unparser unparser
procto (0.0.3) procto (0.0.3)
prometheus-client-mmap (0.9.4) prometheus-client-mmap (0.9.4)
pry (0.10.4) pry (0.11.3)
coderay (~> 1.1.0) coderay (~> 1.1.0)
method_source (~> 0.8.1) method_source (~> 0.9.0)
slop (~> 3.4) pry-byebug (3.4.3)
pry-byebug (3.4.2) byebug (>= 9.0, < 9.1)
byebug (~> 9.0)
pry (~> 0.10) pry (~> 0.10)
pry-rails (0.3.5) pry-rails (0.3.5)
pry (>= 0.9.10) pry (>= 0.9.10)
...@@ -872,7 +871,6 @@ GEM ...@@ -872,7 +871,6 @@ GEM
simplecov-html (~> 0.10.0) simplecov-html (~> 0.10.0)
simplecov-html (0.10.0) simplecov-html (0.10.0)
slack-notifier (1.5.1) slack-notifier (1.5.1)
slop (3.6.0)
spring (2.0.1) spring (2.0.1)
activesupport (>= 4.2) activesupport (>= 4.2)
spring-commands-rspec (1.0.4) spring-commands-rspec (1.0.4)
...@@ -1207,4 +1205,4 @@ DEPENDENCIES ...@@ -1207,4 +1205,4 @@ DEPENDENCIES
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH BUNDLED WITH
1.16.2 1.16.3
...@@ -117,7 +117,7 @@ router.beforeEach((to, from, next) => { ...@@ -117,7 +117,7 @@ router.beforeEach((to, from, next) => {
mergeRequestId: to.params.mrid, mergeRequestId: to.params.mrid,
}) })
.then(mr => { .then(mr => {
store.dispatch('updateActivityBarView', activityBarViews.review); store.dispatch('setCurrentBranchId', mr.source_branch);
store.dispatch('getBranchData', { store.dispatch('getBranchData', {
projectId: fullProjectId, projectId: fullProjectId,
...@@ -144,6 +144,10 @@ router.beforeEach((to, from, next) => { ...@@ -144,6 +144,10 @@ router.beforeEach((to, from, next) => {
}), }),
) )
.then(mrChanges => { .then(mrChanges => {
if (mrChanges.changes.length) {
store.dispatch('updateActivityBarView', activityBarViews.review);
}
mrChanges.changes.forEach((change, ind) => { mrChanges.changes.forEach((change, ind) => {
const changeTreeEntry = store.state.entries[change.new_path]; const changeTreeEntry = store.state.entries[change.new_path];
......
...@@ -54,9 +54,6 @@ export const setFileActive = ({ commit, state, getters, dispatch }, path) => { ...@@ -54,9 +54,6 @@ export const setFileActive = ({ commit, state, getters, dispatch }, path) => {
commit(types.SET_FILE_ACTIVE, { path, active: true }); commit(types.SET_FILE_ACTIVE, { path, active: true });
dispatch('scrollToTab'); dispatch('scrollToTab');
commit(types.SET_CURRENT_PROJECT, file.projectId);
commit(types.SET_CURRENT_BRANCH, file.branchId);
}; };
export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive = true }) => { export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive = true }) => {
......
import initForm from '../form'; import initForm from '../form';
import MirrorRepos from './mirror_repos';
document.addEventListener('DOMContentLoaded', initForm); document.addEventListener('DOMContentLoaded', () => {
initForm();
const mirrorReposContainer = document.querySelector('.js-mirror-settings');
if (mirrorReposContainer) new MirrorRepos(mirrorReposContainer).init();
});
import $ from 'jquery';
import _ from 'underscore';
import { __ } from '~/locale';
import Flash from '~/flash';
import axios from '~/lib/utils/axios_utils';
export default class MirrorRepos {
constructor(container) {
this.$container = $(container);
this.$form = $('.js-mirror-form', this.$container);
this.$urlInput = $('.js-mirror-url', this.$form);
this.$protectedBranchesInput = $('.js-mirror-protected', this.$form);
this.$table = $('.js-mirrors-table-body', this.$container);
this.mirrorEndpoint = this.$form.data('projectMirrorEndpoint');
}
init() {
this.initMirrorPush();
this.registerUpdateListeners();
}
initMirrorPush() {
this.$passwordGroup = $('.js-password-group', this.$container);
this.$password = $('.js-password', this.$passwordGroup);
this.$authMethod = $('.js-auth-method', this.$form);
this.$authMethod.on('change', () => this.togglePassword());
this.$password.on('input.updateUrl', () => this.debouncedUpdateUrl());
}
updateUrl() {
let val = this.$urlInput.val();
if (this.$password) {
const password = this.$password.val();
if (password) val = val.replace('@', `:${password}@`);
}
$('.js-mirror-url-hidden', this.$form).val(val);
}
updateProtectedBranches() {
const val = this.$protectedBranchesInput.get(0).checked
? this.$protectedBranchesInput.val()
: '0';
$('.js-mirror-protected-hidden', this.$form).val(val);
}
registerUpdateListeners() {
this.debouncedUpdateUrl = _.debounce(() => this.updateUrl(), 200);
this.$urlInput.on('input', () => this.debouncedUpdateUrl());
this.$protectedBranchesInput.on('change', () => this.updateProtectedBranches());
this.$table.on('click', '.js-delete-mirror', event => this.deleteMirror(event));
}
togglePassword() {
const isPassword = this.$authMethod.val() === 'password';
if (!isPassword) {
this.$password.val('');
this.updateUrl();
}
this.$passwordGroup.collapse(isPassword ? 'show' : 'hide');
}
deleteMirror(event, existingPayload) {
const $target = $(event.currentTarget);
let payload = existingPayload;
if (!payload) {
payload = {
project: {
remote_mirrors_attributes: {
id: $target.data('mirrorId'),
enabled: 0,
},
},
};
}
return axios
.put(this.mirrorEndpoint, payload)
.then(() => this.removeRow($target))
.catch(() => Flash(__('Failed to remove mirror.')));
}
/* eslint-disable class-methods-use-this */
removeRow($target) {
const row = $target.closest('tr');
$('.js-delete-mirror', row).tooltip('hide');
row.remove();
}
/* eslint-enable class-methods-use-this */
}
<script> <script>
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { componentNames } from '~/vue_shared/components/reports/issue_body'; import { componentNames } from './issue_body';
import ReportSection from '~/vue_shared/components/reports/report_section.vue'; import ReportSection from './report_section.vue';
import SummaryRow from '~/vue_shared/components/reports/summary_row.vue'; import SummaryRow from './summary_row.vue';
import IssuesList from '~/vue_shared/components/reports/issues_list.vue'; import IssuesList from './issues_list.vue';
import Modal from './modal.vue'; import Modal from './modal.vue';
import createStore from '../store'; import createStore from '../store';
import { summaryTextBuilder, reportTextBuilder, statusIcon } from '../store/utils'; import { summaryTextBuilder, reportTextBuilder, statusIcon } from '../store/utils';
......
import TestIssueBody from '~/reports/components/test_issue_body.vue'; import TestIssueBody from './test_issue_body.vue';
export const components = { export const components = {
TestIssueBody, TestIssueBody,
......
<script> <script>
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import { import {
STATUS_FAILED, STATUS_FAILED,
STATUS_NEUTRAL, STATUS_NEUTRAL,
STATUS_SUCCESS, STATUS_SUCCESS,
} from '~/vue_shared/components/reports/constants'; } from '../constants';
export default { export default {
name: 'IssueStatusIcon', name: 'IssueStatusIcon',
......
<script> <script>
import IssuesBlock from '~/vue_shared/components/reports/report_issues.vue'; import IssuesBlock from './report_issues.vue';
import { import {
STATUS_SUCCESS, STATUS_SUCCESS,
STATUS_FAILED, STATUS_FAILED,
STATUS_NEUTRAL, STATUS_NEUTRAL,
} from '~/vue_shared/components/reports/constants'; } from '../constants';
/** /**
* Renders block of issues * Renders block of issues
......
<script> <script>
import IssueStatusIcon from '~/vue_shared/components/reports/issue_status_icon.vue'; import IssueStatusIcon from './issue_status_icon.vue';
import { components, componentNames } from '~/vue_shared/components/reports/issue_body'; import { components, componentNames } from './issue_body';
export default { export default {
name: 'ReportIssues', name: 'ReportIssues',
......
<script> <script>
import { __ } from '~/locale'; import { __ } from '~/locale';
import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue'; import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
import Popover from '~/vue_shared/components/help_popover.vue';
import IssuesList from './issues_list.vue'; import IssuesList from './issues_list.vue';
import Popover from '../help_popover.vue';
const LOADING = 'LOADING'; const LOADING = 'LOADING';
const ERROR = 'ERROR'; const ERROR = 'ERROR';
......
<script> <script>
import CiIcon from '~/vue_shared/components/ci_icon.vue'; import CiIcon from '~/vue_shared/components/ci_icon.vue';
import LoadingIcon from '~/vue_shared/components/loading_icon.vue'; import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import Popover from '../help_popover.vue'; import Popover from '~/vue_shared/components/help_popover.vue';
/** /**
* Renders the summary row for each report * Renders the summary row for each report
......
...@@ -11,6 +11,8 @@ export const SUCCESS = 'SUCCESS'; ...@@ -11,6 +11,8 @@ export const SUCCESS = 'SUCCESS';
export const STATUS_FAILED = 'failed'; export const STATUS_FAILED = 'failed';
export const STATUS_SUCCESS = 'success'; export const STATUS_SUCCESS = 'success';
export const STATUS_NEUTRAL = 'neutral';
export const ICON_WARNING = 'warning'; export const ICON_WARNING = 'warning';
export const ICON_SUCCESS = 'success'; export const ICON_SUCCESS = 'success';
export const ICON_NOTFOUND = 'notfound'; export const ICON_NOTFOUND = 'notfound';
export const STATUS_FAILED = 'failed';
export const STATUS_SUCCESS = 'success';
export const STATUS_NEUTRAL = 'neutral';
...@@ -103,6 +103,7 @@ ...@@ -103,6 +103,7 @@
display: flex; display: flex;
a { a {
width: 100%;
display: flex; display: flex;
} }
......
...@@ -201,7 +201,7 @@ label { ...@@ -201,7 +201,7 @@ label {
} }
.gl-show-field-errors { .gl-show-field-errors {
.form-control { .form-control:not(textarea) {
height: 34px; height: 34px;
} }
......
...@@ -253,7 +253,7 @@ ...@@ -253,7 +253,7 @@
text-align: right; text-align: right;
padding: 0; padding: 0;
position: relative; position: relative;
top: -3px; margin: 0;
} }
.label-badge { .label-badge {
...@@ -274,6 +274,7 @@ ...@@ -274,6 +274,7 @@
.label-links { .label-links {
list-style: none; list-style: none;
margin: 0;
padding: 0; padding: 0;
white-space: nowrap; white-space: nowrap;
} }
......
...@@ -823,10 +823,6 @@ pre.light-well { ...@@ -823,10 +823,6 @@ pre.light-well {
.avatar-container { .avatar-container {
align-self: flex-start; align-self: flex-start;
> a {
width: 100%;
}
} }
.project-details { .project-details {
......
...@@ -301,3 +301,17 @@ ...@@ -301,3 +301,17 @@
margin-bottom: 0; margin-bottom: 0;
} }
} }
.mirror-error-badge {
background-color: $error-bg;
border-radius: $border-radius-default;
color: $white-light;
}
.push-pull-table {
margin-top: 1em;
.mirror-action-buttons {
padding-right: 0;
}
}
module AvatarsHelper module AvatarsHelper
def project_icon(project_id, options = {}) def project_icon(project_id, options = {})
project = source_icon(Project, project_id, options)
if project_id.respond_to?(:avatar_url)
project_id
else
Project.find_by_full_path(project_id)
end
if project.avatar_url
image_tag project.avatar_url, options
else # generated icon
project_identicon(project, options)
end
end end
def project_identicon(project, options = {}) def group_icon(group_id, options = {})
bg_key = (project.id % 7) + 1 source_icon(Group, group_id, options)
options[:class] ||= ''
options[:class] << ' identicon'
options[:class] << " bg#{bg_key}"
content_tag(:div, class: options[:class]) do
project.name[0, 1].upcase
end
end end
# Takes both user and email and returns the avatar_icon by # Takes both user and email and returns the avatar_icon by
...@@ -123,4 +105,32 @@ module AvatarsHelper ...@@ -123,4 +105,32 @@ module AvatarsHelper
mail_to(options[:user_email], avatar) mail_to(options[:user_email], avatar)
end end
end end
private
def source_icon(klass, source_id, options = {})
source =
if source_id.respond_to?(:avatar_url)
source_id
else
klass.find_by_full_path(source_id)
end
if source.avatar_url
image_tag source.avatar_url, options
else
source_identicon(source, options)
end
end
def source_identicon(source, options = {})
bg_key = (source.id % 7) + 1
options[:class] ||= ''
options[:class] << ' identicon'
options[:class] << " bg#{bg_key}"
content_tag(:div, class: options[:class].strip) do
source.name[0, 1].upcase
end
end
end end
...@@ -33,11 +33,6 @@ module GroupsHelper ...@@ -33,11 +33,6 @@ module GroupsHelper
.count .count
end end
def group_icon(group, options = {})
img_path = group_icon_url(group, options)
image_tag img_path, options
end
def group_icon_url(group, options = {}) def group_icon_url(group, options = {})
if group.is_a?(String) if group.is_a?(String)
group = Group.find_by_full_path(group) group = Group.find_by_full_path(group)
......
module MirrorHelper
def mirrors_form_data_attributes
{ project_mirror_endpoint: project_mirror_path(@project) }
end
end
# frozen_string_literal: true
class ProgrammingLanguage < ActiveRecord::Base class ProgrammingLanguage < ActiveRecord::Base
validates :name, presence: true validates :name, presence: true
validates :color, allow_blank: false, color: true validates :color, allow_blank: false, color: true
......
...@@ -470,6 +470,24 @@ class Project < ActiveRecord::Base ...@@ -470,6 +470,24 @@ class Project < ActiveRecord::Base
}x }x
end end
def reference_postfix
'>'
end
def reference_postfix_escaped
'&gt;'
end
# Pattern used to extract `namespace/project>` project references from text.
# '>' or its escaped form ('&gt;') are checked for because '>' is sometimes escaped
# when the reference comes from an external source.
def markdown_reference_pattern
%r{
#{reference_pattern}
(#{reference_postfix}|#{reference_postfix_escaped})
}x
end
def trending def trending
joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id') joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id')
.reorder('trending_projects.id ASC') .reorder('trending_projects.id ASC')
...@@ -908,6 +926,10 @@ class Project < ActiveRecord::Base ...@@ -908,6 +926,10 @@ class Project < ActiveRecord::Base
end end
end end
def to_reference_with_postfix
"#{to_reference(full: true)}#{self.class.reference_postfix}"
end
# `from` argument can be a Namespace or Project. # `from` argument can be a Namespace or Project.
def to_reference(from = nil, full: false) def to_reference(from = nil, full: false)
if full || cross_namespace_reference?(from) if full || cross_namespace_reference?(from)
......
# frozen_string_literal: true
require 'asana' require 'asana'
class AsanaService < Service class AsanaService < Service
......
# frozen_string_literal: true
class AssemblaService < Service class AssemblaService < Service
prop_accessor :token, :subdomain prop_accessor :token, :subdomain
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
......
# frozen_string_literal: true
class BambooService < CiService class BambooService < CiService
include ReactiveService include ReactiveService
......
# frozen_string_literal: true
class BugzillaService < IssueTrackerService class BugzillaService < IssueTrackerService
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated? validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
......
# frozen_string_literal: true
require "addressable/uri" require "addressable/uri"
class BuildkiteService < CiService class BuildkiteService < CiService
......
# frozen_string_literal: true
# This class is to be removed with 9.1 # This class is to be removed with 9.1
# We should also by then remove BuildsEmailService from database # We should also by then remove BuildsEmailService from database
class BuildsEmailService < Service class BuildsEmailService < Service
......
# frozen_string_literal: true
class CampfireService < Service class CampfireService < Service
prop_accessor :token, :subdomain, :room prop_accessor :token, :subdomain, :room
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
...@@ -82,7 +84,7 @@ class CampfireService < Service ...@@ -82,7 +84,7 @@ class CampfireService < Service
before = push[:before] before = push[:before]
after = push[:after] after = push[:after]
message = "" message = []
message << "[#{project.full_name}] " message << "[#{project.full_name}] "
message << "#{push[:user_name]} " message << "#{push[:user_name]} "
...@@ -95,6 +97,6 @@ class CampfireService < Service ...@@ -95,6 +97,6 @@ class CampfireService < Service
message << "#{project.web_url}/compare/#{before}...#{after}" message << "#{project.web_url}/compare/#{before}...#{after}"
end end
message message.join
end end
end end
# frozen_string_literal: true
require 'slack-notifier' require 'slack-notifier'
module ChatMessage module ChatMessage
......
# frozen_string_literal: true
module ChatMessage module ChatMessage
class IssueMessage < BaseMessage class IssueMessage < BaseMessage
attr_reader :title attr_reader :title
......
# frozen_string_literal: true
module ChatMessage module ChatMessage
class MergeMessage < BaseMessage class MergeMessage < BaseMessage
attr_reader :merge_request_iid attr_reader :merge_request_iid
......
# frozen_string_literal: true
module ChatMessage module ChatMessage
class NoteMessage < BaseMessage class NoteMessage < BaseMessage
attr_reader :note attr_reader :note
......
# frozen_string_literal: true
module ChatMessage module ChatMessage
class PipelineMessage < BaseMessage class PipelineMessage < BaseMessage
attr_reader :ref_type attr_reader :ref_type
......
# frozen_string_literal: true
module ChatMessage module ChatMessage
class PushMessage < BaseMessage class PushMessage < BaseMessage
attr_reader :after attr_reader :after
......
# frozen_string_literal: true
module ChatMessage module ChatMessage
class WikiPageMessage < BaseMessage class WikiPageMessage < BaseMessage
attr_reader :title attr_reader :title
......
# frozen_string_literal: true
# Base class for Chat notifications services # Base class for Chat notifications services
# This class is not meant to be used directly, but only to inherit from. # This class is not meant to be used directly, but only to inherit from.
class ChatNotificationService < Service class ChatNotificationService < Service
......
# frozen_string_literal: true
# Base class for CI services # Base class for CI services
# List methods you need to implement to get your CI service # List methods you need to implement to get your CI service
# working with GitLab Merge Requests # working with GitLab Merge Requests
......
# frozen_string_literal: true
class CustomIssueTrackerService < IssueTrackerService class CustomIssueTrackerService < IssueTrackerService
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated? validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
......
# frozen_string_literal: true
# Base class for deployment services # Base class for deployment services
# #
# These services integrate with a deployment solution like Kubernetes/OpenShift, # These services integrate with a deployment solution like Kubernetes/OpenShift,
......
# frozen_string_literal: true
class DroneCiService < CiService class DroneCiService < CiService
include ReactiveService include ReactiveService
......
# frozen_string_literal: true
class EmailsOnPushService < Service class EmailsOnPushService < Service
boolean_accessor :send_from_committer_email boolean_accessor :send_from_committer_email
boolean_accessor :disable_diffs boolean_accessor :disable_diffs
......
# frozen_string_literal: true
class ExternalWikiService < Service class ExternalWikiService < Service
prop_accessor :external_wiki_url prop_accessor :external_wiki_url
......
# frozen_string_literal: true
require "flowdock-git-hook" require "flowdock-git-hook"
# Flow dock depends on Grit to compute the number of commits between two given # Flow dock depends on Grit to compute the number of commits between two given
......
# frozen_string_literal: true
require "gemnasium/gitlab_service" require "gemnasium/gitlab_service"
class GemnasiumService < Service class GemnasiumService < Service
......
# frozen_string_literal: true
class GitlabIssueTrackerService < IssueTrackerService class GitlabIssueTrackerService < IssueTrackerService
include Gitlab::Routing include Gitlab::Routing
......
# frozen_string_literal: true
require 'hangouts_chat' require 'hangouts_chat'
class HangoutsChatService < ChatNotificationService class HangoutsChatService < ChatNotificationService
......
# frozen_string_literal: true
class HipchatService < Service class HipchatService < Service
include ActionView::Helpers::SanitizeHelper include ActionView::Helpers::SanitizeHelper
...@@ -108,7 +110,7 @@ class HipchatService < Service ...@@ -108,7 +110,7 @@ class HipchatService < Service
before = push[:before] before = push[:before]
after = push[:after] after = push[:after]
message = "" message = []
message << "#{push[:user_name]} " message << "#{push[:user_name]} "
if Gitlab::Git.blank_ref?(before) if Gitlab::Git.blank_ref?(before)
...@@ -132,7 +134,7 @@ class HipchatService < Service ...@@ -132,7 +134,7 @@ class HipchatService < Service
end end
end end
message message.join
end end
def markdown(text, options = {}) def markdown(text, options = {})
...@@ -165,11 +167,11 @@ class HipchatService < Service ...@@ -165,11 +167,11 @@ class HipchatService < Service
description = obj_attr[:description] description = obj_attr[:description]
issue_link = "<a href=\"#{issue_url}\">issue ##{issue_iid}</a>" issue_link = "<a href=\"#{issue_url}\">issue ##{issue_iid}</a>"
message = "#{user_name} #{state} #{issue_link} in #{project_link}: <b>#{title}</b>"
message = ["#{user_name} #{state} #{issue_link} in #{project_link}: <b>#{title}</b>"]
message << "<pre>#{markdown(description)}</pre>" message << "<pre>#{markdown(description)}</pre>"
message message.join
end end
def create_merge_request_message(data) def create_merge_request_message(data)
...@@ -184,12 +186,11 @@ class HipchatService < Service ...@@ -184,12 +186,11 @@ class HipchatService < Service
merge_request_url = "#{project_url}/merge_requests/#{merge_request_id}" merge_request_url = "#{project_url}/merge_requests/#{merge_request_id}"
merge_request_link = "<a href=\"#{merge_request_url}\">merge request !#{merge_request_id}</a>" merge_request_link = "<a href=\"#{merge_request_url}\">merge request !#{merge_request_id}</a>"
message = "#{user_name} #{state} #{merge_request_link} in " \ message = ["#{user_name} #{state} #{merge_request_link} in " \
"#{project_link}: <b>#{title}</b>" "#{project_link}: <b>#{title}</b>"]
message << "<pre>#{markdown(description)}</pre>" message << "<pre>#{markdown(description)}</pre>"
message.join
message
end end
def format_title(title) def format_title(title)
...@@ -235,12 +236,11 @@ class HipchatService < Service ...@@ -235,12 +236,11 @@ class HipchatService < Service
end end
subject_html = "<a href=\"#{note_url}\">#{subject_type} #{subject_desc}</a>" subject_html = "<a href=\"#{note_url}\">#{subject_type} #{subject_desc}</a>"
message = "#{user_name} commented on #{subject_html} in #{project_link}: " message = ["#{user_name} commented on #{subject_html} in #{project_link}: "]
message << title message << title
message << "<pre>#{markdown(note, ref: commit_id)}</pre>" message << "<pre>#{markdown(note, ref: commit_id)}</pre>"
message.join
message
end end
def create_pipeline_message(data) def create_pipeline_message(data)
......
# frozen_string_literal: true
require 'uri' require 'uri'
class IrkerService < Service class IrkerService < Service
......
# frozen_string_literal: true
class IssueTrackerService < Service class IssueTrackerService < Service
validate :one_issue_tracker, if: :activated?, on: :manual_change validate :one_issue_tracker, if: :activated?, on: :manual_change
......
# frozen_string_literal: true
class JiraService < IssueTrackerService class JiraService < IssueTrackerService
include Gitlab::Routing include Gitlab::Routing
include ApplicationHelper include ApplicationHelper
......
# frozen_string_literal: true
## ##
# NOTE: # NOTE:
# We'll move this class to Clusters::Platforms::Kubernetes, which contains exactly the same logic. # We'll move this class to Clusters::Platforms::Kubernetes, which contains exactly the same logic.
......
# frozen_string_literal: true
class MattermostService < ChatNotificationService class MattermostService < ChatNotificationService
def title def title
'Mattermost notifications' 'Mattermost notifications'
......
# frozen_string_literal: true
class MattermostSlashCommandsService < SlashCommandsService class MattermostSlashCommandsService < SlashCommandsService
include TriggersHelper include TriggersHelper
......
# frozen_string_literal: true
class MicrosoftTeamsService < ChatNotificationService class MicrosoftTeamsService < ChatNotificationService
def title def title
'Microsoft Teams Notification' 'Microsoft Teams Notification'
......
# frozen_string_literal: true
# For an example companion mocking service, see https://gitlab.com/gitlab-org/gitlab-mock-ci-service # For an example companion mocking service, see https://gitlab.com/gitlab-org/gitlab-mock-ci-service
class MockCiService < CiService class MockCiService < CiService
ALLOWED_STATES = %w[failed canceled running pending success success_with_warnings skipped not_found].freeze ALLOWED_STATES = %w[failed canceled running pending success success_with_warnings skipped not_found].freeze
......
# frozen_string_literal: true
class MockDeploymentService < DeploymentService class MockDeploymentService < DeploymentService
def title def title
'Mock deployment' 'Mock deployment'
......
# frozen_string_literal: true
class MockMonitoringService < MonitoringService class MockMonitoringService < MonitoringService
def title def title
'Mock monitoring' 'Mock monitoring'
......
# frozen_string_literal: true
# Base class for monitoring services # Base class for monitoring services
# #
# These services integrate with a deployment solution like Prometheus # These services integrate with a deployment solution like Prometheus
......
# frozen_string_literal: true
class PackagistService < Service class PackagistService < Service
prop_accessor :username, :token, :server prop_accessor :username, :token, :server
......
# frozen_string_literal: true
class PipelinesEmailService < Service class PipelinesEmailService < Service
prop_accessor :recipients prop_accessor :recipients
boolean_accessor :notify_only_broken_pipelines boolean_accessor :notify_only_broken_pipelines
......
# frozen_string_literal: true
class PivotaltrackerService < Service class PivotaltrackerService < Service
API_ENDPOINT = 'https://www.pivotaltracker.com/services/v5/source_commits'.freeze API_ENDPOINT = 'https://www.pivotaltracker.com/services/v5/source_commits'.freeze
......
# frozen_string_literal: true
class PrometheusService < MonitoringService class PrometheusService < MonitoringService
include PrometheusAdapter include PrometheusAdapter
......
# frozen_string_literal: true
class PushoverService < Service class PushoverService < Service
BASE_URI = 'https://api.pushover.net/1'.freeze BASE_URI = 'https://api.pushover.net/1'.freeze
...@@ -79,7 +81,7 @@ class PushoverService < Service ...@@ -79,7 +81,7 @@ class PushoverService < Service
end end
if data[:total_commits_count] > 0 if data[:total_commits_count] > 0
message << "\nTotal commits count: #{data[:total_commits_count]}" message = [message, "Total commits count: #{data[:total_commits_count]}"].join("\n")
end end
pushover_data = { pushover_data = {
......
# frozen_string_literal: true
class RedmineService < IssueTrackerService class RedmineService < IssueTrackerService
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated? validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
......
# frozen_string_literal: true
class SlackService < ChatNotificationService class SlackService < ChatNotificationService
def title def title
'Slack notifications' 'Slack notifications'
......
# frozen_string_literal: true
class SlackSlashCommandsService < SlashCommandsService class SlackSlashCommandsService < SlashCommandsService
include TriggersHelper include TriggersHelper
......
# frozen_string_literal: true
# Base class for Chat services # Base class for Chat services
# This class is not meant to be used directly, but only to inherrit from. # This class is not meant to be used directly, but only to inherrit from.
class SlashCommandsService < Service class SlashCommandsService < Service
......
# frozen_string_literal: true
class TeamcityService < CiService class TeamcityService < CiService
include ReactiveService include ReactiveService
......
# frozen_string_literal: true
class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base
include ProtectedBranchAccess include ProtectedBranchAccess
end end
# frozen_string_literal: true
class ProtectedBranch::PushAccessLevel < ActiveRecord::Base class ProtectedBranch::PushAccessLevel < ActiveRecord::Base
include ProtectedBranchAccess include ProtectedBranchAccess
end end
# frozen_string_literal: true
class ProtectedTag::CreateAccessLevel < ActiveRecord::Base class ProtectedTag::CreateAccessLevel < ActiveRecord::Base
include ProtectedTagAccess include ProtectedTagAccess
......
# frozen_string_literal: true
class RepositoryLanguage < ActiveRecord::Base class RepositoryLanguage < ActiveRecord::Base
belongs_to :project belongs_to :project
belongs_to :programming_language belongs_to :programming_language
......
# frozen_string_literal: true
class SiteStatistic < ActiveRecord::Base class SiteStatistic < ActiveRecord::Base
# prevents the creation of multiple rows # prevents the creation of multiple rows
default_value_for :id, 1 default_value_for :id, 1
......
# frozen_string_literal: true
module Storage module Storage
class HashedProject class HashedProject
attr_accessor :project attr_accessor :project
......
# frozen_string_literal: true
module Storage module Storage
class LegacyProject class LegacyProject
attr_accessor :project attr_accessor :project
......
class ProjectMirrorSerializer < BaseSerializer
entity ProjectMirrorEntity
end
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
.bordered-box.fork-thumbnail.text-center.prepend-left-default.append-right-default.prepend-top-default.append-bottom-default.forked .bordered-box.fork-thumbnail.text-center.prepend-left-default.append-right-default.prepend-top-default.append-bottom-default.forked
= link_to project_path(forked_project) do = link_to project_path(forked_project) do
- if /no_((\w*)_)*avatar/.match(avatar) - if /no_((\w*)_)*avatar/.match(avatar)
= project_identicon(namespace, class: "avatar s100 identicon") = project_icon(namespace, class: "avatar s100 identicon")
- else - else
.avatar-container.s100 .avatar-container.s100
= image_tag(avatar, class: "avatar s100") = image_tag(avatar, class: "avatar s100")
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
class: ("disabled has-tooltip" unless can_create_project), class: ("disabled has-tooltip" unless can_create_project),
title: (_('You have reached your project limit') unless can_create_project) do title: (_('You have reached your project limit') unless can_create_project) do
- if /no_((\w*)_)*avatar/.match(avatar) - if /no_((\w*)_)*avatar/.match(avatar)
= project_identicon(namespace, class: "avatar s100 identicon") = project_icon(namespace, class: "avatar s100 identicon")
- else - else
.avatar-container.s100 .avatar-container.s100
= image_tag(avatar, class: "avatar s100") = image_tag(avatar, class: "avatar s100")
......
- expanded = Rails.env.test?
- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|')
%section.settings.project-mirror-settings.js-mirror-settings.no-animate{ class: ('expanded' if expanded) }
.settings-header
%h4= _('Mirroring repositories')
%button.btn.js-settings-toggle
= expanded ? _('Collapse') : _('Expand')
%p
= _('Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically.')
= link_to _('Read more'), help_page_path('workflow/repository_mirroring'), target: '_blank'
.settings-content
= form_for @project, url: project_mirror_path(@project), html: { class: 'gl-show-field-errors js-mirror-form', autocomplete: 'false', data: mirrors_form_data_attributes } do |f|
.panel.panel-default
.panel-heading
%h3.panel-title= _('Mirror a repository')
.panel-body
%div= form_errors(@project)
.form-group.has-feedback
= label_tag :url, _('Git repository URL'), class: 'label-light'
= text_field_tag :url, nil, class: 'form-control js-mirror-url js-repo-url', placeholder: _('Input your repository URL'), required: true, pattern: "(#{protocols}):\/\/.+"
= render 'projects/mirrors/instructions'
= render 'projects/mirrors/mirror_repos_form', f: f
.form-check.append-bottom-10
= check_box_tag :only_protected_branches, '1', false, class: 'js-mirror-protected form-check-input'
= label_tag :only_protected_branches, _('Only mirror protected branches'), class: 'form-check-label'
= link_to icon('question-circle'), help_page_path('user/project/protected_branches')
.panel-footer
= f.submit _('Mirror repository'), class: 'btn btn-create', name: :update_remote_mirror
.panel.panel-default
.table-responsive
%table.table.push-pull-table
%thead
%tr
%th
= _('Mirrored repositories')
= render_if_exists 'projects/mirrors/mirrored_repositories_count'
%th= _('Direction')
%th= _('Last update')
%th
%th
%tbody.js-mirrors-table-body
= render_if_exists 'projects/mirrors/table_pull_row'
- @project.remote_mirrors.each_with_index do |mirror, index|
- if mirror.enabled
%tr
%td= mirror.safe_url
%td= _('Push')
%td= mirror.last_update_at.present? ? time_ago_with_tooltip(mirror.last_update_at) : _('Never')
%td
- if mirror.last_error.present?
.badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true' }, title: html_escape(mirror.last_error.try(:strip)) }= _('Error')
%td.mirror-action-buttons
.btn-group.mirror-actions-group.pull-right{ role: 'group' }
= render 'shared/remote_mirror_update_button', remote_mirror: mirror
%button.js-delete-mirror.btn.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o')
- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|')
.form-group
= label_tag :mirror_direction, _('Mirror direction'), class: 'label-light'
= select_tag :mirror_direction, options_for_select([[_('Push'), 'push']]), class: 'form-control js-mirror-direction', disabled: true
= f.fields_for :remote_mirrors, @project.remote_mirrors.build do |rm_f|
= rm_f.hidden_field :enabled, value: '1'
= rm_f.hidden_field :url, class: 'js-mirror-url-hidden', required: true, pattern: "(#{protocols}):\/\/.+"
= rm_f.hidden_field :only_protected_branches, class: 'js-mirror-protected-hidden'
.form-group
= label_tag :auth_method, _('Authentication method'), class: 'label-bold'
= select_tag :auth_method, options_for_select([[_('None'), 'none'], [_('Password'), 'password']], 'none'), { class: "form-control js-auth-method" }
.form-group.js-password-group.collapse
= label_tag :password, _('Password'), class: 'label-bold'
= text_field_tag :password, '', class: 'form-control js-password'
- expanded = Rails.env.test?
%section.settings.no-animate#js-push-remote-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
Push to a remote repository
%button.btn.js-settings-toggle
= expanded ? 'Collapse' : 'Expand'
%p
Set up the remote repository that you want to update with the content of the current repository
every time someone pushes to it.
= link_to 'Read more', help_page_path('workflow/repository_mirroring', anchor: 'pushing-to-a-remote-repository'), target: '_blank'
.settings-content
= form_for @project, url: project_mirror_path(@project) do |f|
%div
= form_errors(@project)
= render "shared/remote_mirror_update_button", remote_mirror: @remote_mirror
- if @remote_mirror.last_error.present?
.panel.panel-danger
.panel-heading
- if @remote_mirror.last_update_at
The remote repository failed to update #{time_ago_with_tooltip(@remote_mirror.last_update_at)}.
- else
The remote repository failed to update.
- if @remote_mirror.last_successful_update_at
Last successful update #{time_ago_with_tooltip(@remote_mirror.last_successful_update_at)}.
.panel-body
%pre
:preserve
#{h(@remote_mirror.last_error.strip)}
= f.fields_for :remote_mirrors, @remote_mirror do |rm_form|
.form-group
= rm_form.check_box :enabled, class: "float-left"
.prepend-left-20
= rm_form.label :enabled, "Remote mirror repository", class: "label-bold append-bottom-0"
%p.light.append-bottom-0
Automatically update the remote mirror's branches, tags, and commits from this repository every time someone pushes to it.
.form-group.has-feedback
= rm_form.label :url, "Git repository URL", class: "label-bold"
= rm_form.text_field :url, class: "form-control", placeholder: 'https://username:password@gitlab.company.com/group/project.git'
= render "projects/mirrors/instructions"
.form-group
= rm_form.check_box :only_protected_branches, class: 'float-left'
.prepend-left-20
= rm_form.label :only_protected_branches, class: 'label-bold'
= link_to icon('question-circle'), help_page_path('user/project/protected_branches')
= f.submit 'Save changes', class: 'btn btn-create', name: 'update_remote_mirror'
- if can?(current_user, :admin_remote_mirror, @project) = render 'projects/mirrors/mirror_repos'
= render 'projects/mirrors/push'
- if @project.has_remote_mirror? - if remote_mirror.update_in_progress?
.append-bottom-default %button.btn.disabled{ type: 'button', data: { toggle: 'tooltip', container: 'body' }, title: _('Updating') }
- if remote_mirror.update_in_progress? = icon("refresh spin")
%span.btn.disabled - else
= icon("refresh spin") = link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn", data: { toggle: 'tooltip', container: 'body' }, title: _('Update now') do
Updating&hellip; = icon("refresh")
- else
= link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn" do
= icon("refresh")
Update Now
- if @remote_mirror.last_successful_update_at
%p.inline.prepend-left-10
Successfully updated #{time_ago_with_tooltip(@remote_mirror.last_successful_update_at)}.
title: First Improvements made to the contributor on-boarding experience.
merge_request: 20682
author: Eddie Stubbington
type: added
---
title: Add the ability to reference projects in comments and other markdown text.
merge_request: 20285
author: Reuben Pereira
type: added
---
title: Add default avatar to group
merge_request: 17271
author: George Tsiolis
type: changed
---
title: Fix label list item container height when there is no label description
merge_request: 21106
author:
type: fixed
---
title: Enable frozen string in rest of app/models/**/*.rb
merge_request: gfyoung
author:
type: performance
---
title: Fix empty merge requests not opening in the Web IDE
merge_request: 21102
author:
type: fixed
---
title: Rails5 fix specs duplicate key value violates unique constraint 'index_gpg_signatures_on_commit_sha'
merge_request: 21119
author: Jasper Maes
type: fixed
# frozen_string_literal: true
module Db
module Fixtures
module Development
class SpamLog
def self.seed
Gitlab::Seeder.quiet do
(::SpamLog.default_per_page + 3).times do |i|
::SpamLog.create(
user: self.random_user,
user_agent: FFaker::Lorem.sentence,
source_ip: FFaker::Internet.ip_v4_address,
title: FFaker::Lorem.sentence,
description: FFaker::Lorem.paragraph,
via_api: FFaker::Boolean.random,
submitted_as_ham: FFaker::Boolean.random,
recaptcha_verified: FFaker::Boolean.random)
print '.'
end
end
end
def self.random_user
User.find(User.pluck(:id).sample)
end
end
end
end
end
Db::Fixtures::Development::SpamLog.seed
class AddMovedToToIssue < ActiveRecord::Migration class AddMovedToToIssue < ActiveRecord::Migration
def change def change
add_reference :issues, :moved_to, references: :issues add_reference :issues, :moved_to, references: :issues # rubocop:disable Migration/AddReference
end end
end end
...@@ -401,14 +401,13 @@ GET /api/v4/projects/1/branches/my%2Fbranch/commits ...@@ -401,14 +401,13 @@ GET /api/v4/projects/1/branches/my%2Fbranch/commits
## Encoding API parameters of `array` and `hash` types ## Encoding API parameters of `array` and `hash` types
When making an API call with parameters of type `array` and/or `hash`, the parameters may be We can call the API with `array` and `hash` types parameters as shown below:
specified as shown below.
### `array` ### `array`
`import_sources` is a parameter of type `array`: `import_sources` is a parameter of type `array`:
``` ```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" \ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" \
-d "import_sources[]=github" \ -d "import_sources[]=github" \
-d "import_sources[]=bitbucket" \ -d "import_sources[]=bitbucket" \
...@@ -419,7 +418,7 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" \ ...@@ -419,7 +418,7 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" \
`override_params` is a parameter of type `hash`: `override_params` is a parameter of type `hash`:
``` ```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" \ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" \
--form "namespace=email" \ --form "namespace=email" \
--form "path=impapi" \ --form "path=impapi" \
...@@ -429,6 +428,20 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" \ ...@@ -429,6 +428,20 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" \
https://gitlab.example.com/api/v4/projects/import https://gitlab.example.com/api/v4/projects/import
``` ```
### Array of hashes
`variables` is a parameter of type `array` containing hash key/value pairs `[{ 'key' => 'UPLOAD_TO_S3', 'value' => 'true' }]`:
```bash
curl --globoff --request POST --header "PRIVATE-TOKEN: ********************" \
"https://gitlab.example.com/api/v4/projects/169/pipeline?ref=master&variables[][key]=VAR1&variables[][value]=hello&variables[][key]=VAR2&variables[][value]=world"
curl --request POST --header "PRIVATE-TOKEN: ********************" \
--header "Content-Type: application/json" \
--data '{ "ref": "master", "variables": [ {"key": "VAR1", "value": "hello"}, {"key": "VAR2", "value": "world"} ] }' \
"https://gitlab.example.com/api/v4/projects/169/pipeline"
```
## `id` vs `iid` ## `id` vs `iid`
When you work with the API, you may notice two similar fields in API entities: When you work with the API, you may notice two similar fields in API entities:
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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