Commit decf0443 authored by Eric Eastwood's avatar Eric Eastwood

Merge branch 'master' into 34102-online-view-of-artifacts-fe

parents 50063d86 5a23af92
...@@ -63,4 +63,5 @@ eslint-report.html ...@@ -63,4 +63,5 @@ eslint-report.html
/.gitlab_workhorse_secret /.gitlab_workhorse_secret
/webpack-report/ /webpack-report/
/locale/**/LC_MESSAGES /locale/**/LC_MESSAGES
/locale/**/*.time_stamp
/.rspec /.rspec
...@@ -404,6 +404,7 @@ docs lint: ...@@ -404,6 +404,7 @@ docs lint:
before_script: [] before_script: []
script: script:
- scripts/lint-doc.sh - scripts/lint-doc.sh
- scripts/lint-changelog-yaml
- mv doc/ /nanoc/content/ - mv doc/ /nanoc/content/
- cd /nanoc - cd /nanoc
# Build HTML from Markdown # Build HTML from Markdown
......
...@@ -195,6 +195,13 @@ entry. ...@@ -195,6 +195,13 @@ entry.
- Added type to CHANGELOG entries. (Jacopo Beschi @jacopo-beschi) - Added type to CHANGELOG entries. (Jacopo Beschi @jacopo-beschi)
- [BUGIFX] Improves subgroup creation permissions. !13418 - [BUGIFX] Improves subgroup creation permissions. !13418
## 9.5.6 (2017-09-29)
- [FIXED] Fix MR ready to merge buttons/controls at mobile breakpoint. !14242
- [FIXED] Fix errors thrown in merge request widget with external CI service/integration.
- [FIXED] Update x/x discussions resolved checkmark icon to be green when all discussions resolved.
- [FIXED] Fix 500 error on merged merge requests when GitLab is restored from a backup.
## 9.5.5 (2017-09-18) ## 9.5.5 (2017-09-18)
- [SECURITY] Upgrade mail and nokogiri gems due to security issues. !13662 (Markus Koller) - [SECURITY] Upgrade mail and nokogiri gems due to security issues. !13662 (Markus Koller)
......
...@@ -229,6 +229,10 @@ members to further discuss scope, design, and technical considerations. This wil ...@@ -229,6 +229,10 @@ members to further discuss scope, design, and technical considerations. This wil
ensure that that your contribution is aligned with the GitLab product and minimize ensure that that your contribution is aligned with the GitLab product and minimize
any rework and delay in getting it merged into master. any rework and delay in getting it merged into master.
GitLab team members who apply the ~"Accepting Merge Requests" label to an issue
should update the issue description with a responsible product manager, inviting
any potential community contributor to @-mention per above.
[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=Accepting+Merge+Requests&scope=all&sort=weight_asc&state=opened [up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=Accepting+Merge+Requests&scope=all&sort=weight_asc&state=opened
[firt-timers]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=Accepting+Merge+Requests&scope=all&sort=upvotes_desc&state=opened&weight=1 [firt-timers]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=Accepting+Merge+Requests&scope=all&sort=upvotes_desc&state=opened&weight=1
......
...@@ -105,7 +105,7 @@ gem 'fog-rackspace', '~> 0.1.1' ...@@ -105,7 +105,7 @@ gem 'fog-rackspace', '~> 0.1.1'
gem 'fog-aliyun', '~> 0.1.0' gem 'fog-aliyun', '~> 0.1.0'
# for Google storage # for Google storage
gem 'google-api-client', '~> 0.8.6' gem 'google-api-client', '~> 0.13.6'
# for aws storage # for aws storage
gem 'unf', '~> 0.1.4' gem 'unf', '~> 0.1.4'
...@@ -239,7 +239,7 @@ gem 'rack-proxy', '~> 0.6.0' ...@@ -239,7 +239,7 @@ gem 'rack-proxy', '~> 0.6.0'
gem 'sass-rails', '~> 5.0.6' gem 'sass-rails', '~> 5.0.6'
gem 'uglifier', '~> 2.7.2' gem 'uglifier', '~> 2.7.2'
gem 'addressable', '~> 2.3.8' gem 'addressable', '~> 2.5.2'
gem 'bootstrap-sass', '~> 3.3.0' gem 'bootstrap-sass', '~> 3.3.0'
gem 'font-awesome-rails', '~> 4.7' gem 'font-awesome-rails', '~> 4.7'
gem 'gemojione', '~> 3.3' gem 'gemojione', '~> 3.3'
...@@ -356,7 +356,7 @@ end ...@@ -356,7 +356,7 @@ end
group :test do group :test do
gem 'shoulda-matchers', '~> 3.1.2', require: false gem 'shoulda-matchers', '~> 3.1.2', require: false
gem 'email_spec', '~> 1.6.0' gem 'email_spec', '~> 1.6.0'
gem 'json-schema', '~> 2.6.2' gem 'json-schema', '~> 2.8.0'
gem 'webmock', '~> 2.3.2' gem 'webmock', '~> 2.3.2'
gem 'test_after_commit', '~> 1.1' gem 'test_after_commit', '~> 1.1'
gem 'sham_rack', '~> 1.3.6' gem 'sham_rack', '~> 1.3.6'
...@@ -398,7 +398,7 @@ group :ed25519 do ...@@ -398,7 +398,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 0.37.0', require: 'gitaly' gem 'gitaly-proto', '~> 0.39.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false gem 'toml-rb', '~> 0.3.15', require: false
......
...@@ -45,7 +45,8 @@ GEM ...@@ -45,7 +45,8 @@ GEM
adamantium (0.2.0) adamantium (0.2.0)
ice_nine (~> 0.11.0) ice_nine (~> 0.11.0)
memoizable (~> 0.4.0) memoizable (~> 0.4.0)
addressable (2.3.8) addressable (2.5.2)
public_suffix (>= 2.0.2, < 4.0)
akismet (2.0.0) akismet (2.0.0)
allocations (1.0.5) allocations (1.0.5)
arel (6.0.4) arel (6.0.4)
...@@ -62,10 +63,6 @@ GEM ...@@ -62,10 +63,6 @@ GEM
attr_encrypted (3.0.3) attr_encrypted (3.0.3)
encryptor (~> 3.0.0) encryptor (~> 3.0.0)
attr_required (1.0.0) attr_required (1.0.0)
autoparse (0.3.3)
addressable (>= 2.3.1)
extlib (>= 0.9.15)
multi_json (>= 1.0.0)
autoprefixer-rails (6.2.3) autoprefixer-rails (6.2.3)
execjs execjs
json json
...@@ -146,6 +143,8 @@ GEM ...@@ -146,6 +143,8 @@ GEM
debugger-ruby_core_source (1.3.8) debugger-ruby_core_source (1.3.8)
deckar01-task_list (2.0.0) deckar01-task_list (2.0.0)
html-pipeline html-pipeline
declarative (0.0.10)
declarative-option (0.1.0)
default_value_for (3.0.2) default_value_for (3.0.2)
activerecord (>= 3.2.0, < 5.1) activerecord (>= 3.2.0, < 5.1)
descendants_tracker (0.0.4) descendants_tracker (0.0.4)
...@@ -188,7 +187,6 @@ GEM ...@@ -188,7 +187,6 @@ GEM
excon (0.57.1) excon (0.57.1)
execjs (2.6.0) execjs (2.6.0)
expression_parser (0.9.0) expression_parser (0.9.0)
extlib (0.9.16)
factory_girl (4.7.0) factory_girl (4.7.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
factory_girl_rails (4.7.0) factory_girl_rails (4.7.0)
...@@ -275,7 +273,7 @@ GEM ...@@ -275,7 +273,7 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gherkin-ruby (0.3.2) gherkin-ruby (0.3.2)
gitaly-proto (0.37.0) gitaly-proto (0.39.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.0) grpc (~> 1.0)
github-linguist (4.7.6) github-linguist (4.7.6)
...@@ -288,10 +286,10 @@ GEM ...@@ -288,10 +286,10 @@ GEM
flowdock (~> 0.7) flowdock (~> 0.7)
gitlab-grit (>= 2.4.1) gitlab-grit (>= 2.4.1)
multi_json multi_json
gitlab-grit (2.8.1) gitlab-grit (2.8.2)
charlock_holmes (~> 0.6) charlock_holmes (~> 0.6)
diff-lcs (~> 1.1) diff-lcs (~> 1.1)
mime-types (>= 1.16, < 3) mime-types (>= 1.16)
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab-markup (1.6.2) gitlab-markup (1.6.2)
gitlab_omniauth-ldap (2.0.4) gitlab_omniauth-ldap (2.0.4)
...@@ -319,20 +317,16 @@ GEM ...@@ -319,20 +317,16 @@ GEM
json json
multi_json multi_json
request_store (>= 1.0) request_store (>= 1.0)
google-api-client (0.8.7) google-api-client (0.13.6)
activesupport (>= 3.2, < 5.0) addressable (~> 2.5, >= 2.5.1)
addressable (~> 2.3) googleauth (~> 0.5)
autoparse (~> 0.3) httpclient (>= 2.8.1, < 3.0)
extlib (~> 0.9) mime-types (~> 3.0)
faraday (~> 0.9) representable (~> 3.0)
googleauth (~> 0.3) retriable (>= 2.0, < 4.0)
launchy (~> 2.4)
multi_json (~> 1.10)
retriable (~> 1.4)
signet (~> 0.6)
google-protobuf (3.4.0.2) google-protobuf (3.4.0.2)
googleauth (0.5.1) googleauth (0.5.3)
faraday (~> 0.9) faraday (~> 0.12)
jwt (~> 1.4) jwt (~> 1.4)
logging (~> 2.0) logging (~> 2.0)
memoist (~> 0.12) memoist (~> 0.12)
...@@ -422,8 +416,8 @@ GEM ...@@ -422,8 +416,8 @@ GEM
multi_json (>= 1.3) multi_json (>= 1.3)
securecompare securecompare
url_safe_base64 url_safe_base64
json-schema (2.6.2) json-schema (2.8.0)
addressable (~> 2.3.8) addressable (>= 2.4)
jwt (1.5.6) jwt (1.5.6)
kaminari (1.0.1) kaminari (1.0.1)
activesupport (>= 4.1.0) activesupport (>= 4.1.0)
...@@ -475,18 +469,20 @@ GEM ...@@ -475,18 +469,20 @@ GEM
mail (2.6.6) mail (2.6.6)
mime-types (>= 1.16, < 4) mime-types (>= 1.16, < 4)
mail_room (0.9.1) mail_room (0.9.1)
memoist (0.15.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.8.2)
mime-types (2.99.3) mime-types (3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
mimemagic (0.3.0) mimemagic (0.3.0)
mini_mime (0.1.4) mini_mime (0.1.4)
mini_portile2 (2.3.0) mini_portile2 (2.3.0)
minitest (5.7.0) minitest (5.7.0)
mmap2 (2.2.7) mmap2 (2.2.7)
mousetrap-rails (1.4.6) mousetrap-rails (1.4.6)
multi_json (1.12.1) multi_json (1.12.2)
multi_xml (0.6.0) multi_xml (0.6.0)
multipart-post (2.0.0) multipart-post (2.0.0)
mustermann (1.0.0) mustermann (1.0.0)
...@@ -635,6 +631,7 @@ GEM ...@@ -635,6 +631,7 @@ GEM
pry (~> 0.10) pry (~> 0.10)
pry-rails (0.3.5) pry-rails (0.3.5)
pry (>= 0.9.10) pry (>= 0.9.10)
public_suffix (3.0.0)
pyu-ruby-sasl (0.0.3.3) pyu-ruby-sasl (0.0.3.3)
rack (1.6.8) rack (1.6.8)
rack-accept (0.4.5) rack-accept (0.4.5)
...@@ -717,6 +714,10 @@ GEM ...@@ -717,6 +714,10 @@ GEM
redis-store (~> 1.2.0) redis-store (~> 1.2.0)
redis-store (1.2.0) redis-store (1.2.0)
redis (>= 2.2) redis (>= 2.2)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
uber (< 0.2.0)
request_store (1.3.1) request_store (1.3.1)
responders (2.3.0) responders (2.3.0)
railties (>= 4.2.0, < 5.1) railties (>= 4.2.0, < 5.1)
...@@ -724,7 +725,7 @@ GEM ...@@ -724,7 +725,7 @@ GEM
http-cookie (>= 1.0.2, < 2.0) http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 4.0) mime-types (>= 1.16, < 4.0)
netrc (~> 0.8) netrc (~> 0.8)
retriable (1.4.1) retriable (3.1.1)
rinku (2.0.0) rinku (2.0.0)
rotp (2.1.2) rotp (2.1.2)
rouge (2.2.1) rouge (2.2.1)
...@@ -903,6 +904,7 @@ GEM ...@@ -903,6 +904,7 @@ GEM
tzinfo (1.2.3) tzinfo (1.2.3)
thread_safe (~> 0.1) thread_safe (~> 0.1)
u2f (0.2.1) u2f (0.2.1)
uber (0.1.0)
uglifier (2.7.2) uglifier (2.7.2)
execjs (>= 0.3.0) execjs (>= 0.3.0)
json (>= 1.8.0) json (>= 1.8.0)
...@@ -963,7 +965,7 @@ DEPENDENCIES ...@@ -963,7 +965,7 @@ DEPENDENCIES
ace-rails-ap (~> 4.1.0) ace-rails-ap (~> 4.1.0)
activerecord_sane_schema_dumper (= 0.2) activerecord_sane_schema_dumper (= 0.2)
acts-as-taggable-on (~> 4.0) acts-as-taggable-on (~> 4.0)
addressable (~> 2.3.8) addressable (~> 2.5.2)
akismet (~> 2.0) akismet (~> 2.0)
allocations (~> 1.0) allocations (~> 1.0)
asana (~> 0.6.0) asana (~> 0.6.0)
...@@ -1025,7 +1027,7 @@ DEPENDENCIES ...@@ -1025,7 +1027,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0) gettext_i18n_rails_js (~> 1.2.0)
gitaly-proto (~> 0.37.0) gitaly-proto (~> 0.39.0)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2) gitlab-markup (~> 1.6.2)
...@@ -1033,7 +1035,7 @@ DEPENDENCIES ...@@ -1033,7 +1035,7 @@ DEPENDENCIES
gollum-lib (~> 4.2) gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.4) gollum-rugged_adapter (~> 0.4.4)
gon (~> 6.1.0) gon (~> 6.1.0)
google-api-client (~> 0.8.6) google-api-client (~> 0.13.6)
gpgme gpgme
grape (~> 1.0) grape (~> 1.0)
grape-entity (~> 0.6.0) grape-entity (~> 0.6.0)
...@@ -1051,7 +1053,7 @@ DEPENDENCIES ...@@ -1051,7 +1053,7 @@ DEPENDENCIES
jira-ruby (~> 1.4) jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2) jquery-atwho-rails (~> 1.3.2)
jquery-rails (~> 4.1.0) jquery-rails (~> 4.1.0)
json-schema (~> 2.6.2) json-schema (~> 2.8.0)
jwt (~> 1.5.6) jwt (~> 1.5.6)
kaminari (~> 1.0) kaminari (~> 1.0)
knapsack (~> 1.11.0) knapsack (~> 1.11.0)
......
...@@ -197,6 +197,11 @@ month. When we say 'the most recent monthly release', this can refer to either ...@@ -197,6 +197,11 @@ month. When we say 'the most recent monthly release', this can refer to either
the version currently running on GitLab.com, or the most recent version the version currently running on GitLab.com, or the most recent version
available in the package repositories. available in the package repositories.
A regression issue should be labeled with the appropriate [subject label](../CONTRIBUTING.md#subject-labels-wiki-container-registry-ldap-api-etc)
and [team label](../CONTRIBUTING.md#team-labels-ci-discussion-edge-platform-etc),
just like any other issue, to help GitLab team members focus on issues that are
relevant to [their area of responsibility](https://about.gitlab.com/handbook/engineering/workflow/#choosing-something-to-work-on).
## Release retrospective and kickoff ## Release retrospective and kickoff
- [Retrospective](https://about.gitlab.com/handbook/engineering/workflow/#retrospective) - [Retrospective](https://about.gitlab.com/handbook/engineering/workflow/#retrospective)
......
...@@ -298,7 +298,7 @@ class CopyAsGFM { ...@@ -298,7 +298,7 @@ class CopyAsGFM {
const documentFragment = getSelectedFragment(); const documentFragment = getSelectedFragment();
if (!documentFragment) return; if (!documentFragment) return;
const el = transformer(documentFragment.cloneNode(true)); const el = transformer(documentFragment.cloneNode(true), e.currentTarget);
if (!el) return; if (!el) return;
e.preventDefault(); e.preventDefault();
...@@ -338,55 +338,64 @@ class CopyAsGFM { ...@@ -338,55 +338,64 @@ class CopyAsGFM {
} }
static transformGFMSelection(documentFragment) { static transformGFMSelection(documentFragment) {
const gfmEls = documentFragment.querySelectorAll('.md, .wiki'); const gfmElements = documentFragment.querySelectorAll('.md, .wiki');
switch (gfmEls.length) { switch (gfmElements.length) {
case 0: { case 0: {
return documentFragment; return documentFragment;
} }
case 1: { case 1: {
return gfmEls[0]; return gfmElements[0];
} }
default: { default: {
const allGfmEl = document.createElement('div'); const allGfmElement = document.createElement('div');
for (let i = 0; i < gfmEls.length; i += 1) { for (let i = 0; i < gfmElements.length; i += 1) {
const lineEl = gfmEls[i]; const gfmElement = gfmElements[i];
allGfmEl.appendChild(lineEl); allGfmElement.appendChild(gfmElement);
allGfmEl.appendChild(document.createTextNode('\n\n')); allGfmElement.appendChild(document.createTextNode('\n\n'));
} }
return allGfmEl; return allGfmElement;
} }
} }
} }
static transformCodeSelection(documentFragment) { static transformCodeSelection(documentFragment, target) {
const lineEls = documentFragment.querySelectorAll('.line'); let lineSelector = '.line';
let codeEl; if (target) {
if (lineEls.length > 1) { const lineClass = ['left-side', 'right-side'].filter(name => target.classList.contains(name))[0];
codeEl = document.createElement('pre'); if (lineClass) {
codeEl.className = 'code highlight'; lineSelector = `.line_content.${lineClass} ${lineSelector}`;
}
}
const lineElements = documentFragment.querySelectorAll(lineSelector);
let codeElement;
if (lineElements.length > 1) {
codeElement = document.createElement('pre');
codeElement.className = 'code highlight';
const lang = lineEls[0].getAttribute('lang'); const lang = lineElements[0].getAttribute('lang');
if (lang) { if (lang) {
codeEl.setAttribute('lang', lang); codeElement.setAttribute('lang', lang);
} }
} else { } else {
codeEl = document.createElement('code'); codeElement = document.createElement('code');
} }
if (lineEls.length > 0) { if (lineElements.length > 0) {
for (let i = 0; i < lineEls.length; i += 1) { for (let i = 0; i < lineElements.length; i += 1) {
const lineEl = lineEls[i]; const lineElement = lineElements[i];
codeEl.appendChild(lineEl); codeElement.appendChild(lineElement);
codeEl.appendChild(document.createTextNode('\n')); codeElement.appendChild(document.createTextNode('\n'));
} }
} else { } else {
codeEl.appendChild(documentFragment); codeElement.appendChild(documentFragment);
} }
return codeEl; return codeElement;
} }
static nodeToGFM(node, respectWhitespaceParam = false) { static nodeToGFM(node, respectWhitespaceParam = false) {
......
<script> <script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
export default { export default {
props: { props: {
...@@ -8,6 +10,8 @@ ...@@ -8,6 +10,8 @@
}, },
components: { components: {
userAvatarImage, userAvatarImage,
limitWarning,
totalTime,
}, },
}; };
</script> </script>
......
<script> <script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
export default { export default {
props: { props: {
...@@ -8,6 +10,8 @@ ...@@ -8,6 +10,8 @@
}, },
components: { components: {
userAvatarImage, userAvatarImage,
limitWarning,
totalTime,
}, },
}; };
</script> </script>
......
<script> <script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import iconCommit from '../svg/icon_commit.svg'; import iconCommit from '../svg/icon_commit.svg';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
export default { export default {
props: { props: {
items: Array, items: Array,
stage: Object, stage: Object,
}, },
components: { components: {
userAvatarImage, userAvatarImage,
totalTime,
limitWarning,
}, },
computed: { computed: {
iconCommit() { iconCommit() {
return iconCommit; return iconCommit;
}, },
}, },
}; };
</script> </script>
<template> <template>
<div> <div>
......
<script> <script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
export default { export default {
props: { props: {
...@@ -8,6 +10,8 @@ ...@@ -8,6 +10,8 @@
}, },
components: { components: {
userAvatarImage, userAvatarImage,
totalTime,
limitWarning,
}, },
}; };
</script> </script>
......
<script> <script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import iconBranch from '../svg/icon_branch.svg'; import iconBranch from '../svg/icon_branch.svg';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
export default { export default {
props: { props: {
...@@ -9,6 +11,8 @@ ...@@ -9,6 +11,8 @@
}, },
components: { components: {
userAvatarImage, userAvatarImage,
totalTime,
limitWarning,
}, },
computed: { computed: {
iconBranch() { iconBranch() {
......
<script> <script>
import iconBuildStatus from '../svg/icon_build_status.svg'; import iconBuildStatus from '../svg/icon_build_status.svg';
import iconBranch from '../svg/icon_branch.svg'; import iconBranch from '../svg/icon_branch.svg';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
export default { export default {
props: { props: {
items: Array, items: Array,
stage: Object, stage: Object,
}, },
components: {
totalTime,
limitWarning,
},
computed: { computed: {
iconBuildStatus() { iconBuildStatus() {
return iconBuildStatus; return iconBuildStatus;
...@@ -15,7 +21,7 @@ export default { ...@@ -15,7 +21,7 @@ export default {
return iconBranch; return iconBranch;
}, },
}, },
}; };
</script> </script>
<template> <template>
<div> <div>
......
...@@ -3,14 +3,12 @@ ...@@ -3,14 +3,12 @@
import Vue from 'vue'; import Vue from 'vue';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import Translate from '../vue_shared/translate'; import Translate from '../vue_shared/translate';
import limitWarningComponent from './components/limit_warning_component.vue';
import stageCodeComponent from './components/stage_code_component.vue'; import stageCodeComponent from './components/stage_code_component.vue';
import stagePlanComponent from './components/stage_plan_component.vue'; import stagePlanComponent from './components/stage_plan_component.vue';
import stageComponent from './components/stage_component.vue'; import stageComponent from './components/stage_component.vue';
import stageReviewComponent from './components/stage_review_component.vue'; import stageReviewComponent from './components/stage_review_component.vue';
import stageStagingComponent from './components/stage_staging_component.vue'; import stageStagingComponent from './components/stage_staging_component.vue';
import stageTestComponent from './components/stage_test_component.vue'; import stageTestComponent from './components/stage_test_component.vue';
import totalTime from './components/total_time_component.vue';
import CycleAnalyticsService from './cycle_analytics_service'; import CycleAnalyticsService from './cycle_analytics_service';
import CycleAnalyticsStore from './cycle_analytics_store'; import CycleAnalyticsStore from './cycle_analytics_store';
...@@ -133,8 +131,4 @@ $(() => { ...@@ -133,8 +131,4 @@ $(() => {
}, },
}, },
}); });
// Register global components
Vue.component('limit-warning', limitWarningComponent);
Vue.component('total-time', totalTime);
}); });
...@@ -24,7 +24,8 @@ class Diff { ...@@ -24,7 +24,8 @@ class Diff {
if (!isBound) { if (!isBound) {
$(document) $(document)
.on('click', '.js-unfold', this.handleClickUnfold.bind(this)) .on('click', '.js-unfold', this.handleClickUnfold.bind(this))
.on('click', '.diff-line-num a', this.handleClickLineNum.bind(this)); .on('click', '.diff-line-num a', this.handleClickLineNum.bind(this))
.on('mousedown', 'td.line_content.parallel', this.handleParallelLineDown.bind(this));
isBound = true; isBound = true;
} }
...@@ -100,6 +101,18 @@ class Diff { ...@@ -100,6 +101,18 @@ class Diff {
this.highlightSelectedLine(); this.highlightSelectedLine();
} }
handleParallelLineDown(e) {
const line = $(e.currentTarget);
const table = line.closest('table');
table.removeClass('left-side-selected right-side-selected');
const lineClass = ['left-side', 'right-side'].filter(name => line.hasClass(name))[0];
if (lineClass) {
table.addClass(`${lineClass}-selected`);
}
}
diffViewType() { diffViewType() {
return $('.inline-parallel-buttons a.active').data('view-type'); return $('.inline-parallel-buttons a.active').data('view-type');
} }
......
...@@ -548,6 +548,7 @@ GitLabDropdown = (function() { ...@@ -548,6 +548,7 @@ GitLabDropdown = (function() {
GitLabDropdown.prototype.positionMenuAbove = function() { GitLabDropdown.prototype.positionMenuAbove = function() {
var $menu = this.dropdown.find('.dropdown-menu'); var $menu = this.dropdown.find('.dropdown-menu');
$menu.addClass('dropdown-open-top');
$menu.css('top', 'initial'); $menu.css('top', 'initial');
$menu.css('bottom', '100%'); $menu.css('bottom', '100%');
}; };
......
...@@ -67,10 +67,13 @@ const PARTICIPANTS_ROW_COUNT = 7; ...@@ -67,10 +67,13 @@ const PARTICIPANTS_ROW_COUNT = 7;
originalText = $(this).data("original-text"); originalText = $(this).data("original-text");
if (currentText === originalText) { if (currentText === originalText) {
$(this).text(lessText); $(this).text(lessText);
if (gl.lazyLoader) gl.lazyLoader.loadCheck();
} else { } else {
$(this).text(originalText); $(this).text(originalText);
} }
return $(".js-participants-hidden").toggle();
$(".js-participants-hidden").toggle();
}; };
return IssuableContext; return IssuableContext;
......
...@@ -55,7 +55,7 @@ window.dateFormat = dateFormat; ...@@ -55,7 +55,7 @@ window.dateFormat = dateFormat;
if (!timeagoInstance) { if (!timeagoInstance) {
const localeRemaining = function(number, index) { const localeRemaining = function(number, index) {
return [ return [
[s__('Timeago|less than a minute ago'), s__('Timeago|a while')], [s__('Timeago|less than a minute ago'), s__('Timeago|in a while')],
[s__('Timeago|less than a minute ago'), s__('Timeago|%s seconds remaining')], [s__('Timeago|less than a minute ago'), s__('Timeago|%s seconds remaining')],
[s__('Timeago|about a minute ago'), s__('Timeago|1 minute remaining')], [s__('Timeago|about a minute ago'), s__('Timeago|1 minute remaining')],
[s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')], [s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')],
...@@ -73,7 +73,7 @@ window.dateFormat = dateFormat; ...@@ -73,7 +73,7 @@ window.dateFormat = dateFormat;
}; };
locale = function(number, index) { locale = function(number, index) {
return [ return [
[s__('Timeago|less than a minute ago'), s__('Timeago|a while')], [s__('Timeago|less than a minute ago'), s__('Timeago|in a while')],
[s__('Timeago|less than a minute ago'), s__('Timeago|in %s seconds')], [s__('Timeago|less than a minute ago'), s__('Timeago|in %s seconds')],
[s__('Timeago|about a minute ago'), s__('Timeago|in 1 minute')], [s__('Timeago|about a minute ago'), s__('Timeago|in 1 minute')],
[s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')], [s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')],
......
import Jed from 'jed'; import Jed from 'jed';
import sprintf from './sprintf';
/** /**
This is required to require all the translation folders in the current directory This is required to require all the translation folders in the current directory
this saves us having to do this manually & keep up to date with new languages this saves us having to do this manually & keep up to date with new languages
...@@ -66,4 +68,5 @@ export { lang }; ...@@ -66,4 +68,5 @@ export { lang };
export { gettext as __ }; export { gettext as __ };
export { ngettext as n__ }; export { ngettext as n__ };
export { pgettext as s__ }; export { pgettext as s__ };
export { sprintf };
export default locale; export default locale;
import _ from 'underscore';
/**
Very limited implementation of sprintf supporting only named parameters.
@param input (translated) text with parameters (e.g. '%{num_users} users use us')
@param parameters object mapping parameter names to values (e.g. { num_users: 5 })
@param escapeParameters whether parameter values should be escaped (see http://underscorejs.org/#escape)
@returns {String} the text with parameters replaces (e.g. '5 users use us')
@see https://ruby-doc.org/core-2.3.3/Kernel.html#method-i-sprintf
@see https://gitlab.com/gitlab-org/gitlab-ce/issues/37992
**/
export default (input, parameters, escapeParameters = true) => {
let output = input;
if (parameters) {
Object.keys(parameters).forEach((parameterName) => {
const parameterValue = parameters[parameterName];
const escapedParameterValue = escapeParameters ? _.escape(parameterValue) : parameterValue;
output = output.replace(new RegExp(`%{${parameterName}}`, 'g'), escapedParameterValue);
});
}
return output;
};
...@@ -62,7 +62,7 @@ ...@@ -62,7 +62,7 @@
}, },
deleteHandler() { deleteHandler() {
// eslint-disable-next-line no-alert // eslint-disable-next-line no-alert
if (confirm('Are you sure you want to delete this list?')) { if (confirm('Are you sure you want to delete this comment?')) {
this.isDeleting = true; this.isDeleting = true;
this.deleteNote(this.note) this.deleteNote(this.note)
......
...@@ -19,7 +19,7 @@ export default class ProjectsService { ...@@ -19,7 +19,7 @@ export default class ProjectsService {
getSearchedProjects(searchQuery) { getSearchedProjects(searchQuery) {
return this.projectsPath.get({ return this.projectsPath.get({
simple: false, simple: true,
per_page: 20, per_page: 20,
membership: !!gon.current_user_id, membership: !!gon.current_user_id,
order_by: 'last_activity_at', order_by: 'last_activity_at',
......
...@@ -95,7 +95,7 @@ export default RepoFile; ...@@ -95,7 +95,7 @@ export default RepoFile;
</div> </div>
</td> </td>
<td class="hidden-xs"> <td class="hidden-xs text-right">
<span <span
class="commit-update" class="commit-update"
:title="tooltipTitle(file.lastCommitUpdate)"> :title="tooltipTitle(file.lastCommitUpdate)">
......
...@@ -75,7 +75,7 @@ export default { ...@@ -75,7 +75,7 @@ export default {
<tr> <tr>
<th class="name">Name</th> <th class="name">Name</th>
<th class="hidden-sm hidden-xs last-commit">Last Commit</th> <th class="hidden-sm hidden-xs last-commit">Last Commit</th>
<th class="hidden-xs last-update">Last Update</th> <th class="hidden-xs last-update text-right">Last Update</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
......
...@@ -29,7 +29,11 @@ import Cookies from 'js-cookie'; ...@@ -29,7 +29,11 @@ import Cookies from 'js-cookie';
$('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading); $('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading);
$('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded); $('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded);
$document.on('click', '.js-sidebar-toggle', function(e, triggered) { $document.on('click', '.js-sidebar-toggle', this.sidebarToggleClicked);
return $(document).off('click', '.js-issuable-todo').on('click', '.js-issuable-todo', this.toggleTodo);
};
Sidebar.prototype.sidebarToggleClicked = function (e, triggered) {
var $allGutterToggleIcons, $this, $thisIcon; var $allGutterToggleIcons, $this, $thisIcon;
e.preventDefault(); e.preventDefault();
$this = $(this); $this = $(this);
...@@ -47,10 +51,8 @@ import Cookies from 'js-cookie'; ...@@ -47,10 +51,8 @@ import Cookies from 'js-cookie';
if (gl.lazyLoader) gl.lazyLoader.loadCheck(); if (gl.lazyLoader) gl.lazyLoader.loadCheck();
} }
if (!triggered) { if (!triggered) {
return Cookies.set("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed')); Cookies.set("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'));
} }
});
return $(document).off('click', '.js-issuable-todo').on('click', '.js-issuable-todo', this.toggleTodo);
}; };
Sidebar.prototype.toggleTodo = function(e) { Sidebar.prototype.toggleTodo = function(e) {
......
...@@ -27,7 +27,7 @@ export default { ...@@ -27,7 +27,7 @@ export default {
<button <button
v-if="showDisabledButton" v-if="showDisabledButton"
type="button" type="button"
class="btn btn-success btn-sm" class="js-disabled-merge-button btn btn-success btn-sm"
disabled="true"> disabled="true">
Merge Merge
</button> </button>
......
...@@ -16,9 +16,9 @@ export default { ...@@ -16,9 +16,9 @@ export default {
<div class="media-body"> <div class="media-body">
<mr-widget-author-and-time <mr-widget-author-and-time
actionText="Closed by" actionText="Closed by"
:author="mr.closedBy" :author="mr.closedEvent.author"
:dateTitle="mr.updatedAt" :dateTitle="mr.closedEvent.updatedAt"
:dateReadable="mr.closedAt" :dateReadable="mr.closedEvent.formattedUpdatedAt"
/> />
<section class="mr-info-list"> <section class="mr-info-list">
<p> <p>
......
...@@ -10,8 +10,17 @@ export default { ...@@ -10,8 +10,17 @@ export default {
}, },
template: ` template: `
<div class="mr-widget-body media"> <div class="mr-widget-body media">
<status-icon status="failed" showDisabledButton /> <status-icon
status="failed"
showDisabledButton />
<div class="media-body space-children"> <div class="media-body space-children">
<span
v-if="mr.shouldBeRebased"
class="bold">
Fast-forward merge is not possible.
To merge this request, first rebase locally.
</span>
<template v-else>
<span class="bold"> <span class="bold">
There are merge conflicts<span v-if="!mr.canMerge">.</span> There are merge conflicts<span v-if="!mr.canMerge">.</span>
<span v-if="!mr.canMerge"> <span v-if="!mr.canMerge">
...@@ -21,16 +30,17 @@ export default { ...@@ -21,16 +30,17 @@ export default {
<a <a
v-if="mr.canMerge && mr.conflictResolutionPath" v-if="mr.canMerge && mr.conflictResolutionPath"
:href="mr.conflictResolutionPath" :href="mr.conflictResolutionPath"
class="btn btn-default btn-xs js-resolve-conflicts-button"> class="js-resolve-conflicts-button btn btn-default btn-xs">
Resolve conflicts Resolve conflicts
</a> </a>
<a <a
v-if="mr.canMerge" v-if="mr.canMerge"
class="btn btn-default btn-xs js-merge-locally-button" class="js-merge-locally-button btn btn-default btn-xs"
data-toggle="modal" data-toggle="modal"
href="#modal_merge_info"> href="#modal_merge_info">
Merge locally Merge locally
</a> </a>
</template>
</div> </div>
</div> </div>
`, `,
......
...@@ -69,9 +69,9 @@ export default { ...@@ -69,9 +69,9 @@ export default {
<div class="space-children"> <div class="space-children">
<mr-widget-author-and-time <mr-widget-author-and-time
actionText="Merged by" actionText="Merged by"
:author="mr.mergedBy" :author="mr.mergedEvent.author"
:dateTitle="mr.updatedAt" :date-title="mr.mergedEvent.updatedAt"
:dateReadable="mr.mergedAt" /> :date-readable="mr.mergedEvent.formattedUpdatedAt" />
<a <a
v-if="mr.canRevertInCurrentMR" v-if="mr.canRevertInCurrentMR"
v-tooltip v-tooltip
......
...@@ -284,10 +284,16 @@ export default { ...@@ -284,10 +284,16 @@ export default {
:mr="mr" :mr="mr"
:is-merge-button-disabled="isMergeButtonDisabled" /> :is-merge-button-disabled="isMergeButtonDisabled" />
<span
v-if="mr.ffOnlyEnabled"
class="js-fast-forward-message">
Fast-forward merge without a merge commit
</span>
<button <button
v-else
@click="toggleCommitMessageEditor" @click="toggleCommitMessageEditor"
:disabled="isMergeButtonDisabled" :disabled="isMergeButtonDisabled"
class="btn btn-default btn-xs" class="js-modify-commit-message-button btn btn-default btn-xs"
type="button"> type="button">
Modify commit message Modify commit message
</button> </button>
......
...@@ -37,10 +37,8 @@ export default class MergeRequestStore { ...@@ -37,10 +37,8 @@ export default class MergeRequestStore {
} }
this.updatedAt = data.updated_at; this.updatedAt = data.updated_at;
this.mergedAt = MergeRequestStore.getEventDate(data.merge_event); this.mergedEvent = MergeRequestStore.getEventObject(data.merge_event);
this.closedAt = MergeRequestStore.getEventDate(data.closed_event); this.closedEvent = MergeRequestStore.getEventObject(data.closed_event);
this.mergedBy = MergeRequestStore.getAuthorObject(data.merge_event);
this.closedBy = MergeRequestStore.getAuthorObject(data.closed_event);
this.setToMWPSBy = MergeRequestStore.getAuthorObject({ author: data.merge_user || {} }); this.setToMWPSBy = MergeRequestStore.getAuthorObject({ author: data.merge_user || {} });
this.mergeUserId = data.merge_user_id; this.mergeUserId = data.merge_user_id;
this.currentUserId = gon.current_user_id; this.currentUserId = gon.current_user_id;
...@@ -57,6 +55,8 @@ export default class MergeRequestStore { ...@@ -57,6 +55,8 @@ export default class MergeRequestStore {
this.onlyAllowMergeIfPipelineSucceeds = data.only_allow_merge_if_pipeline_succeeds || false; this.onlyAllowMergeIfPipelineSucceeds = data.only_allow_merge_if_pipeline_succeeds || false;
this.mergeWhenPipelineSucceeds = data.merge_when_pipeline_succeeds || false; this.mergeWhenPipelineSucceeds = data.merge_when_pipeline_succeeds || false;
this.mergePath = data.merge_path; this.mergePath = data.merge_path;
this.ffOnlyEnabled = data.ff_only_enabled;
this.shouldBeRebased = !!data.should_be_rebased;
this.statusPath = data.status_path; this.statusPath = data.status_path;
this.emailPatchesPath = data.email_patches_path; this.emailPatchesPath = data.email_patches_path;
this.plainDiffPath = data.plain_diff_path; this.plainDiffPath = data.plain_diff_path;
...@@ -118,6 +118,14 @@ export default class MergeRequestStore { ...@@ -118,6 +118,14 @@ export default class MergeRequestStore {
} }
} }
static getEventObject(event) {
return {
author: MergeRequestStore.getAuthorObject(event),
updatedAt: gl.utils.formatDate(MergeRequestStore.getEventUpdatedAtDate(event)),
formattedUpdatedAt: MergeRequestStore.getEventDate(event),
};
}
static getAuthorObject(event) { static getAuthorObject(event) {
if (!event) { if (!event) {
return {}; return {};
...@@ -131,6 +139,14 @@ export default class MergeRequestStore { ...@@ -131,6 +139,14 @@ export default class MergeRequestStore {
}; };
} }
static getEventUpdatedAtDate(event) {
if (!event) {
return '';
}
return event.updated_at;
}
static getEventDate(event) { static getEventDate(event) {
const timeagoInstance = new Timeago(); const timeagoInstance = new Timeago();
...@@ -138,7 +154,7 @@ export default class MergeRequestStore { ...@@ -138,7 +154,7 @@ export default class MergeRequestStore {
return ''; return '';
} }
return timeagoInstance.format(event.updated_at); return timeagoInstance.format(MergeRequestStore.getEventUpdatedAtDate(event));
} }
} }
...@@ -2,6 +2,7 @@ import { ...@@ -2,6 +2,7 @@ import {
__, __,
n__, n__,
s__, s__,
sprintf,
} from '../locale'; } from '../locale';
export default (Vue) => { export default (Vue) => {
...@@ -37,6 +38,7 @@ export default (Vue) => { ...@@ -37,6 +38,7 @@ export default (Vue) => {
@returns {String} Translated context based text @returns {String} Translated context based text
**/ **/
s__, s__,
sprintf,
}, },
}); });
}; };
...@@ -745,6 +745,10 @@ ...@@ -745,6 +745,10 @@
#{$selector}.dropdown-menu-nav { #{$selector}.dropdown-menu-nav {
margin-bottom: 24px; margin-bottom: 24px;
&.dropdown-open-top {
margin-bottom: $dropdown-vertical-offset;
}
li { li {
display: block; display: block;
padding: 0 1px; padding: 0 1px;
...@@ -873,6 +877,13 @@ ...@@ -873,6 +877,13 @@
min-width: 100%; min-width: 100%;
} }
} }
header.navbar-gitlab-new .header-content .dropdown {
.dropdown-menu {
left: 0;
min-width: 100%;
}
}
} }
@include new-style-dropdown('.breadcrumbs-list .dropdown '); @include new-style-dropdown('.breadcrumbs-list .dropdown ');
......
...@@ -229,6 +229,10 @@ ul.content-list { ...@@ -229,6 +229,10 @@ ul.content-list {
.label-default { .label-default {
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
} }
.avatar-cell {
align-self: flex-start;
}
} }
.panel > .content-list > li { .panel > .content-list > li {
......
...@@ -295,7 +295,7 @@ header.navbar-gitlab-new { ...@@ -295,7 +295,7 @@ header.navbar-gitlab-new {
.header-user .dropdown-menu-nav, .header-user .dropdown-menu-nav,
.header-new .dropdown-menu-nav { .header-new .dropdown-menu-nav {
margin-top: 4px; margin-top: $dropdown-vertical-offset;
} }
.breadcrumbs { .breadcrumbs {
......
...@@ -327,6 +327,7 @@ $regular_font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-San ...@@ -327,6 +327,7 @@ $regular_font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-San
* Dropdowns * Dropdowns
*/ */
$dropdown-width: 300px; $dropdown-width: 300px;
$dropdown-vertical-offset: 4px;
$dropdown-link-color: #555; $dropdown-link-color: #555;
$dropdown-link-hover-bg: $row-hover; $dropdown-link-hover-bg: $row-hover;
$dropdown-empty-row-bg: rgba(#000, .04); $dropdown-empty-row-bg: rgba(#000, .04);
......
...@@ -77,6 +77,18 @@ ...@@ -77,6 +77,18 @@
word-wrap: break-word; word-wrap: break-word;
} }
} }
&.left-side-selected {
td.line_content.parallel.right-side {
@include user-select(none);
}
}
&.right-side-selected {
td.line_content.parallel.left-side {
@include user-select(none);
}
}
} }
tr.line_holder.parallel { tr.line_holder.parallel {
......
...@@ -362,7 +362,7 @@ ...@@ -362,7 +362,7 @@
.dropdown-menu { .dropdown-menu {
top: initial; top: initial;
bottom: 40px; bottom: 100%;
width: 298px; width: 298px;
} }
......
...@@ -59,6 +59,7 @@ module AuthenticatesWithTwoFactor ...@@ -59,6 +59,7 @@ module AuthenticatesWithTwoFactor
sign_in(user) sign_in(user)
else else
user.increment_failed_attempts! user.increment_failed_attempts!
Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=OTP")
flash.now[:alert] = 'Invalid two-factor code.' flash.now[:alert] = 'Invalid two-factor code.'
prompt_for_two_factor(user) prompt_for_two_factor(user)
end end
...@@ -75,6 +76,7 @@ module AuthenticatesWithTwoFactor ...@@ -75,6 +76,7 @@ module AuthenticatesWithTwoFactor
sign_in(user) sign_in(user)
else else
user.increment_failed_attempts! user.increment_failed_attempts!
Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=U2F")
flash.now[:alert] = 'Authentication via U2F device failed.' flash.now[:alert] = 'Authentication via U2F device failed.'
prompt_for_two_factor(user) prompt_for_two_factor(user)
end end
......
...@@ -15,9 +15,9 @@ module NotesActions ...@@ -15,9 +15,9 @@ module NotesActions
notes = notes_finder.execute notes = notes_finder.execute
.inc_relations_for_view .inc_relations_for_view
.reject { |n| n.cross_reference_not_visible_for?(current_user) }
notes = prepare_notes_for_rendering(notes) notes = prepare_notes_for_rendering(notes)
notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
notes_json[:notes] = notes_json[:notes] =
if noteable.discussions_rendered_on_frontend? if noteable.discussions_rendered_on_frontend?
......
...@@ -14,6 +14,7 @@ class ConfirmationsController < Devise::ConfirmationsController ...@@ -14,6 +14,7 @@ class ConfirmationsController < Devise::ConfirmationsController
if signed_in?(resource_name) if signed_in?(resource_name)
after_sign_in(resource) after_sign_in(resource)
else else
Gitlab::AppLogger.info("Email Confirmed: username=#{resource.username} email=#{resource.email} ip=#{request.remote_ip}")
flash[:notice] += " Please sign in." flash[:notice] += " Please sign in."
new_session_path(resource_name) new_session_path(resource_name)
end end
......
...@@ -16,7 +16,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -16,7 +16,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :authorize_create_issue!, only: [:new, :create] before_action :authorize_create_issue!, only: [:new, :create]
# Allow modify issue # Allow modify issue
before_action :authorize_update_issue!, only: [:edit, :update, :move] before_action :authorize_update_issue!, only: [:update, :move]
# Allow create a new branch and empty WIP merge request from current issue # Allow create a new branch and empty WIP merge request from current issue
before_action :authorize_create_merge_request!, only: [:create_merge_request] before_action :authorize_create_merge_request!, only: [:create_merge_request]
...@@ -63,10 +63,6 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -63,10 +63,6 @@ class Projects::IssuesController < Projects::ApplicationController
respond_with(@issue) respond_with(@issue)
end end
def edit
respond_with(@issue)
end
def show def show
@noteable = @issue @noteable = @issue
@note = @project.notes.new(noteable: @issue) @note = @project.notes.new(noteable: @issue)
...@@ -126,10 +122,6 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -126,10 +122,6 @@ class Projects::IssuesController < Projects::ApplicationController
@issue = Issues::UpdateService.new(project, current_user, update_params).execute(issue) @issue = Issues::UpdateService.new(project, current_user, update_params).execute(issue)
respond_to do |format| respond_to do |format|
format.html do
recaptcha_check_with_fallback { render :edit }
end
format.json do format.json do
render_issue_json render_issue_json
end end
......
...@@ -18,16 +18,12 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -18,16 +18,12 @@ class Projects::WikisController < Projects::ApplicationController
response.headers['Content-Security-Policy'] = "default-src 'none'" response.headers['Content-Security-Policy'] = "default-src 'none'"
response.headers['X-Content-Security-Policy'] = "default-src 'none'" response.headers['X-Content-Security-Policy'] = "default-src 'none'"
if file.on_disk?
send_file file.on_disk_path, disposition: 'inline'
else
send_data( send_data(
file.raw_data, file.raw_data,
type: file.mime_type, type: file.mime_type,
disposition: 'inline', disposition: 'inline',
filename: file.name filename: file.name
) )
end
else else
return render('empty') unless can?(current_user, :create_wiki, @project) return render('empty') unless can?(current_user, :create_wiki, @project)
@page = WikiPage.new(@project_wiki) @page = WikiPage.new(@project_wiki)
......
...@@ -344,6 +344,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -344,6 +344,7 @@ class ProjectsController < Projects::ApplicationController
:tag_list, :tag_list,
:visibility_level, :visibility_level,
:template_name, :template_name,
:merge_method,
project_feature_attributes: %i[ project_feature_attributes: %i[
builds_access_level builds_access_level
......
...@@ -42,10 +42,12 @@ class RegistrationsController < Devise::RegistrationsController ...@@ -42,10 +42,12 @@ class RegistrationsController < Devise::RegistrationsController
end end
def after_sign_up_path_for(user) def after_sign_up_path_for(user)
Gitlab::AppLogger.info("User Created: username=#{user.username} email=#{user.email} ip=#{request.remote_ip} confirmed:#{user.confirmed?}")
user.confirmed? ? dashboard_projects_path : users_almost_there_path user.confirmed? ? dashboard_projects_path : users_almost_there_path
end end
def after_inactive_sign_up_path_for(_resource) def after_inactive_sign_up_path_for(resource)
Gitlab::AppLogger.info("User Created: username=#{resource.username} email=#{resource.email} ip=#{request.remote_ip} confirmed:false")
users_almost_there_path users_almost_there_path
end end
......
...@@ -13,6 +13,8 @@ class SessionsController < Devise::SessionsController ...@@ -13,6 +13,8 @@ class SessionsController < Devise::SessionsController
before_action :auto_sign_in_with_provider, only: [:new] before_action :auto_sign_in_with_provider, only: [:new]
before_action :load_recaptcha before_action :load_recaptcha
after_action :log_failed_login, only: [:new]
def new def new
set_minimum_password_length set_minimum_password_length
@ldap_servers = Gitlab::LDAP::Config.available_servers @ldap_servers = Gitlab::LDAP::Config.available_servers
...@@ -29,12 +31,13 @@ class SessionsController < Devise::SessionsController ...@@ -29,12 +31,13 @@ class SessionsController < Devise::SessionsController
end end
# hide the signed-in notification # hide the signed-in notification
flash[:notice] = nil flash[:notice] = nil
log_audit_event(current_user, with: authentication_method) log_audit_event(current_user, resource, with: authentication_method)
log_user_activity(current_user) log_user_activity(current_user)
end end
end end
def destroy def destroy
Gitlab::AppLogger.info("User Logout: username=#{current_user.username} ip=#{request.remote_ip}")
super super
# hide the signed_out notice # hide the signed_out notice
flash[:notice] = nil flash[:notice] = nil
...@@ -42,6 +45,16 @@ class SessionsController < Devise::SessionsController ...@@ -42,6 +45,16 @@ class SessionsController < Devise::SessionsController
private private
def log_failed_login
return unless failed_login?
Gitlab::AppLogger.info("Failed Login: username=#{user_params[:login]} ip=#{request.remote_ip}")
end
def failed_login?
(options = env["warden.options"]) && options[:action] == "unauthenticated"
end
def login_counter def login_counter
@login_counter ||= Gitlab::Metrics.counter(:user_session_logins_total, 'User sign in count') @login_counter ||= Gitlab::Metrics.counter(:user_session_logins_total, 'User sign in count')
end end
...@@ -123,7 +136,8 @@ class SessionsController < Devise::SessionsController ...@@ -123,7 +136,8 @@ class SessionsController < Devise::SessionsController
user.invalidate_otp_backup_code!(user_params[:otp_attempt]) user.invalidate_otp_backup_code!(user_params[:otp_attempt])
end end
def log_audit_event(user, options = {}) def log_audit_event(user, resource, options = {})
Gitlab::AppLogger.info("Successful Login: username=#{resource.username} ip=#{request.remote_ip} method=#{options[:with]} admin=#{resource.admin?}")
AuditEventService.new(user, user, options) AuditEventService.new(user, user, options)
.for_authentication.security_event .for_authentication.security_event
end end
......
...@@ -33,19 +33,21 @@ module DiffHelper ...@@ -33,19 +33,21 @@ module DiffHelper
end end
def diff_match_line(old_pos, new_pos, text: '', view: :inline, bottom: false) def diff_match_line(old_pos, new_pos, text: '', view: :inline, bottom: false)
content = content_tag :td, text, class: "line_content match #{view == :inline ? '' : view}" content_line_class = %w[line_content match]
cls = ['diff-line-num', 'unfold', 'js-unfold'] content_line_class << 'parallel' if view == :parallel
cls << 'js-unfold-bottom' if bottom
line_num_class = %w[diff-line-num unfold js-unfold]
line_num_class << 'js-unfold-bottom' if bottom
html = '' html = ''
if old_pos if old_pos
html << content_tag(:td, '...', class: cls + ['old_line'], data: { linenumber: old_pos }) html << content_tag(:td, '...', class: [*line_num_class, 'old_line'], data: { linenumber: old_pos })
html << content unless view == :inline html << content_tag(:td, text, class: [*content_line_class, 'left-side']) if view == :parallel
end end
if new_pos if new_pos
html << content_tag(:td, '...', class: cls + ['new_line'], data: { linenumber: new_pos }) html << content_tag(:td, '...', class: [*line_num_class, 'new_line'], data: { linenumber: new_pos })
html << content html << content_tag(:td, text, class: [*content_line_class, ('right-side' if view == :parallel)])
end end
html.html_safe html.html_safe
......
...@@ -21,11 +21,14 @@ module ProjectsHelper ...@@ -21,11 +21,14 @@ module ProjectsHelper
classes = %W[avatar avatar-inline s#{opts[:size]}] classes = %W[avatar avatar-inline s#{opts[:size]}]
classes << opts[:avatar_class] if opts[:avatar_class] classes << opts[:avatar_class] if opts[:avatar_class]
image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: classes, alt: '') avatar = avatar_icon(author, opts[:size])
src = opts[:lazy_load] ? nil : avatar
image_tag(src, width: opts[:size], class: classes, alt: '', "data-src" => avatar)
end end
def link_to_member(project, author, opts = {}, &block) def link_to_member(project, author, opts = {}, &block)
default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name", tooltip: false } default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name", tooltip: false, lazy_load: false }
opts = default_opts.merge(opts) opts = default_opts.merge(opts)
return "(deleted)" unless author return "(deleted)" unless author
......
...@@ -229,6 +229,10 @@ module Ci ...@@ -229,6 +229,10 @@ module Ci
variables variables
end end
def features
{ trace_sections: true }
end
def merge_request def merge_request
return @merge_request if defined?(@merge_request) return @merge_request if defined?(@merge_request)
......
...@@ -73,7 +73,7 @@ class GpgKey < ActiveRecord::Base ...@@ -73,7 +73,7 @@ class GpgKey < ActiveRecord::Base
end end
def verified_and_belongs_to_email?(email) def verified_and_belongs_to_email?(email)
emails_with_verified_status.fetch(email, false) emails_with_verified_status.fetch(email.downcase, false)
end end
def update_invalid_gpg_signatures def update_invalid_gpg_signatures
......
...@@ -524,6 +524,14 @@ class MergeRequest < ActiveRecord::Base ...@@ -524,6 +524,14 @@ class MergeRequest < ActiveRecord::Base
true true
end end
def ff_merge_possible?
project.repository.ancestor?(target_branch_sha, diff_head_sha)
end
def should_be_rebased?
project.ff_merge_must_be_possible? && !ff_merge_possible?
end
def can_cancel_merge_when_pipeline_succeeds?(current_user) def can_cancel_merge_when_pipeline_succeeds?(current_user)
can_be_merged_by?(current_user) || self.author == current_user can_be_merged_by?(current_user) || self.author == current_user
end end
......
...@@ -64,6 +64,7 @@ class Project < ActiveRecord::Base ...@@ -64,6 +64,7 @@ class Project < ActiveRecord::Base
# Storage specific hooks # Storage specific hooks
after_initialize :use_hashed_storage after_initialize :use_hashed_storage
after_create :check_repository_absence!
after_create :ensure_storage_path_exists after_create :ensure_storage_path_exists
after_save :ensure_storage_path_exists, if: :namespace_id_changed? after_save :ensure_storage_path_exists, if: :namespace_id_changed?
...@@ -72,6 +73,7 @@ class Project < ActiveRecord::Base ...@@ -72,6 +73,7 @@ class Project < ActiveRecord::Base
attr_accessor :old_path_with_namespace attr_accessor :old_path_with_namespace
attr_accessor :template_name attr_accessor :template_name
attr_writer :pipeline_status attr_writer :pipeline_status
attr_accessor :skip_disk_validation
alias_attribute :title, :name alias_attribute :title, :name
...@@ -227,7 +229,7 @@ class Project < ActiveRecord::Base ...@@ -227,7 +229,7 @@ class Project < ActiveRecord::Base
validates :import_url, importable_url: true, if: [:external_import?, :import_url_changed?] validates :import_url, importable_url: true, if: [:external_import?, :import_url_changed?]
validates :star_count, numericality: { greater_than_or_equal_to: 0 } validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create validate :check_limit, on: :create
validate :can_create_repository?, on: [:create, :update], if: ->(project) { !project.persisted? || project.renamed? } validate :check_repository_path_availability, on: :update, if: ->(project) { project.renamed? }
validate :avatar_type, validate :avatar_type,
if: ->(project) { project.avatar.present? && project.avatar_changed? } if: ->(project) { project.avatar.present? && project.avatar_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i } validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
...@@ -1018,12 +1020,15 @@ class Project < ActiveRecord::Base ...@@ -1018,12 +1020,15 @@ class Project < ActiveRecord::Base
end end
# Check if repository already exists on disk # Check if repository already exists on disk
def can_create_repository? def check_repository_path_availability
return true if skip_disk_validation
return false unless repository_storage_path return false unless repository_storage_path
expires_full_path_cache # we need to clear cache to validate renames correctly expires_full_path_cache # we need to clear cache to validate renames correctly
if gitlab_shell.exists?(repository_storage_path, "#{disk_path}.git") # Check if repository with same path already exists on disk we can
# skip this for the hashed storage because the path does not change
if legacy_storage? && repository_with_same_path_already_exists?
errors.add(:base, 'There is already a repository with that name on disk') errors.add(:base, 'There is already a repository with that name on disk')
return false return false
end end
...@@ -1035,7 +1040,7 @@ class Project < ActiveRecord::Base ...@@ -1035,7 +1040,7 @@ class Project < ActiveRecord::Base
# Forked import is handled asynchronously # Forked import is handled asynchronously
return if forked? && !force return if forked? && !force
if gitlab_shell.add_repository(repository_storage_path, disk_path) if gitlab_shell.add_repository(repository_storage, disk_path)
repository.after_create repository.after_create
true true
else else
...@@ -1564,6 +1569,34 @@ class Project < ActiveRecord::Base ...@@ -1564,6 +1569,34 @@ class Project < ActiveRecord::Base
persisted? && path_changed? persisted? && path_changed?
end end
def merge_method
if self.merge_requests_ff_only_enabled
:ff
elsif self.merge_requests_rebase_enabled
:rebase_merge
else
:merge
end
end
def merge_method=(method)
case method.to_s
when "ff"
self.merge_requests_ff_only_enabled = true
self.merge_requests_rebase_enabled = true
when "rebase_merge"
self.merge_requests_ff_only_enabled = false
self.merge_requests_rebase_enabled = true
when "merge"
self.merge_requests_ff_only_enabled = false
self.merge_requests_rebase_enabled = false
end
end
def ff_merge_must_be_possible?
self.merge_requests_ff_only_enabled || self.merge_requests_rebase_enabled
end
def migrate_to_hashed_storage! def migrate_to_hashed_storage!
return if hashed_storage? return if hashed_storage?
...@@ -1611,6 +1644,19 @@ class Project < ActiveRecord::Base ...@@ -1611,6 +1644,19 @@ class Project < ActiveRecord::Base
Gitlab::ReferenceCounter.new(gl_repository(is_wiki: true)).value Gitlab::ReferenceCounter.new(gl_repository(is_wiki: true)).value
end end
def check_repository_absence!
return if skip_disk_validation
if repository_storage_path.blank? || repository_with_same_path_already_exists?
errors.add(:base, 'There is already a repository with that name on disk')
throw :abort
end
end
def repository_with_same_path_already_exists?
gitlab_shell.exists?(repository_storage_path, "#{disk_path}.git")
end
# set last_activity_at to the same as created_at # set last_activity_at to the same as created_at
def set_last_activity_at def set_last_activity_at
update_column(:last_activity_at, self.created_at) update_column(:last_activity_at, self.created_at)
......
...@@ -54,12 +54,15 @@ class ProjectWiki ...@@ -54,12 +54,15 @@ class ProjectWiki
[Gitlab.config.gitlab.relative_url_root, '/', @project.full_path, '/wikis'].join('') [Gitlab.config.gitlab.relative_url_root, '/', @project.full_path, '/wikis'].join('')
end end
# Returns the Gollum::Wiki object. # Returns the Gitlab::Git::Wiki object.
def wiki def wiki
@wiki ||= begin @wiki ||= begin
Gollum::Wiki.new(path_to_repo) gl_repository = Gitlab::GlRepository.gl_repository(project, true)
rescue Rugged::OSError raw_repository = Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', gl_repository)
create_repo!
create_repo!(raw_repository) unless raw_repository.exists?
Gitlab::Git::Wiki.new(raw_repository)
end end
end end
...@@ -86,20 +89,14 @@ class ProjectWiki ...@@ -86,20 +89,14 @@ class ProjectWiki
# Returns an initialized WikiPage instance or nil # Returns an initialized WikiPage instance or nil
def find_page(title, version = nil) def find_page(title, version = nil)
page_title, page_dir = page_title_and_dir(title) page_title, page_dir = page_title_and_dir(title)
if page = wiki.page(page_title, version, page_dir)
if page = wiki.page(title: page_title, version: version, dir: page_dir)
WikiPage.new(self, page, true) WikiPage.new(self, page, true)
else
nil
end end
end end
def find_file(name, version = nil, try_on_disk = true) def find_file(name, version = nil)
version = wiki.ref if version.nil? # Gollum::Wiki#file ? wiki.file(name, version)
if wiki_file = wiki.file(name, version, try_on_disk)
wiki_file
else
nil
end
end end
def create_page(title, content, format = :markdown, message = nil) def create_page(title, content, format = :markdown, message = nil)
...@@ -108,7 +105,7 @@ class ProjectWiki ...@@ -108,7 +105,7 @@ class ProjectWiki
wiki.write_page(title, format.to_sym, content, commit) wiki.write_page(title, format.to_sym, content, commit)
update_project_activity update_project_activity
rescue Gollum::DuplicatePageError => e rescue Gitlab::Git::Wiki::DuplicatePageError => e
@error_message = "Duplicate page: #{e.message}" @error_message = "Duplicate page: #{e.message}"
return false return false
end end
...@@ -116,13 +113,13 @@ class ProjectWiki ...@@ -116,13 +113,13 @@ class ProjectWiki
def update_page(page, content:, title: nil, format: :markdown, message: nil) def update_page(page, content:, title: nil, format: :markdown, message: nil)
commit = commit_details(:updated, message, page.title) commit = commit_details(:updated, message, page.title)
wiki.update_page(page, title || page.name, format.to_sym, content, commit) wiki.update_page(page.path, title || page.name, format.to_sym, content, commit)
update_project_activity update_project_activity
end end
def delete_page(page, message = nil) def delete_page(page, message = nil)
wiki.delete_page(page, commit_details(:deleted, message, page.title)) wiki.delete_page(page.path, commit_details(:deleted, message, page.title))
update_project_activity update_project_activity
end end
...@@ -145,20 +142,8 @@ class ProjectWiki ...@@ -145,20 +142,8 @@ class ProjectWiki
wiki.class.default_ref wiki.class.default_ref
end end
def create_repo!
if init_repo(disk_path)
wiki = Gollum::Wiki.new(path_to_repo)
else
raise CouldNotCreateWikiError
end
repository.after_create
wiki
end
def ensure_repository def ensure_repository
create_repo! unless repository_exists? raise CouldNotCreateWikiError unless wiki.repository_exists?
end end
def hook_attrs def hook_attrs
...@@ -173,24 +158,24 @@ class ProjectWiki ...@@ -173,24 +158,24 @@ class ProjectWiki
private private
def init_repo(disk_path) def create_repo!(raw_repository)
gitlab_shell.add_repository(project.repository_storage_path, disk_path) gitlab_shell.add_repository(project.repository_storage, disk_path)
raise CouldNotCreateWikiError unless raw_repository.exists?
repository.after_create
end end
def commit_details(action, message = nil, title = nil) def commit_details(action, message = nil, title = nil)
commit_message = message || default_message(action, title) commit_message = message || default_message(action, title)
{ email: @user.email, name: @user.name, message: commit_message } Gitlab::Git::Wiki::CommitDetails.new(@user.name, @user.email, commit_message)
end end
def default_message(action, title) def default_message(action, title)
"#{@user.username} #{action} page: #{title}" "#{@user.username} #{action} page: #{title}"
end end
def path_to_repo
@path_to_repo ||= File.join(project.repository_storage_path, "#{disk_path}.git")
end
def update_project_activity def update_project_activity
@project.touch(:last_activity_at, :last_repository_updated_at) @project.touch(:last_activity_at, :last_repository_updated_at)
end end
......
...@@ -34,7 +34,10 @@ class Repository ...@@ -34,7 +34,10 @@ class Repository
CACHED_METHODS = %i(size commit_count rendered_readme contribution_guide CACHED_METHODS = %i(size commit_count rendered_readme contribution_guide
changelog license_blob license_key gitignore koding_yml changelog license_blob license_key gitignore koding_yml
gitlab_ci_yml branch_names tag_names branch_count gitlab_ci_yml branch_names tag_names branch_count
tag_count avatar exists? empty? root_ref).freeze tag_count avatar exists? empty? root_ref has_visible_content?).freeze
# Methods that use cache_method but only memoize the value
MEMOIZED_CACHED_METHODS = %i(license empty_repo?).freeze
# Certain method caches should be refreshed when certain types of files are # Certain method caches should be refreshed when certain types of files are
# changed. This Hash maps file types (as returned by Gitlab::FileDetector) to # changed. This Hash maps file types (as returned by Gitlab::FileDetector) to
...@@ -269,7 +272,7 @@ class Repository ...@@ -269,7 +272,7 @@ class Repository
end end
def expire_branches_cache def expire_branches_cache
expire_method_caches(%i(branch_names branch_count)) expire_method_caches(%i(branch_names branch_count has_visible_content?))
@local_branches = nil @local_branches = nil
@branch_exists_memo = nil @branch_exists_memo = nil
end end
...@@ -340,7 +343,7 @@ class Repository ...@@ -340,7 +343,7 @@ class Repository
def expire_emptiness_caches def expire_emptiness_caches
return unless empty? return unless empty?
expire_method_caches(%i(empty?)) expire_method_caches(%i(empty? has_visible_content?))
end end
def lookup_cache def lookup_cache
...@@ -847,6 +850,25 @@ class Repository ...@@ -847,6 +850,25 @@ class Repository
end end
end end
def ff_merge(user, source, target_branch, merge_request: nil)
our_commit = rugged.branches[target_branch].target
their_commit =
if source.is_a?(Gitlab::Git::Commit)
source.raw_commit
else
rugged.lookup(source)
end
raise 'Invalid merge target' if our_commit.nil?
raise 'Invalid merge source' if their_commit.nil?
with_branch(user, target_branch) do |start_commit|
merge_request&.update(in_progress_merge_commit_sha: their_commit.oid)
their_commit.oid
end
end
def revert( def revert(
user, commit, branch_name, message, user, commit, branch_name, message,
start_branch_name: nil, start_project: project) start_branch_name: nil, start_project: project)
......
...@@ -692,8 +692,12 @@ class User < ActiveRecord::Base ...@@ -692,8 +692,12 @@ class User < ActiveRecord::Base
end end
def ldap_user? def ldap_user?
if identities.loaded?
identities.find { |identity| identity.provider.start_with?('ldap') && !identity.extern_uid.nil? }
else
identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"]) identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"])
end end
end
def ldap_identity def ldap_identity
@ldap_identity ||= identities.find_by(["provider LIKE ?", "ldap%"]) @ldap_identity ||= identities.find_by(["provider LIKE ?", "ldap%"])
...@@ -1063,6 +1067,12 @@ class User < ActiveRecord::Base ...@@ -1063,6 +1067,12 @@ class User < ActiveRecord::Base
user_synced_attributes_metadata&.read_only?(attribute) user_synced_attributes_metadata&.read_only?(attribute)
end end
# override, from Devise
def lock_access!
Gitlab::AppLogger.info("Account Locked: username=#{username}")
super
end
protected protected
# override, from Devise::Validatable # override, from Devise::Validatable
......
...@@ -50,7 +50,7 @@ class WikiPage ...@@ -50,7 +50,7 @@ class WikiPage
# The Gitlab ProjectWiki instance. # The Gitlab ProjectWiki instance.
attr_reader :wiki attr_reader :wiki
# The raw Gollum::Page instance. # The raw Gitlab::Git::WikiPage instance.
attr_reader :page attr_reader :page
# The attributes Hash used for storing and validating # The attributes Hash used for storing and validating
...@@ -75,7 +75,7 @@ class WikiPage ...@@ -75,7 +75,7 @@ class WikiPage
if @attributes[:slug].present? if @attributes[:slug].present?
@attributes[:slug] @attributes[:slug]
else else
wiki.wiki.preview_page(title, '', format).url_path wiki.wiki.preview_slug(title, format)
end end
end end
...@@ -131,7 +131,7 @@ class WikiPage ...@@ -131,7 +131,7 @@ class WikiPage
def versions def versions
return [] unless persisted? return [] unless persisted?
@page.versions wiki.wiki.page_versions(@page.path)
end end
def commit def commit
...@@ -264,8 +264,8 @@ class WikiPage ...@@ -264,8 +264,8 @@ class WikiPage
end end
page_title, page_dir = wiki.page_title_and_dir(page_details) page_title, page_dir = wiki.page_title_and_dir(page_details)
gollum_wiki = wiki.wiki gitlab_git_wiki = wiki.wiki
@page = gollum_wiki.paged(page_title, page_dir) @page = gitlab_git_wiki.page(title: page_title, dir: page_dir)
set_attributes set_attributes
@persisted = errors.blank? @persisted = errors.blank?
......
...@@ -13,6 +13,11 @@ class MergeRequestEntity < IssuableEntity ...@@ -13,6 +13,11 @@ class MergeRequestEntity < IssuableEntity
expose :target_branch expose :target_branch
expose :target_project_id expose :target_project_id
expose :should_be_rebased?, as: :should_be_rebased
expose :ff_only_enabled do |merge_request|
merge_request.project.merge_requests_ff_only_enabled
end
# Events # Events
expose :merge_event, using: EventEntity expose :merge_event, using: EventEntity
expose :closed_event, using: EventEntity expose :closed_event, using: EventEntity
......
module MergeRequests
# MergeService class
#
# Do git fast-forward merge and in case of success
# mark merge request as merged and execute all hooks and notifications
# Executed when you do fast-forward merge via GitLab UI
#
class FfMergeService < MergeRequests::MergeService
private
def commit
repository.ff_merge(current_user,
source,
merge_request.target_branch,
merge_request: merge_request)
rescue Gitlab::Git::HooksService::PreReceiveError => e
raise MergeError, e.message
rescue StandardError => e
raise MergeError, "Something went wrong during merge: #{e.message}"
ensure
merge_request.update(in_progress_merge_commit_sha: nil)
end
end
end
...@@ -11,6 +11,11 @@ module MergeRequests ...@@ -11,6 +11,11 @@ module MergeRequests
attr_reader :merge_request, :source attr_reader :merge_request, :source
def execute(merge_request) def execute(merge_request)
if project.merge_requests_ff_only_enabled && !self.is_a?(FfMergeService)
FfMergeService.new(project, current_user, params).execute(merge_request)
return
end
@merge_request = merge_request @merge_request = merge_request
unless @merge_request.mergeable? unless @merge_request.mergeable?
......
- form = local_assigns.fetch(:form)
- project = local_assigns.fetch(:project)
.radio
= label_tag :project_merge_method_ff do
= form.radio_button :merge_method, :ff, class: "js-merge-method-radio"
%strong Fast-forward merge
%br
%span.descr
No merge commits are created and all merges are fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded.
%br
%span.descr
When fast-forward merge is not possible, the user must first rebase locally.
- form = local_assigns.fetch(:form)
.radio
= label_tag :project_merge_method_rebase_merge do
= form.radio_button :merge_method, :rebase_merge, class: "js-merge-method-radio"
%strong Merge commit with semi-linear history
%br
%span.descr
A merge commit is created for every merge, but merging is only allowed if fast-forward merge is possible.
This way you could make sure that if this merge request would build, after merging to target branch it would also build.
%br
%span.descr
When fast-forward merge is not possible, the user must first rebase locally.
- form = local_assigns.fetch(:form) - form = local_assigns.fetch(:form)
.form-group
= label_tag :merge_method_merge, class: 'label-light' do
Merge method
.radio
= label_tag :project_merge_method_merge do
= form.radio_button :merge_method, :merge, class: "js-merge-method-radio"
%strong Merge commit
%br
%span.descr
A merge commit is created for every merge, and merging is allowed as long as there are no conflicts.
= render 'merge_request_rebase_settings', form: form
= render 'merge_request_fast_forward_settings', project: @project, form: form
= render 'projects/merge_request_merge_settings', form: form = render 'projects/merge_request_merge_settings', form: form
...@@ -5,25 +5,24 @@ ...@@ -5,25 +5,24 @@
= diff_match_line @form.since, @form.since, text: @match_line, view: diff_view = diff_match_line @form.since, @form.since, text: @match_line, view: diff_view
- @lines.each_with_index do |line, index| - @lines.each_with_index do |line, index|
- line_new = index + @form.since - line_number_new = index + @form.since
- line_old = line_new - @form.offset - line_number_old = line_number_new - @form.offset
- line_content = capture do - line[0, 0] = ' ' * @form.indent
%td.line_content.noteable_line{ class: line_class }==#{' ' * @form.indent}#{line} %tr.line_holder.diff-expanded{ id: line_number_old, class: line_class }
%tr.line_holder.diff-expanded{ id: line_old, class: line_class }
- case diff_view - case diff_view
- when :inline - when :inline
%td.old_line.diff-line-num{ data: { linenumber: line_old } } %td.old_line.diff-line-num{ data: { linenumber: line_number_old } }
%a{ href: "#", data: { linenumber: line_old }, disabled: true } %a{ href: "#", data: { linenumber: line_number_old }, disabled: true }
%td.new_line.diff-line-num{ data: { linenumber: line_new } } %td.new_line.diff-line-num{ data: { linenumber: line_number_new } }
%a{ href: "#", data: { linenumber: line_new }, disabled: true } %a{ href: "#", data: { linenumber: line_number_new }, disabled: true }
= line_content %td.line_content.noteable_line{ class: line_class }= line
- when :parallel - when :parallel
%td.old_line.diff-line-num{ data: { linenumber: line_old } } %td.old_line.diff-line-num{ data: { linenumber: line_number_old } }
%a{ href: "##{line_old}", data: { linenumber: line_old }, disabled: true } %a{ href: "##{line_number_old}", data: { linenumber: line_number_old }, disabled: true }
= line_content %td.line_content.noteable_line.left-side{ class: line_class }= line
%td.new_line.diff-line-num{ data: { linenumber: line_new } } %td.new_line.diff-line-num{ data: { linenumber: line_number_new } }
%a{ href: "##{line_new}", data: { linenumber: line_new }, disabled: true } %a{ href: "##{line_number_new}", data: { linenumber: line_number_new }, disabled: true }
= line_content %td.line_content.noteable_line.right-side{ class: line_class }= line
- if @form.unfold? && @form.bottom? && @form.to < @blob.lines.size - if @form.unfold? && @form.bottom? && @form.to < @blob.lines.size
%tr.line_holder{ id: @form.to, class: line_class } %tr.line_holder{ id: @form.to, class: line_class }
......
...@@ -14,20 +14,20 @@ ...@@ -14,20 +14,20 @@
= diff_match_line left.old_pos, nil, text: left.text, view: :parallel = diff_match_line left.old_pos, nil, text: left.text, view: :parallel
- when 'old-nonewline', 'new-nonewline' - when 'old-nonewline', 'new-nonewline'
%td.old_line.diff-line-num %td.old_line.diff-line-num
%td.line_content.match= left.text %td.line_content.match.left-side= left.text
- else - else
- left_line_code = diff_file.line_code(left) - left_line_code = diff_file.line_code(left)
- left_position = diff_file.position(left) - left_position = diff_file.position(left)
%td.old_line.diff-line-num.js-avatar-container{ id: left_line_code, class: left.type, data: { linenumber: left.old_pos } } %td.old_line.diff-line-num.js-avatar-container{ class: left.type, data: { linenumber: left.old_pos } }
= add_diff_note_button(left_line_code, left_position, 'old') = add_diff_note_button(left_line_code, left_position, 'old')
%a{ href: "##{left_line_code}", data: { linenumber: left.old_pos } } %a{ href: "##{left_line_code}", data: { linenumber: left.old_pos } }
- discussion_left = discussions_left.try(:first) - discussion_left = discussions_left.try(:first)
- if discussion_left && discussion_left.resolvable? - if discussion_left && discussion_left.resolvable?
%diff-note-avatars{ "discussion-id" => discussion_left.id } %diff-note-avatars{ "discussion-id" => discussion_left.id }
%td.line_content.parallel.noteable_line{ class: left.type }= diff_line_content(left.text) %td.line_content.parallel.noteable_line.left-side{ id: left_line_code, class: left.type }= diff_line_content(left.text)
- else - else
%td.old_line.diff-line-num.empty-cell %td.old_line.diff-line-num.empty-cell
%td.line_content.parallel %td.line_content.parallel.left-side
- if right - if right
- case right.type - case right.type
...@@ -35,20 +35,20 @@ ...@@ -35,20 +35,20 @@
= diff_match_line nil, right.new_pos, text: left.text, view: :parallel = diff_match_line nil, right.new_pos, text: left.text, view: :parallel
- when 'old-nonewline', 'new-nonewline' - when 'old-nonewline', 'new-nonewline'
%td.new_line.diff-line-num %td.new_line.diff-line-num
%td.line_content.match= right.text %td.line_content.match.right-side= right.text
- else - else
- right_line_code = diff_file.line_code(right) - right_line_code = diff_file.line_code(right)
- right_position = diff_file.position(right) - right_position = diff_file.position(right)
%td.new_line.diff-line-num.js-avatar-container{ id: right_line_code, class: right.type, data: { linenumber: right.new_pos } } %td.new_line.diff-line-num.js-avatar-container{ class: right.type, data: { linenumber: right.new_pos } }
= add_diff_note_button(right_line_code, right_position, 'new') = add_diff_note_button(right_line_code, right_position, 'new')
%a{ href: "##{right_line_code}", data: { linenumber: right.new_pos } } %a{ href: "##{right_line_code}", data: { linenumber: right.new_pos } }
- discussion_right = discussions_right.try(:first) - discussion_right = discussions_right.try(:first)
- if discussion_right && discussion_right.resolvable? - if discussion_right && discussion_right.resolvable?
%diff-note-avatars{ "discussion-id" => discussion_right.id } %diff-note-avatars{ "discussion-id" => discussion_right.id }
%td.line_content.parallel.noteable_line{ class: right.type }= diff_line_content(right.text) %td.line_content.parallel.noteable_line.right-side{ id: right_line_code, class: right.type }= diff_line_content(right.text)
- else - else
%td.old_line.diff-line-num.empty-cell %td.old_line.diff-line-num.empty-cell
%td.line_content.parallel %td.line_content.parallel.right-side
- if discussions_left || discussions_right - if discussions_left || discussions_right
= render "discussions/parallel_diff_discussion", discussions_left: discussions_left, discussions_right: discussions_right = render "discussions/parallel_diff_discussion", discussions_left: discussions_left, discussions_right: discussions_right
......
- page_title "Edit", "#{@issue.title} (#{@issue.to_reference})", "Issues"
%h3.page-title
Edit Issue ##{@issue.iid}
%hr
= render "form"
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
= webpack_bundle_tag 'repo' = webpack_bundle_tag 'repo'
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } %div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
- if show_auto_devops_callout?(@project) - if show_auto_devops_callout?(@project) && !show_new_repo?
= render 'shared/auto_devops_callout' = render 'shared/auto_devops_callout'
= render 'projects/last_push' = render 'projects/last_push'
= render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id) = render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id)
...@@ -29,13 +29,13 @@ ...@@ -29,13 +29,13 @@
commit.id, index == 0) do commit.id, index == 0) do
= truncate_sha(commit.id) = truncate_sha(commit.id)
%td %td
= commit.author.name = commit.author_name
%td %td
= commit.message = commit.message
%td %td
#{time_ago_with_tooltip(version.authored_date)} #{time_ago_with_tooltip(version.authored_date)}
%td %td
%strong %strong
= @page.page.wiki.page(@page.page.name, commit.id).try(:format) = version.format
= render 'sidebar' = render 'sidebar'
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
.nav-text .nav-text
%h2.wiki-page-title= @page.title.capitalize %h2.wiki-page-title= @page.title.capitalize
%span.wiki-last-edit-by %span.wiki-last-edit-by
= (_("Last edited by %{name}") % { name: "<strong>#{@page.commit.author.name}</strong>" }).html_safe = (_("Last edited by %{name}") % { name: "<strong>#{@page.commit.author_name}</strong>" }).html_safe
#{time_ago_with_tooltip(@page.commit.authored_date)} #{time_ago_with_tooltip(@page.commit.authored_date)}
.nav-controls .nav-controls
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
.hide-collapsed.participants-list .hide-collapsed.participants-list
- participants.each do |participant| - participants.each do |participant|
.participants-author.js-participants-author .participants-author.js-participants-author
= link_to_member(@project, participant, name: false, size: 24) = link_to_member(@project, participant, name: false, size: 24, lazy_load: true)
- if participants_extra > 0 - if participants_extra > 0
.hide-collapsed.participants-more .hide-collapsed.participants-more
%a.js-participants-more{ href: "#", data: { original_text: "+ #{participants_size - 7} more", less_text: "- show less" } } %a.js-participants-more{ href: "#", data: { original_text: "+ #{participants_size - 7} more", less_text: "- show less" } }
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
= button_tag type: 'button', class: 'btn btn-nr dropdown-toggle comment-btn js-note-new-discussion js-disable-on-submit', data: { 'dropdown-trigger' => '#resolvable-comment-menu' }, 'aria-label' => 'Open comment type dropdown' do = button_tag type: 'button', class: 'btn btn-nr dropdown-toggle comment-btn js-note-new-discussion js-disable-on-submit', data: { 'dropdown-trigger' => '#resolvable-comment-menu' }, 'aria-label' => 'Open comment type dropdown' do
= icon('caret-down', class: 'toggle-icon') = icon('caret-down', class: 'toggle-icon')
%ul#resolvable-comment-menu.dropdown-menu{ data: { dropdown: true } } %ul#resolvable-comment-menu.dropdown-menu.dropdown-open-top{ data: { dropdown: true } }
%li#comment.droplab-item-selected{ data: { value: '', 'submit-text' => 'Comment', 'close-text' => "Comment & close #{noteable_name}", 'reopen-text' => "Comment & reopen #{noteable_name}" } } %li#comment.droplab-item-selected{ data: { value: '', 'submit-text' => 'Comment', 'close-text' => "Comment & close #{noteable_name}", 'reopen-text' => "Comment & reopen #{noteable_name}" } }
%button.btn.btn-transparent %button.btn.btn-transparent
= icon('check', class: 'icon') = icon('check', class: 'icon')
......
---
title: Link SAML users to LDAP by email.
merge_request: 14216
author:
type: changed
---
title: Load sidebar participants avatars only when visible
merge_request: 14270
author:
type: other
---
title: Remove the ability to visit the issue edit form directly
merge_request: 14523
author:
type: removed
---
title: Strip gitlab-runner section markers in build trace HTML view
merge_request: 14393
author:
type: added
---
title: Use `simple=true` for projects API in Projects dropdown for better search performance
merge_request:
author:
type: other
---
title: Fix bottom spacing for dropdowns that open upwards
merge_request: 14535
author:
type: fixed
---
title: Does not check if an invariant hashed storage path exists on disk when renaming
projects.
merge_request: 14428
author:
type: fixed
---
title: Fix navigation dropdown close animation on mobile screens
merge_request: 14649
author:
type: fixed
---
title: Ensure no exception is raised when Raven tries to get the current user in API
context
merge_request: 14580
author:
type: fixed
--- ---
title: Fix 500 error on merged merge requests when GitLab is restored from a backup title: Fix comment deletion confirmation dialog typo
merge_request: merge_request:
author: author:
type: fixed type: fixed
---
title: Whitelist authorized_keys.lock in the gitlab:check rake task
merge_request: 14624
author:
type: fixed
---
title: Change index on ci_builds to optimize Jobs Controller
merge_request:
author:
type: other
---
title: Add (partial) index on Labels.template
merge_request:
author:
type: other
---
title: "Add \"implements\" to the default issue closing message regex"
merge_request: 14612
author: Guilherme Vieira
type: added
---
title: Fixed commit avatars being centered vertically
merge_request:
author:
type: fixed
---
title: Only copy old/new code when selecting left/right side of parallel diff
merge_request:
author:
type: added
---
title: Add documentation to summarise project archiving
merge_request: 14650
author:
type: other
---
title: Move Custom merge methods from EE
merge_request:
author:
type: added
---
title: Compare email addresses case insensitively when verifying GPG signatures
merge_request: 14376
author: Tim Bishop
type: fixed
---
title: 'Kubernetes integration: ensure v1.8.0 compatibility'
merge_request: 14635
author:
type: fixed
---
title: Bump google-api-client Gem from 0.8.6 to 0.13.6
merge_request:
author:
type: other
---
title: Fixed merge request widget merged & closed date tooltip text
merge_request:
author:
type: fixed
---
title: Fix case sensitive email confirmation on signup
merge_request: 14606
author: robdel12
type: fixed
---
title: Add username as GL_USERNAME in hooks
merge_request:
author:
---
title: Fix gitlab-rake gitlab:import:repos task failing
merge_request:
author:
type: fixed
---
title: Fix pushes to an empty repository not invalidating has_visible_content? cache
merge_request:
author:
type: fixed
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.
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