Commit 506fbc04 authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'rc/ce-to-ee-wednesday' into 'master'

CE Upstream - Wednesday

Closes gitlab-ce#27925, gitlab-com/infrastructure#1139, gitlab-com/www-gitlab-com#1152, and gitlab-ce#27939

See merge request !1239
parents 9d769395 ba60a9dd
......@@ -12,12 +12,18 @@
"localStorage": false
},
"plugins": [
"filenames"
"filenames",
"import"
],
"settings": {
"import/resolver": {
"webpack": {
"config": "./config/webpack.config.js"
}
}
},
"rules": {
"filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"],
"no-multiple-empty-lines": ["error", { "max": 1 }],
"import/no-extraneous-dependencies": "off",
"import/no-unresolved": "off"
"no-multiple-empty-lines": ["error", { "max": 1 }]
}
}
......@@ -109,7 +109,10 @@ setup-test-env:
<<: *dedicated-runner
stage: prepare
script:
- npm install
- node --version
- yarn --version
- yarn install --pure-lockfile
- yarn check # ensure that yarn.lock matches package.json
- bundle exec rake gitlab:assets:compile
- bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
artifacts:
......@@ -228,13 +231,12 @@ karma:
<<: *use-db
<<: *dedicated-runner
script:
- npm link istanbul
- bundle exec rake karma
artifacts:
name: coverage-javascript
expire_in: 31d
paths:
- coverage-javascript/default/
- coverage-javascript/
lint-doc:
stage: test
......@@ -307,11 +309,9 @@ lint:javascript:
paths:
- node_modules/
stage: test
image: "node:7.1"
before_script:
- npm install
before_script: []
script:
- npm --silent run eslint
- yarn run eslint
lint:javascript:report:
<<: *dedicated-runner
......@@ -319,12 +319,10 @@ lint:javascript:report:
paths:
- node_modules/
stage: post-test
image: "node:7.1"
before_script:
- npm install
before_script: []
script:
- find app/ spec/ -name '*.js' -or -name '*.js.es6' -exec sed --in-place 's|/\* eslint-disable .*\*/||' {} \; # run report over all files
- npm --silent run eslint-report || true # ignore exit code
- yarn run eslint-report || true # ignore exit code
artifacts:
name: eslint-report
expire_in: 31d
......@@ -377,7 +375,7 @@ pages:
- mv public/ .public/
- mkdir public/
- mv coverage/ public/coverage-ruby/ || true
- mv coverage-javascript/default/ public/coverage-javascript/ || true
- mv coverage-javascript/ public/coverage-javascript/ || true
- mv eslint-report.html public/ || true
artifacts:
paths:
......
......@@ -29,6 +29,7 @@ gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.2'
gem 'omniauth-google-oauth2', '~> 0.4.1'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-oauth2-generic', '~> 0.2.2'
gem 'omniauth-saml', '~> 1.7.0'
gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
......
......@@ -507,6 +507,8 @@ GEM
omniauth-oauth2 (1.3.1)
oauth2 (~> 1.0)
omniauth (~> 1.2)
omniauth-oauth2-generic (0.2.2)
omniauth-oauth2 (~> 1.0)
omniauth-saml (1.7.0)
omniauth (~> 1.3)
ruby-saml (~> 1.4)
......@@ -961,6 +963,7 @@ DEPENDENCIES
omniauth-gitlab (~> 1.0.2)
omniauth-google-oauth2 (~> 0.4.1)
omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2)
omniauth-saml (~> 1.7.0)
omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0)
......
......@@ -58,8 +58,7 @@ requireAll(require.context('./u2f', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./droplab', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('.', false, /^\.\/(?!application\.js).*\.(js|es6)$/));
require('vendor/fuzzaldrin-plus');
window.ES6Promise = require('vendor/es6-promise.auto');
window.ES6Promise.polyfill();
require('es6-promise').polyfill();
(function () {
document.addEventListener('beforeunload', function () {
......
......@@ -3,5 +3,5 @@
Vue.filter('due-date', (value) => {
const date = new Date(value);
return dateFormat(date, 'mmm d, yyyy');
return dateFormat(date, 'mmm d, yyyy', true);
});
......@@ -20,7 +20,10 @@ $(() => {
gl.commits.PipelinesTableBundle.$destroy(true);
}
gl.commits.pipelines.PipelinesTableBundle = new gl.commits.pipelines.PipelinesTableView({
el: document.querySelector('#commit-pipeline-table-view'),
});
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
gl.commits.pipelines.PipelinesTableBundle = new gl.commits.pipelines.PipelinesTableView();
if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) {
gl.commits.pipelines.PipelinesTableBundle.$mount(pipelineTableViewEl);
}
});
......@@ -8,7 +8,22 @@
* Uses Vue.Resource
*/
class PipelinesService {
constructor(endpoint) {
/**
* FIXME: The url provided to request the pipelines in the new merge request
* page already has `.json`.
* This should be fixed when the endpoint is improved.
*
* @param {String} root
*/
constructor(root) {
let endpoint;
if (root.indexOf('.json') === -1) {
endpoint = `${root}.json`;
} else {
endpoint = root;
}
this.pipelines = Vue.resource(endpoint);
}
......
......@@ -56,15 +56,14 @@ require('./pipelines_store');
},
/**
* When the component is created the service to fetch the data will be
* initialized with the correct endpoint.
* When the component is about to be mounted, tell the service to fetch the data
*
* A request to fetch the pipelines will be made.
* In case of a successfull response we will store the data in the provided
* store, in case of a failed response we need to warn the user.
*
*/
created() {
beforeMount() {
const pipelinesService = new gl.commits.pipelines.PipelinesService(this.endpoint);
this.isLoading = true;
......
......@@ -13,6 +13,12 @@
<div>
<div class="events-description">
{{ stage.description }}
<span v-if="items.length === 50" class="events-info pull-right">
<i class="fa fa-warning has-tooltip"
title="Limited to showing 50 events at most"
data-placement="top"></i>
Showing 50 events
</span>
</div>
<ul class="stage-event-list">
<li v-for="commit in items" class="stage-event-item">
......
......@@ -47,9 +47,11 @@
}
// Only filter asynchronously only if option remote is set
if (this.options.remote) {
$inputContainer.parent().addClass('is-loading');
clearTimeout(timeout);
return timeout = setTimeout(function() {
return this.options.query(this.input.val(), function(data) {
$inputContainer.parent().removeClass('is-loading');
return this.options.callback(data);
}.bind(this));
}.bind(this), 250);
......
......@@ -61,6 +61,7 @@ require('./flash');
constructor({ action, setUrl, stubLocation } = {}) {
this.diffsLoaded = false;
this.pipelinesLoaded = false;
this.commitsLoaded = false;
this.fixedLayoutPref = null;
......@@ -128,6 +129,13 @@ require('./flash');
$.scrollTo('.merge-request-details .merge-request-tabs', {
offset: 0,
});
} else if (action === 'pipelines') {
if (this.pipelinesLoaded) {
return;
}
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
gl.commits.pipelines.PipelinesTableBundle.$mount(pipelineTableViewEl);
this.pipelinesLoaded = true;
} else {
this.expandView();
this.resetViewContainer();
......
......@@ -29,7 +29,7 @@
if (selected.id == null) {
return selected.text;
} else {
return selected.kind + ": " + selected.path;
return selected.kind + ": " + selected.full_path;
}
},
data: function(term, dataCallback) {
......@@ -50,7 +50,7 @@
if (namespace.id == null) {
return namespace.text;
} else {
return namespace.kind + ": " + namespace.path;
return namespace.kind + ": " + namespace.full_path;
}
},
renderRow: this.renderRow,
......
......@@ -25,6 +25,7 @@
bindEvents() {
$('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm);
$('#user_notification_email').on('change', this.submitForm);
$('#user_notified_of_own_activity').on('change', this.submitForm);
$('.update-username').on('ajax:before', this.beforeUpdateUsername);
$('.update-username').on('ajax:complete', this.afterUpdateUsername);
$('.update-notifications').on('ajax:success', this.onUpdateNotifs);
......
......@@ -128,8 +128,7 @@
.note-action-button .link-highlight,
.toolbar-btn,
.dropdown-toggle-caret,
.fa:not(.fa-bell) {
.dropdown-toggle-caret {
@include transition(color);
}
......
......@@ -28,6 +28,8 @@
.avatar {
@extend .avatar-circle;
@include transition-property(none);
width: 40px;
height: 40px;
padding: 0;
......
......@@ -284,7 +284,11 @@
.events-description {
line-height: 65px;
padding-left: $gl-padding;
padding: 0 $gl-padding;
}
.events-info {
color: $gl-text-color-secondary;
}
}
......
......@@ -193,7 +193,6 @@
top: $header-height;
bottom: 0;
right: 0;
z-index: 8;
transition: width .3s;
background: $gray-light;
padding: 10px 20px;
......
......@@ -85,14 +85,18 @@
-webkit-align-items: center;
align-items: center;
i,
svg {
margin-right: 8px;
}
svg {
margin-right: 4px;
position: relative;
top: 1px;
overflow: visible;
}
&> span {
& > span {
padding-right: 4px;
}
......
......@@ -864,7 +864,7 @@
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: 90px;
max-width: 70%;
color: $gl-text-color-secondary;
margin-left: 2px;
display: inline-block;
......
......@@ -35,12 +35,8 @@
margin-bottom: 10px;
}
.project-path {
padding-right: 0;
.form-control {
border-radius: $border-radius-base;
}
.project-path .form-control {
border-radius: $border-radius-base;
}
.input-group > div {
......
......@@ -171,6 +171,8 @@
.tree-controls {
float: right;
margin-top: 11px;
position: relative;
z-index: 2;
.project-action-button {
margin-left: $btn-side-margin;
......
.new-wiki-page {
.new-wiki-page-slug-tip {
display: inline-block;
max-width: 100%;
margin-top: 5px;
}
}
.title .edit-wiki-header {
width: 780px;
margin-left: auto;
......@@ -9,12 +17,18 @@
@extend .top-area;
position: relative;
.wiki-breadcrumb {
border-bottom: 1px solid $white-normal;
padding: 11px 0;
}
.wiki-page-title {
margin: 0;
font-size: 22px;
}
.wiki-last-edit-by {
display: block;
color: $gl-text-color-secondary;
strong {
......@@ -121,6 +135,10 @@
margin: 5px 0 10px;
}
ul.wiki-pages ul {
padding-left: 15px;
}
.wiki-sidebar-header {
padding: 0 $gl-padding $gl-padding;
......@@ -129,3 +147,15 @@
}
}
}
ul.wiki-pages-list.content-list {
& ul {
list-style: none;
margin-left: 0;
padding-left: 15px;
}
& ul li {
padding: 5px 0;
}
}
......@@ -17,6 +17,6 @@ class Profiles::NotificationsController < Profiles::ApplicationController
end
def user_params
params.require(:user).permit(:notification_email)
params.require(:user).permit(:notification_email, :notified_of_own_activity)
end
end
......@@ -8,6 +8,7 @@ class Projects::WikisController < Projects::ApplicationController
def pages
@wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page])
@wiki_entries = WikiPage.group_by_directory(@wiki_pages)
end
def show
......@@ -88,12 +89,7 @@ class Projects::WikisController < Projects::ApplicationController
def destroy
@page = @project_wiki.find_page(params[:id])
if @page
@page.delete
# Triggers repository update on secondary nodes when Geo is enabled
Gitlab::Geo.notify_wiki_update(@project) if Gitlab::Geo.primary?
end
WikiPages::DestroyService.new(@project, current_user).execute(@page)
redirect_to(
namespace_project_wiki_path(@project.namespace, @project, :home),
......@@ -126,7 +122,7 @@ class Projects::WikisController < Projects::ApplicationController
# Call #wiki to make sure the Wiki Repo is initialized
@project_wiki.wiki
@sidebar_wiki_pages = @project_wiki.pages.first(15)
@sidebar_wiki_entries = WikiPage.group_by_directory(@project_wiki.pages.first(15))
rescue ProjectWiki::CouldNotCreateWikiError
flash[:notice] = "Could not create Wiki Repository at this time. Please try again later."
redirect_to project_path(@project)
......
module BuildsHelper
def sidebar_build_class(build, current_build)
build_class = ''
build_class += ' active' if build == current_build
build_class += ' active' if build.id === current_build.id
build_class += ' retried' if build.retried?
build_class
end
......
......@@ -75,10 +75,10 @@ module MergeRequestsHelper
new_namespace_project_merge_request_path(
@project.namespace, @project,
merge_request: {
source_project_id: @merge_request.source_project_id,
target_project_id: @merge_request.target_project_id,
source_branch: @merge_request.source_branch,
target_branch: @merge_request.target_branch,
source_project_id: merge_request.source_project_id,
target_project_id: merge_request.target_project_id,
source_branch: merge_request.source_branch,
target_branch: merge_request.target_branch,
},
change_branches: true
)
......
......@@ -10,7 +10,7 @@ module NamespacesHelper
data_attr_users = { 'data-options-parent' => 'users' }
group_opts = [
"Groups", groups.sort_by(&:human_name).map { |g| [display_path ? g.path : g.human_name, g.id, data_attr_group] }
"Groups", groups.sort_by(&:human_name).map { |g| [display_path ? g.full_path : g.human_name, g.id, data_attr_group] }
]
users_opts = [
......
......@@ -63,7 +63,7 @@ module SubmoduleHelper
namespace = components.pop.gsub(/^\.\.$/, '')
if namespace.empty?
namespace = @project.namespace.path
namespace = @project.namespace.full_path
end
[
......
......@@ -15,6 +15,7 @@ module TodosHelper
when Todo::MARKED then 'added a todo for'
when Todo::APPROVAL_REQUIRED then 'set you as an approver for'
when Todo::UNMERGEABLE then 'Could not merge'
when Todo::DIRECTLY_ADDRESSED then 'directly addressed you on'
end
end
......@@ -88,7 +89,8 @@ module TodosHelper
{ id: Todo::ASSIGNED, text: 'Assigned' },
{ id: Todo::MENTIONED, text: 'Mentioned' },
{ id: Todo::MARKED, text: 'Added' },
{ id: Todo::BUILD_FAILED, text: 'Pipelines' }
{ id: Todo::BUILD_FAILED, text: 'Pipelines' },
{ id: Todo::DIRECTLY_ADDRESSED, text: 'Directly addressed' }
]
end
......
module WikiHelper
# Produces a pure text breadcrumb for a given page.
#
# page_slug - The slug of a WikiPage object.
#
# Returns a String composed of the capitalized name of each directory and the
# capitalized name of the page itself.
def breadcrumb(page_slug)
page_slug.split('/').
map { |dir_or_page| WikiPage.unhyphenize(dir_or_page).capitalize }.
join(' / ')
end
end
......@@ -44,8 +44,15 @@ module Mentionable
end
def all_references(current_user = nil, extractor: nil)
extractor ||= Gitlab::ReferenceExtractor.
new(project, current_user)
# Use custom extractor if it's passed in the function parameters.
if extractor
@extractor = extractor
else
@extractor ||= Gitlab::ReferenceExtractor.
new(project, current_user)
@extractor.reset_memoized_values
end
self.class.mentionable_attrs.each do |attr, options|
text = __send__(attr)
......@@ -55,16 +62,20 @@ module Mentionable
skip_project_check: skip_project_check?
)
extractor.analyze(text, options)
@extractor.analyze(text, options)
end
extractor
@extractor
end
def mentioned_users(current_user = nil)
all_references(current_user).users
end
def directly_addressed_users(current_user = nil)
all_references(current_user).directly_addressed_users
end
# Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
def referenced_mentionables(current_user = self.author)
refs = all_references(current_user)
......
class DirectlyAddressedUser
class << self
def reference_pattern
User.reference_pattern
end
end
end
......@@ -50,9 +50,10 @@ class Event < ActiveRecord::Base
class << self
# Update Gitlab::ContributionsCalendar#activity_dates if this changes
def contributions
where("action = ? OR (target_type in (?) AND action in (?))",
Event::PUSHED, ["MergeRequest", "Issue"],
[Event::CREATED, Event::CLOSED, Event::MERGED])
where("action = ? OR (target_type IN (?) AND action IN (?)) OR (target_type = ? AND action = ?)",
Event::PUSHED,
["MergeRequest", "Issue"], [Event::CREATED, Event::CLOSED, Event::MERGED],
"Note", Event::COMMENTED)
end
def limit_recent(limit = 20, offset = nil)
......
......@@ -97,7 +97,7 @@ class Group < Namespace
end
def to_reference(_from_project = nil, full: nil)
"#{self.class.reference_prefix}#{name}"
"#{self.class.reference_prefix}#{full_path}"
end
def web_url
......
......@@ -584,7 +584,7 @@ class MergeRequest < ActiveRecord::Base
# Return the set of issues that will be closed if this merge request is accepted.
def closes_issues(current_user = self.author)
if target_branch == project.default_branch
messages = [description]
messages = [title, description]
messages.concat(commits.map(&:safe_message)) if merge_request_diff
Gitlab::ClosingIssueExtractor.new(project, current_user).
......@@ -598,7 +598,7 @@ class MergeRequest < ActiveRecord::Base
return [] unless target_branch == project.default_branch
ext = Gitlab::ReferenceExtractor.new(project, current_user)
ext.analyze(description)
ext.analyze("#{title}\n#{description}")
ext.issues - closes_issues(current_user)
end
......@@ -621,7 +621,7 @@ class MergeRequest < ActiveRecord::Base
def source_project_namespace
if source_project && source_project.namespace
source_project.namespace.path
source_project.namespace.full_path
else
"(removed)"
end
......@@ -629,7 +629,7 @@ class MergeRequest < ActiveRecord::Base
def target_project_namespace
if target_project && target_project.namespace
target_project.namespace.path
target_project.namespace.full_path
else
"(removed)"
end
......
......@@ -493,7 +493,7 @@ class Project < ActiveRecord::Base
if forked?
job_id = RepositoryForkWorker.perform_async(id, forked_from_project.repository_storage_path,
forked_from_project.path_with_namespace,
self.namespace.path)
self.namespace.full_path)
else
job_id = RepositoryImportWorker.perform_async(self.id)
end
......@@ -1057,8 +1057,8 @@ class Project < ActiveRecord::Base
Gitlab::AppLogger.info "Project was renamed: #{old_path_with_namespace} -> #{new_path_with_namespace}"
Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.path)
Gitlab::PagesTransfer.new.rename_project(path_was, path, namespace.path)
Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.full_path)
Gitlab::PagesTransfer.new.rename_project(path_was, path, namespace.full_path)
end
# Expires various caches before a project is renamed.
......@@ -1298,19 +1298,25 @@ class Project < ActiveRecord::Base
end
def pages_url
subdomain, _, url_path = full_path.partition('/')
# The hostname always needs to be in downcased
# All web servers convert hostname to lowercase
host = "#{namespace.path}.#{Settings.pages.host}".downcase
host = "#{subdomain}.#{Settings.pages.host}".downcase
# The host in URL always needs to be downcased
url = Gitlab.config.pages.url.sub(/^https?:\/\//) do |prefix|
"#{prefix}#{namespace.path}."
"#{prefix}#{subdomain}."
end.downcase
# If the project path is the same as host, we serve it as group page
return url if host == path
return url if host == url_path
"#{url}/#{url_path}"
end
"#{url}/#{path}"
def pages_subdomain
full_path.partition('/').first
end
def pages_path
......@@ -1327,8 +1333,8 @@ class Project < ActiveRecord::Base
# 3. We asynchronously remove pages with force
temp_path = "#{path}.#{SecureRandom.hex}.deleted"
if Gitlab::PagesTransfer.new.rename_project(path, temp_path, namespace.path)
PagesWorker.perform_in(5.minutes, :remove, namespace.path, temp_path)
if Gitlab::PagesTransfer.new.rename_project(path, temp_path, namespace.full_path)
PagesWorker.perform_in(5.minutes, :remove, namespace.full_path, temp_path)
end
end
......@@ -1432,7 +1438,7 @@ class Project < ActiveRecord::Base
end
def ensure_dir_exist
gitlab_shell.add_namespace(repository_storage_path, namespace.path)
gitlab_shell.add_namespace(repository_storage_path, namespace.full_path)
end
def predefined_variables
......@@ -1440,7 +1446,7 @@ class Project < ActiveRecord::Base
{ key: 'CI_PROJECT_ID', value: id.to_s, public: true },
{ key: 'CI_PROJECT_NAME', value: path, public: true },
{ key: 'CI_PROJECT_PATH', value: path_with_namespace, public: true },
{ key: 'CI_PROJECT_NAMESPACE', value: namespace.path, public: true },
{ key: 'CI_PROJECT_NAMESPACE', value: namespace.full_path, public: true },
{ key: 'CI_PROJECT_URL', value: web_url, public: true }
]
end
......
......@@ -12,7 +12,7 @@ class DroneCiService < CiService
def compose_service_hook
hook = service_hook || build_service_hook
# If using a service template, project may not be available
hook.url = [drone_url, "/api/hook", "?owner=#{project.namespace.path}", "&name=#{project.path}", "&access_token=#{token}"].join if project
hook.url = [drone_url, "/api/hook", "?owner=#{project.namespace.full_path}", "&name=#{project.path}", "&access_token=#{token}"].join if project
hook.enable_ssl_verification = !!enable_ssl_verification
hook.save
end
......@@ -38,7 +38,7 @@ class DroneCiService < CiService
def commit_status_path(sha, ref)
url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}",
"gitlab/#{project.full_path}/commits/#{sha}",
"?branch=#{URI::encode(ref.to_s)}&access_token=#{token}"]
URI.join(*url).to_s
......@@ -73,7 +73,7 @@ class DroneCiService < CiService
def build_page(sha, ref)
url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}",
"gitlab/#{project.full_path}/redirect/commits/#{sha}",
"?branch=#{URI::encode(ref.to_s)}"]
URI.join(*url).to_s
......
......@@ -1319,6 +1319,14 @@ class Repository
action[:content]
end
detect = CharlockHolmes::EncodingDetector.new.detect(content) if content
unless detect && detect[:type] == :binary
# When writing to the repo directly as we are doing here,
# the `core.autocrlf` config isn't taken into account.
content.gsub!("\r\n", "\n") if self.autocrlf
end
oid = rugged.write(content, :blob)
index.add(path: path, oid: oid, mode: mode)
......
class Todo < ActiveRecord::Base
include Sortable
ASSIGNED = 1
MENTIONED = 2
BUILD_FAILED = 3
MARKED = 4
APPROVAL_REQUIRED = 5 # This is an EE-only feature
UNMERGEABLE = 6
ASSIGNED = 1
MENTIONED = 2
BUILD_FAILED = 3
MARKED = 4
APPROVAL_REQUIRED = 5 # This is an EE-only feature
UNMERGEABLE = 6
DIRECTLY_ADDRESSED = 7
ACTION_NAMES = {
ASSIGNED => :assigned,
......@@ -14,7 +15,8 @@ class Todo < ActiveRecord::Base
BUILD_FAILED => :build_failed,
MARKED => :marked,
APPROVAL_REQUIRED => :approval_required,
UNMERGEABLE => :unmergeable
UNMERGEABLE => :unmergeable,
DIRECTLY_ADDRESSED => :directly_addressed
}
belongs_to :author, class_name: "User"
......
......@@ -53,7 +53,12 @@ class User < ActiveRecord::Base
has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id
# Profile
has_many :keys, dependent: :destroy
has_many :keys, -> do
type = Key.arel_table[:type]
where(type.not_eq('DeployKey').or(type.eq(nil)))
end, dependent: :destroy
has_many :deploy_keys, -> { where(type: 'DeployKey') }, dependent: :destroy
has_many :emails, dependent: :destroy
has_many :personal_access_tokens, dependent: :destroy
has_many :identities, dependent: :destroy, autosave: true
......@@ -355,7 +360,7 @@ class User < ActiveRecord::Base
def reference_pattern
%r{
#{Regexp.escape(reference_prefix)}
(?<user>#{Gitlab::Regex::NAMESPACE_REGEX_STR})
(?<user>#{Gitlab::Regex::NAMESPACE_REF_REGEX_STR})
}x
end
end
......
class WikiDirectory
include ActiveModel::Validations
attr_accessor :slug, :pages
validates :slug, presence: true
def initialize(slug, pages = [])
@slug = slug
@pages = pages
end
# Relative path to the partial to be used when rendering collections
# of this object.
def to_partial_path
'projects/wikis/wiki_directory'
end
end
......@@ -12,6 +12,32 @@ class WikiPage
ActiveModel::Name.new(self, nil, 'wiki')
end
# Sorts and groups pages by directory.
#
# pages - an array of WikiPage objects.
#
# Returns an array of WikiPage and WikiDirectory objects. The entries are
# sorted by alphabetical order (directories and pages inside each directory).
# Pages at the root level come before everything.
def self.group_by_directory(pages)
return [] if pages.blank?
pages.sort_by { |page| [page.directory, page.slug] }.
group_by(&:directory).
map do |dir, pages|
if dir.present?
WikiDirectory.new(dir, pages)
else
pages
end
end.
flatten
end
def self.unhyphenize(name)
name.gsub(/-+/, ' ')
end
def to_key
[:slug]
end
......@@ -56,7 +82,7 @@ class WikiPage
# The formatted title of this page.
def title
if @attributes[:title]
@attributes[:title].gsub(/-+/, ' ')
self.class.unhyphenize(@attributes[:title])
else
""
end
......@@ -72,6 +98,11 @@ class WikiPage
@attributes[:content] ||= @page&.text_data
end
# The hierarchy of the directory this page is contained in.
def directory
wiki.page_title_and_dir(slug).last
end
# The processed/formatted content of this page.
def formatted_content
@attributes[:formatted_content] ||= @page&.formatted_data
......@@ -170,6 +201,16 @@ class WikiPage
end
end
# Relative path to the partial to be used when rendering collections
# of this object.
def to_partial_path
'projects/wikis/wiki_page'
end
def id
page.version.to_s
end
private
def set_attributes
......
......@@ -161,7 +161,11 @@ module MergeRequests
return unless @commits.present?
merge_requests_for_source_branch.each do |merge_request|
wip_commit = @commits.detect(&:work_in_progress?)
commit_shas = merge_request.commits_sha
wip_commit = @commits.detect do |commit|
commit.work_in_progress? && commit_shas.include?(commit.sha)
end
if wip_commit && !merge_request.work_in_progress?
merge_request.update(title: merge_request.wip_title)
......
......@@ -234,7 +234,7 @@ class NotificationService
recipients = reject_unsubscribed_users(recipients, note.noteable)
recipients = reject_users_without_access(recipients, note.noteable)
recipients.delete(note.author)
recipients.delete(note.author) unless note.author.notified_of_own_activity?
recipients = recipients.uniq
notify_method = "note_#{note.to_ability_name}_email".to_sym
......@@ -344,8 +344,9 @@ class NotificationService
recipients ||= build_recipients(
pipeline,
pipeline.project,
nil, # The acting user, who won't be added to recipients
action: pipeline.status).map(&:notification_email)
pipeline.user,
action: pipeline.status,
skip_current_user: false).map(&:notification_email)
if recipients.any?
mailer.public_send(email_template, pipeline, recipients).deliver_later
......@@ -668,7 +669,7 @@ class NotificationService
recipients = reject_unsubscribed_users(recipients, target)
recipients = reject_users_without_access(recipients, target)
recipients.delete(current_user) if skip_current_user
recipients.delete(current_user) if skip_current_user && !current_user.notified_of_own_activity?
recipients.uniq
end
......@@ -677,7 +678,7 @@ class NotificationService
recipients = add_labels_subscribers([], project, target, labels: labels)
recipients = reject_unsubscribed_users(recipients, target)
recipients = reject_users_without_access(recipients, target)
recipients.delete(current_user)
recipients.delete(current_user) unless current_user.notified_of_own_activity?
recipients.uniq
end
......
......@@ -36,7 +36,7 @@ module Projects
def groups
current_user.authorized_groups.sort_by(&:path).map do |group|
count = group.users.count
{ username: group.path, name: group.name, count: count, avatar_url: group.avatar_url }
{ username: group.full_path, name: group.full_name, count: count, avatar_url: group.avatar_url }
end
end
......
......@@ -30,7 +30,7 @@ module Projects
Project.transaction do
old_path = project.path_with_namespace
old_group = project.group
new_path = File.join(new_namespace.try(:path) || '', project.path)
new_path = File.join(new_namespace.try(:full_path) || '', project.path)
if Project.where(path: project.path, namespace_id: new_namespace.try(:id)).present?
raise TransferError.new("Project with same path in target namespace already exists")
......@@ -63,10 +63,10 @@ module Projects
Labels::TransferService.new(current_user, old_group, project).execute
# Move uploads
Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path)
Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.full_path, new_namespace.full_path)
# Move pages
Gitlab::PagesTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path)
Gitlab::PagesTransfer.new.move_project(project.path, old_namespace.full_path, new_namespace.full_path)
project.old_path_with_namespace = old_path
......
......@@ -256,6 +256,12 @@ class TodoService
end
def create_mention_todos(project, target, author, note = nil)
# Create Todos for directly addressed users
directly_addressed_users = filter_directly_addressed_users(project, note || target, author)
attributes = attributes_for_todo(project, target, author, Todo::DIRECTLY_ADDRESSED, note)
create_todos(directly_addressed_users, attributes)
# Create Todos for mentioned users
mentioned_users = filter_mentioned_users(project, note || target, author)
attributes = attributes_for_todo(project, target, author, Todo::MENTIONED, note)
create_todos(mentioned_users, attributes)
......@@ -300,10 +306,18 @@ class TodoService
)
end
def filter_todo_users(users, project, target)
reject_users_without_access(users, project, target).uniq
end
def filter_mentioned_users(project, target, author)
mentioned_users = target.mentioned_users(author)
mentioned_users = reject_users_without_access(mentioned_users, project, target)
mentioned_users.uniq
filter_todo_users(mentioned_users, project, target)
end
def filter_directly_addressed_users(project, target, author)
directly_addressed_users = target.directly_addressed_users(author)
filter_todo_users(directly_addressed_users, project, target)
end
def reject_users_without_access(users, project, target)
......
module WikiPages
class DestroyService < WikiPages::BaseService
def execute(page)
if page&.delete
execute_hooks(page, 'delete')
# Triggers repository update on secondary nodes when Geo is enabled
Gitlab::Geo.notify_wiki_update(project) if Gitlab::Geo.primary?
end
page
end
end
end
......@@ -182,6 +182,6 @@
- @groups.each do |group|
%p
= link_to [:admin, group], class: 'str-truncated-60' do
= group.name
= group.full_name
%span.light.pull-right
#{time_ago_with_tooltip(group.created_at)}
......@@ -30,7 +30,7 @@
- toggle_text = 'Namespace'
- if params[:namespace_id].present?
- namespace = Namespace.find(params[:namespace_id])
- toggle_text = "#{namespace.kind}: #{namespace.path}"
- toggle_text = "#{namespace.kind}: #{namespace.full_path}"
= dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { toggle_class: 'js-namespace-select large' })
.dropdown-menu.dropdown-select.dropdown-menu-align-right
= dropdown_title('Namespaces')
......
%ul.nav-links
%li{ class: ("active" unless params[:filter]) }>
= link_to activity_dashboard_path, class: 'shortcuts-activity', data: {placement: 'right'} do
Your Projects
%li{ class: ("active" if params[:filter] == 'starred') }>
= link_to activity_dashboard_path(filter: 'starred'), data: {placement: 'right'} do
Starred Projects
.top-area
%ul.nav-links
%li{ class: ("active" unless params[:filter]) }>
= link_to activity_dashboard_path, class: 'shortcuts-activity', data: {placement: 'right'} do
Your Projects
%li{ class: ("active" if params[:filter] == 'starred') }>
= link_to activity_dashboard_path(filter: 'starred'), data: {placement: 'right'} do
Starred Projects
......@@ -4,7 +4,7 @@
import_button = tr.find(".btn-import")
origin_target = target_field.text()
project_name = "#{@project_name}"
origin_namespace = "#{@target_namespace.path}"
origin_namespace = "#{@target_namespace.full_path}"
target_field.empty()
target_field.append("<p class='alert alert-danger'>This namespace has already been taken! Please choose another one.</p>")
target_field.append("<input type='text' name='target_namespace' />")
......
......@@ -34,6 +34,11 @@
.clearfix
= form_for @user, url: profile_notifications_path, method: :put do |f|
%label{ for: 'user_notified_of_own_activity' }
= f.check_box :notified_of_own_activity
%span Receive notifications about your own activity
%hr
%h5
Groups (#{@group_notifications.count})
......
#commit-pipeline-table-view{ data: { endpoint: endpoint } }
- disable_initialization = local_assigns.fetch(:disable_initialization, false)
#commit-pipeline-table-view{ data: { disable_initialization: disable_initialization,
endpoint: endpoint,
} }
.pipeline-svgs{ data: { "commit_icon_svg" => custom_icon("icon_commit"),
"icon_status_canceled" => custom_icon("icon_status_canceled"),
"icon_status_running" => custom_icon("icon_status_running"),
......
......@@ -10,13 +10,13 @@
- if diff_file.renamed_file
- old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
%strong.file-title-name.has-tooltip{ data: { title: old_path } }
%strong.file-title-name.has-tooltip{ data: { title: old_path, container: 'body' } }
= old_path
&rarr;
%strong.file-title-name.has-tooltip{ data: { title: new_path } }
%strong.file-title-name.has-tooltip{ data: { title: new_path, container: 'body' } }
= new_path
- else
%strong.file-title-name.has-tooltip{ data: { title: diff_file.new_path } }
%strong.file-title-name.has-tooltip{ data: { title: diff_file.new_path, container: 'body' } }
= diff_file.new_path
- if diff_file.deleted_file
deleted
......
......@@ -6,7 +6,7 @@
.col-lg-9
.project-edit-errors
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f|
%fieldset.append-bottom-0
%fieldset
.row
.form-group.col-md-9
= f.label :name, class: 'label-light', for: 'project_name_edit' do
......@@ -42,7 +42,7 @@
= f.text_field :tag_list, value: @project.tag_list.to_s, maxlength: 2000, class: "form-control"
%p.help-block Separate tags with commas.
%hr
%fieldset.append-bottom-0
%fieldset
%h5.prepend-top-0
Sharing &amp; Permissions
.form_group.prepend-top-20.sharing-and-permissions
......@@ -243,7 +243,7 @@
.form-group
.input-group
.input-group-addon
#{URI.join(root_url, @project.namespace.path)}/
#{URI.join(root_url, @project.namespace.full_path)}/
= f.text_field :path, class: 'form-control'
%ul
%li Be careful. Renaming a project's repository can have unintended side effects.
......
......@@ -39,7 +39,7 @@
= icon("folder-open-o", class: "settings-list-icon")
.pull-left
= link_to group do
= group.name
= group.full_name
%br
up to #{group_link.human_access}
- if group_link.expires?
......
......@@ -94,7 +94,7 @@
-# This tab is always loaded via AJAX
#pipelines.pipelines.tab-pane
- if @pipelines.any?
= render 'projects/commit/pipelines_list', endpoint: pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
= render 'projects/commit/pipelines_list', disable_initialization: true, endpoint: pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
#diffs.diffs.tab-pane
-# This tab is always loaded via AJAX
......
......@@ -12,7 +12,7 @@
Create or Import your project from popular Git services
.col-lg-9
= form_for @project, html: { class: 'new_project' } do |f|
%fieldset.append-bottom-0
.row
.form-group.col-xs-12.col-sm-6
= f.label :namespace_id, class: 'label-light' do
%span
......
......@@ -17,7 +17,7 @@
%p
To access the domain create a new DNS record:
%pre
#{@domain.domain} CNAME #{@domain.project.namespace.path}.#{Settings.pages.host}.
#{@domain.domain} CNAME #{@domain.project.pages_subdomain}.#{Settings.pages.host}.
%tr
%td
Certificate
......
......@@ -13,5 +13,9 @@
= label_tag :new_wiki_path do
%span Page slug
= text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project), autofocus: true
%span.new-wiki-page-slug-tip
= icon('lightbulb-o')
Tip: You can specify the full path for the new file.
We will automatically create any missing directories.
.form-actions
= button_tag 'Create Page', class: 'build-new-wiki btn btn-create'
%li
= link_to wiki_page.title, namespace_project_wiki_path(@project.namespace, @project, wiki_page)
%small (#{wiki_page.format})
.pull-right
%small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)}
......@@ -12,10 +12,8 @@
.blocks-container
.block.block-first
%ul.wiki-pages
- @sidebar_wiki_pages.each do |wiki_page|
%li{ class: params[:id] == wiki_page.slug ? 'active' : '' }
= link_to namespace_project_wiki_path(@project.namespace, @project, wiki_page) do
= wiki_page.title.capitalize
= render @sidebar_wiki_entries, context: 'sidebar'
.block
= link_to namespace_project_wikis_pages_path(@project.namespace, @project), class: 'btn btn-block' do
More Pages
......
%li{ class: params[:id] == wiki_page.slug ? 'active' : '' }
= link_to namespace_project_wiki_path(@project.namespace, @project, wiki_page) do
= wiki_page.title.capitalize
%li
= wiki_directory.slug
%ul
= render wiki_directory.pages, context: context
= render "#{context}_wiki_page", wiki_page: wiki_page
......@@ -13,11 +13,7 @@
= icon('cloud-download')
Clone repository
%ul.content-list
- @wiki_pages.each do |wiki_page|
%li
= link_to wiki_page.title, namespace_project_wiki_path(@project.namespace, @project, wiki_page)
%small (#{wiki_page.format})
.pull-right
%small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)}
%ul.wiki-pages-list.content-list
= render @wiki_entries, context: 'pages'
= paginate @wiki_pages, theme: 'gitlab'
......@@ -6,9 +6,11 @@
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
.wiki-breadcrumb
%span= breadcrumb(@page.slug)
.nav-text
%h2.wiki-page-title= @page.title.capitalize
%span.wiki-last-edit-by
Last edited by
%strong
......
......@@ -6,7 +6,7 @@
%span.list-item-name
= image_tag group_icon(group), class: "avatar s40", alt: ''
%strong
= link_to group.name, group_path(group)
= link_to group.full_name, group_path(group)
.cgray
Joined #{time_ago_with_tooltip(group.created_at)}
- if group_link.expires?
......
.clearfix.calendar
.js-contrib-calendar
.calendar-hint
Summary of issues, merge requests, and push events
Summary of issues, merge requests, push events, and comments
:javascript
new Calendar(
#{@activity_dates.to_json},
......
......@@ -10,11 +10,17 @@
%i.fa.fa-clock-o
= event.created_at.to_s(:time)
- if event.push?
#{event.action_name} #{event.ref_type} #{event.ref_name}
#{event.action_name} #{event.ref_type}
%strong
- commits_path = namespace_project_commits_path(event.project.namespace, event.project, event.ref_name)
= link_to_if event.project.repository.branch_exists?(event.ref_name), event.ref_name, commits_path
- else
= event_action_name(event)
- if event.target
%strong= link_to "#{event.target.to_reference}", [event.project.namespace.becomes(Namespace), event.project, event.target]
%strong
- if event.note?
= link_to event.note_target.to_reference, event_note_target_path(event)
- elsif event.target
= link_to event.target.to_reference, [event.project.namespace.becomes(Namespace), event.project, event.target]
at
%strong
......
......@@ -106,6 +106,8 @@
%i.fa.fa-spinner.fa-spin
.user-calendar-activities
%h4.prepend-top-20
Most Recent Activity
.content_list{ data: { href: user_path } }
= spinner
......
---
title: Execute web hooks for WikiPage delete operation
merge_request: 8198
author:
---
title: Add discussion events to contributions calendar
merge_request: 8821
author:
---
title: V3 deprecated templates endpoints removal
merge_request: 8853
author:
---
title: Show directory hierarchy when listing wiki pages
merge_request: 8133
author: Alex Braha Stoll
---
title: Show Issues mentioned / being closed from a Merge Requests title below the
'Accept Merge Request' button
merge_request: 9194
author: Jan Christophersen
---
title: Added a feature to create a 'directly addressed' Todo when mentioned in the beginning of a line.
merge_request: 7926
author: Ershad Kunnakkadan
---
title: Added 'Most Recent Activity' header to the User Profile page
merge_request: 9189
author: Jan Christophersen
---
title: Add Links to Branches in Calendar Activity
merge_request: 9224
author: Jan Christophersen
title: Add the oauth2_generic OmniAuth strategy
merge_request: 9048
author: Joe Marty
\ No newline at end of file
---
title: Do not display deploy keys in user's own ssh keys list
merge_request: 9024
author:
---
title: "Fix small height of activity header page"
merge_request: 8952
author: Pavel Sorokin
---
title: Update doc for enabling or disabling GitLab CI
merge_request: 8965
author: Takuya Noguchi
---
title: Set maximum width for mini pipeline graph text so it is not truncated to early
merge_request: 9188
author:
---
title: Fix stray pipelines API request when showing MR
merge_request:
author:
---
title: Fix Merge request pipelines displays JSON
merge_request:
author:
---
title: Fix current build arrow indicator
merge_request:
author:
---
title: Add space between text and loading icon in Megre Request Widget
merge_request: 9119
author:
---
title: Display loading indicator when filtering ref switcher dropdown
merge_request:
author:
---
title: Adds container to tooltip in order to make it work with overflow:hidden in
parent element
merge_request:
author:
---
title: Fix z index issues with sidebar
merge_request:
author:
---
title: Reintroduce coverage report for JavaScript
merge_request: 9133
author: winniehell
---
title: Replace static fixture for header_spec.js
merge_request: 9174
author: winniehell
---
title: Replace static fixture for right_sidebar_spec.js
merge_request: 9211
author: winniehell
---
title: Don't connect in Gitlab::Database.adapter_name
merge_request:
author:
---
title: Add limit to the number of events showed in cycle analytics
merge_request:
author:
---
title: Fix timezone on issue boards due date
merge_request:
author:
---
title: Disable invalid service templates
merge_request:
author:
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.
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.
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.
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.
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