Commit 0e5ee119 authored by Lin Jen-Shin (godfat)'s avatar Lin Jen-Shin (godfat)

Merge branch 'master' into '25680-CI_ENVIRONMENT_URL'

# Conflicts:
#   db/schema.rb
parents 88ba7a03 3605e430
...@@ -150,6 +150,7 @@ stages: ...@@ -150,6 +150,7 @@ stages:
# Trigger a package build on omnibus-gitlab repository # Trigger a package build on omnibus-gitlab repository
build-package: build-package:
image: ruby:2.3-alpine
before_script: [] before_script: []
services: [] services: []
variables: variables:
...@@ -486,25 +487,6 @@ lint:javascript:report: ...@@ -486,25 +487,6 @@ lint:javascript:report:
paths: paths:
- eslint-report.html - eslint-report.html
# Trigger docs build
# https://gitlab.com/gitlab-com/doc-gitlab-com/blob/master/README.md#deployment-process
trigger_docs:
stage: post-test
image: "alpine"
<<: *dedicated-runner
before_script:
- apk update && apk add curl
variables:
GIT_STRATEGY: "none"
cache: {}
artifacts: {}
script:
- "HTTP_STATUS=$(curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master -F variables[PROJECT]=${CI_PROJECT_NAME} --silent --output curl.log --write-out '%{http_code}' https://gitlab.com/api/v3/projects/1794617/trigger/builds)"
- if [ "${HTTP_STATUS}" -ne "201" ]; then echo "Error ${HTTP_STATUS}"; cat curl.log; echo; exit 1; fi
only:
- master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
pages: pages:
before_script: [] before_script: []
stage: pages stage: pages
......
...@@ -11,11 +11,11 @@ linters: ...@@ -11,11 +11,11 @@ linters:
# !global, !important, and !optional flags. # !global, !important, and !optional flags.
BangFormat: BangFormat:
enabled: false enabled: false
# Whether or not to prefer `border: 0` over `border: none`. # Whether or not to prefer `border: 0` over `border: none`.
BorderZero: BorderZero:
enabled: false enabled: false
# Reports when you define a rule set using a selector with chained classes # Reports when you define a rule set using a selector with chained classes
# (a.k.a. adjoining classes). # (a.k.a. adjoining classes).
ChainedClasses: ChainedClasses:
...@@ -25,13 +25,13 @@ linters: ...@@ -25,13 +25,13 @@ linters:
# (e.g. `color: green` is a color keyword) # (e.g. `color: green` is a color keyword)
ColorKeyword: ColorKeyword:
enabled: false enabled: false
# Prefer color literals (keywords or hexadecimal codes) to be used only in # Prefer color literals (keywords or hexadecimal codes) to be used only in
# variable declarations. They should be referred to via variables everywhere # variable declarations. They should be referred to via variables everywhere
# else. # else.
ColorVariable: ColorVariable:
enabled: true enabled: true
# Which form of comments to prefer in CSS. # Which form of comments to prefer in CSS.
Comment: Comment:
enabled: false enabled: false
...@@ -39,7 +39,7 @@ linters: ...@@ -39,7 +39,7 @@ linters:
# Reports @debug statements (which you probably left behind accidentally). # Reports @debug statements (which you probably left behind accidentally).
DebugStatement: DebugStatement:
enabled: false enabled: false
# Rule sets should be ordered as follows: # Rule sets should be ordered as follows:
# - @extend declarations # - @extend declarations
# - @include declarations without inner @content # - @include declarations without inner @content
...@@ -54,19 +54,19 @@ linters: ...@@ -54,19 +54,19 @@ linters:
# more information. # more information.
DisableLinterReason: DisableLinterReason:
enabled: true enabled: true
# Reports when you define the same property twice in a single rule set. # Reports when you define the same property twice in a single rule set.
DuplicateProperty: DuplicateProperty:
enabled: false enabled: false
# Separate rule, function, and mixin declarations with empty lines. # Separate rule, function, and mixin declarations with empty lines.
EmptyLineBetweenBlocks: EmptyLineBetweenBlocks:
enabled: true enabled: true
# Reports when you have an empty rule set. # Reports when you have an empty rule set.
EmptyRule: EmptyRule:
enabled: true enabled: true
# Reports when you have an @extend directive. # Reports when you have an @extend directive.
ExtendDirective: ExtendDirective:
enabled: false enabled: false
...@@ -75,49 +75,49 @@ linters: ...@@ -75,49 +75,49 @@ linters:
# when adding lines to the file, since SCM systems such as git won't # when adding lines to the file, since SCM systems such as git won't
# think that you touched the last line. # think that you touched the last line.
FinalNewline: FinalNewline:
enabled: false enabled: true
# HEX colors should use three-character values where possible. # HEX colors should use three-character values where possible.
HexLength: HexLength:
enabled: false enabled: false
# HEX color values should use lower-case colors to differentiate between # HEX color values should use lower-case colors to differentiate between
# letters and numbers, e.g. `#E3E3E3` vs. `#e3e3e3`. # letters and numbers, e.g. `#E3E3E3` vs. `#e3e3e3`.
HexNotation: HexNotation:
enabled: true enabled: true
# Avoid using ID selectors. # Avoid using ID selectors.
IdSelector: IdSelector:
enabled: false enabled: false
# The basenames of @imported SCSS partials should not begin with an # The basenames of @imported SCSS partials should not begin with an
# underscore and should not include the filename extension. # underscore and should not include the filename extension.
ImportPath: ImportPath:
enabled: false enabled: false
# Avoid using !important in properties. It is usually indicative of a # Avoid using !important in properties. It is usually indicative of a
# misunderstanding of CSS specificity and can lead to brittle code. # misunderstanding of CSS specificity and can lead to brittle code.
ImportantRule: ImportantRule:
enabled: false enabled: false
# Indentation should always be done in increments of 2 spaces. # Indentation should always be done in increments of 2 spaces.
Indentation: Indentation:
enabled: true enabled: true
width: 2 width: 2
# Don't write leading zeros for numeric values with a decimal point. # Don't write leading zeros for numeric values with a decimal point.
LeadingZero: LeadingZero:
enabled: false enabled: false
# Reports when you define the same selector twice in a single sheet. # Reports when you define the same selector twice in a single sheet.
MergeableSelector: MergeableSelector:
enabled: false enabled: false
# Functions, mixins, variables, and placeholders should be declared # Functions, mixins, variables, and placeholders should be declared
# with all lowercase letters and hyphens instead of underscores. # with all lowercase letters and hyphens instead of underscores.
NameFormat: NameFormat:
enabled: false enabled: false
# Avoid nesting selectors too deeply. # Avoid nesting selectors too deeply.
NestingDepth: NestingDepth:
enabled: false enabled: false
...@@ -129,12 +129,12 @@ linters: ...@@ -129,12 +129,12 @@ linters:
# Sort properties in a strict order. # Sort properties in a strict order.
PropertySortOrder: PropertySortOrder:
enabled: false enabled: false
# Reports when you use an unknown or disabled CSS property # Reports when you use an unknown or disabled CSS property
# (ignoring vendor-prefixed properties). # (ignoring vendor-prefixed properties).
PropertySpelling: PropertySpelling:
enabled: false enabled: false
# Configure which units are allowed for property values. # Configure which units are allowed for property values.
PropertyUnits: PropertyUnits:
enabled: false enabled: false
...@@ -144,25 +144,25 @@ linters: ...@@ -144,25 +144,25 @@ linters:
# be declared with one colon. # be declared with one colon.
PseudoElement: PseudoElement:
enabled: true enabled: true
# Avoid qualifying elements in selectors (also known as "tag-qualifying"). # Avoid qualifying elements in selectors (also known as "tag-qualifying").
QualifyingElement: QualifyingElement:
enabled: false enabled: false
# Don't write selectors with a depth of applicability greater than 3. # Don't write selectors with a depth of applicability greater than 3.
SelectorDepth: SelectorDepth:
enabled: false enabled: false
# Selectors should always use hyphenated-lowercase, rather than camelCase or # Selectors should always use hyphenated-lowercase, rather than camelCase or
# snake_case. # snake_case.
SelectorFormat: SelectorFormat:
enabled: false enabled: false
convention: hyphenated_lowercase convention: hyphenated_lowercase
# Prefer the shortest shorthand form possible for properties that support it. # Prefer the shortest shorthand form possible for properties that support it.
Shorthand: Shorthand:
enabled: true enabled: true
# Each property should have its own line, except in the special case of # Each property should have its own line, except in the special case of
# single line rulesets. # single line rulesets.
SingleLinePerProperty: SingleLinePerProperty:
...@@ -173,11 +173,11 @@ linters: ...@@ -173,11 +173,11 @@ linters:
# individual selector occupy a single line. # individual selector occupy a single line.
SingleLinePerSelector: SingleLinePerSelector:
enabled: true enabled: true
# Commas in lists should be followed by a space. # Commas in lists should be followed by a space.
SpaceAfterComma: SpaceAfterComma:
enabled: false enabled: false
# Properties should be formatted with a single space separating the colon # Properties should be formatted with a single space separating the colon
# from the property's value. # from the property's value.
SpaceAfterPropertyColon: SpaceAfterPropertyColon:
...@@ -197,12 +197,12 @@ linters: ...@@ -197,12 +197,12 @@ linters:
# colon. # colon.
SpaceAfterVariableName: SpaceAfterVariableName:
enabled: false enabled: false
# Operators should be formatted with a single space on both sides of an # Operators should be formatted with a single space on both sides of an
# infix operator. # infix operator.
SpaceAroundOperator: SpaceAroundOperator:
enabled: true enabled: true
# Opening braces should be preceded by a single space. # Opening braces should be preceded by a single space.
SpaceBeforeBrace: SpaceBeforeBrace:
enabled: true enabled: true
...@@ -210,7 +210,7 @@ linters: ...@@ -210,7 +210,7 @@ linters:
# Parentheses should not be padded with spaces. # Parentheses should not be padded with spaces.
SpaceBetweenParens: SpaceBetweenParens:
enabled: false enabled: false
# Enforces that string literals should be written with a consistent form # Enforces that string literals should be written with a consistent form
# of quotes (single or double). # of quotes (single or double).
StringQuotes: StringQuotes:
...@@ -241,7 +241,7 @@ linters: ...@@ -241,7 +241,7 @@ linters:
# be unnecessary. # be unnecessary.
UnnecessaryParentReference: UnnecessaryParentReference:
enabled: false enabled: false
# URLs should be valid and not contain protocols or domain names. # URLs should be valid and not contain protocols or domain names.
UrlFormat: UrlFormat:
enabled: true enabled: true
......
...@@ -341,7 +341,7 @@ GEM ...@@ -341,7 +341,7 @@ GEM
grape-entity (0.6.0) grape-entity (0.6.0)
activesupport activesupport
multi_json (>= 1.3.2) multi_json (>= 1.3.2)
grpc (1.2.5) grpc (1.3.4)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
googleauth (~> 0.5.1) googleauth (~> 0.5.1)
haml (4.0.7) haml (4.0.7)
......
...@@ -330,10 +330,26 @@ class CopyAsGFM { ...@@ -330,10 +330,26 @@ class CopyAsGFM {
} }
static transformGFMSelection(documentFragment) { static transformGFMSelection(documentFragment) {
// If the documentFragment contains more than just Markdown, don't copy as GFM. const gfmEls = documentFragment.querySelectorAll('.md, .wiki');
if (documentFragment.querySelector('.md, .wiki')) return null; switch (gfmEls.length) {
case 0: {
return documentFragment;
}
case 1: {
return gfmEls[0];
}
default: {
const allGfmEl = document.createElement('div');
for (let i = 0; i < gfmEls.length; i += 1) {
const lineEl = gfmEls[i];
allGfmEl.appendChild(lineEl);
allGfmEl.appendChild(document.createTextNode('\n\n'));
}
return documentFragment; return allGfmEl;
}
}
} }
static transformCodeSelection(documentFragment) { static transformCodeSelection(documentFragment) {
......
...@@ -123,7 +123,7 @@ import ShortcutsBlob from './shortcuts_blob'; ...@@ -123,7 +123,7 @@ import ShortcutsBlob from './shortcuts_blob';
break; break;
case 'projects:merge_requests:index': case 'projects:merge_requests:index':
case 'projects:issues:index': case 'projects:issues:index':
if (gl.FilteredSearchManager) { if (gl.FilteredSearchManager && document.querySelector('.filtered-search')) {
new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests'); new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests');
} }
Issuable.init(); Issuable.init();
......
/* eslint-disable */ /* eslint-disable */
import AjaxCache from '../../lib/utils/ajax_cache';
const AjaxFilter = { const AjaxFilter = {
init: function(hook) { init: function(hook) {
...@@ -58,50 +59,24 @@ const AjaxFilter = { ...@@ -58,50 +59,24 @@ const AjaxFilter = {
this.loading = true; this.loading = true;
var params = config.params || {}; var params = config.params || {};
params[config.searchKey] = searchValue; params[config.searchKey] = searchValue;
var self = this;
self.cache = self.cache || {};
var url = config.endpoint + this.buildParams(params); var url = config.endpoint + this.buildParams(params);
var urlCachedData = self.cache[url]; return AjaxCache.retrieve(url)
if (urlCachedData) { .then((data) => {
self._loadData(urlCachedData, config, self); this._loadData(data, config);
} else { })
this._loadUrlData(url) .catch(config.onError);
.then(function(data) {
self._loadData(data, config, self);
}, config.onError).catch(config.onError);
}
}, },
_loadUrlData: function _loadUrlData(url) { _loadData(data, config) {
var self = this; const list = this.hook.list;
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
self.cache[url] = data;
return resolve(data);
} else {
return reject([xhr.responseText, xhr.status]);
}
}
};
xhr.send();
});
},
_loadData: function _loadData(data, config, self) {
const list = self.hook.list;
if (config.loadingTemplate && list.data === undefined || if (config.loadingTemplate && list.data === undefined ||
list.data.length === 0) { list.data.length === 0) {
const dataLoadingTemplate = list.list.querySelector('[data-loading-template]'); const dataLoadingTemplate = list.list.querySelector('[data-loading-template]');
if (dataLoadingTemplate) { if (dataLoadingTemplate) {
dataLoadingTemplate.outerHTML = self.listTemplate; dataLoadingTemplate.outerHTML = this.listTemplate;
} }
} }
if (!self.destroyed) { if (!this.destroyed) {
var hookListChildren = list.list.children; var hookListChildren = list.list.children;
var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic'); var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic');
if (onlyDynamicList && data.length === 0) { if (onlyDynamicList && data.length === 0) {
...@@ -109,7 +84,7 @@ const AjaxFilter = { ...@@ -109,7 +84,7 @@ const AjaxFilter = {
} }
list.setData.call(list, data); list.setData.call(list, data);
} }
self.notLoading(); this.notLoading();
list.currentIndex = 0; list.currentIndex = 0;
}, },
......
...@@ -285,7 +285,7 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion'; ...@@ -285,7 +285,7 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
// Similar to `toggler_behavior` in the discussion tab // Similar to `toggler_behavior` in the discussion tab
const hash = window.gl.utils.getLocationHash(); const hash = window.gl.utils.getLocationHash();
const anchor = hash && $container.find(`[id="${hash}"]`); const anchor = hash && $container.find(`[id="${hash}"]`);
if (anchor) { if (anchor && anchor.length > 0) {
const notesContent = anchor.closest('.notes_content'); const notesContent = anchor.closest('.notes_content');
const lineType = notesContent.hasClass('new') ? 'new' : 'old'; const lineType = notesContent.hasClass('new') ? 'new' : 'old';
notes.toggleDiffNote({ notes.toggleDiffNote({
......
...@@ -1212,7 +1212,7 @@ const normalizeNewlines = function(str) { ...@@ -1212,7 +1212,7 @@ const normalizeNewlines = function(str) {
`<li id="${uniqueId}" class="note being-posted fade-in-half timeline-entry"> `<li id="${uniqueId}" class="note being-posted fade-in-half timeline-entry">
<div class="timeline-entry-inner"> <div class="timeline-entry-inner">
<div class="timeline-icon"> <div class="timeline-icon">
<a href="/${currentUsername}"><span class="dummy-avatar"></span></a> <a href="/${currentUsername}"><span class="avatar dummy-avatar"></span></a>
</div> </div>
<div class="timeline-content ${discussionClass}"> <div class="timeline-content ${discussionClass}">
<div class="note-header"> <div class="note-header">
......
import Raven from 'raven-js'; import Raven from 'raven-js';
import $ from 'jquery';
const IGNORE_ERRORS = [ const IGNORE_ERRORS = [
// Random plugins/extensions // Random plugins/extensions
...@@ -74,7 +75,7 @@ const RavenConfig = { ...@@ -74,7 +75,7 @@ const RavenConfig = {
}, },
bindRavenErrors() { bindRavenErrors() {
window.$(document).on('ajaxError.raven', this.handleRavenErrors); $(document).on('ajaxError.raven', this.handleRavenErrors);
}, },
handleRavenErrors(event, req, config, err) { handleRavenErrors(event, req, config, err) {
......
...@@ -38,7 +38,7 @@ import './shortcuts_navigation'; ...@@ -38,7 +38,7 @@ import './shortcuts_navigation';
} }
ShortcutsIssuable.prototype.replyWithSelectedText = function() { ShortcutsIssuable.prototype.replyWithSelectedText = function() {
var quote, documentFragment, selected, separator; var quote, documentFragment, el, selected, separator;
var replyField = $('.js-main-target-form #note_note'); var replyField = $('.js-main-target-form #note_note');
documentFragment = window.gl.utils.getSelectedFragment(); documentFragment = window.gl.utils.getSelectedFragment();
...@@ -47,10 +47,8 @@ import './shortcuts_navigation'; ...@@ -47,10 +47,8 @@ import './shortcuts_navigation';
return; return;
} }
// If the documentFragment contains more than just Markdown, don't copy as GFM. el = window.gl.CopyAsGFM.transformGFMSelection(documentFragment.cloneNode(true));
if (documentFragment.querySelector('.md, .wiki')) return; selected = window.gl.CopyAsGFM.nodeToGFM(el);
selected = window.gl.CopyAsGFM.nodeToGFM(documentFragment);
if (selected.trim() === "") { if (selected.trim() === "") {
return; return;
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
window.SingleFileDiff = (function() { window.SingleFileDiff = (function() {
var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER; var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER;
WRAPPER = '<div class="diff-content diff-wrap-lines"></div>'; WRAPPER = '<div class="diff-content"></div>';
LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>'; LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>';
......
<script>
import ciIconBadge from './ci_badge_link.vue';
import timeagoTooltip from './time_ago_tooltip.vue';
import tooltipMixin from '../mixins/tooltip';
import userAvatarLink from './user_avatar/user_avatar_link.vue';
/**
* Renders header component for job and pipeline page based on UI mockups
*
* Used in:
* - job show page
* - pipeline show page
*/
export default {
props: {
status: {
type: Object,
required: true,
},
itemName: {
type: String,
required: true,
},
itemId: {
type: Number,
required: true,
},
time: {
type: String,
required: true,
},
user: {
type: Object,
required: true,
},
actions: {
type: Array,
required: false,
default: () => [],
},
},
mixins: [
tooltipMixin,
],
components: {
ciIconBadge,
timeagoTooltip,
userAvatarLink,
},
computed: {
userAvatarAltText() {
return `${this.user.name}'s avatar`;
},
},
methods: {
onClickAction(action) {
this.$emit('postAction', action);
},
},
};
</script>
<template>
<header class="page-content-header top-area">
<section class="header-main-content">
<ci-icon-badge :status="status" />
<strong>
{{itemName}} #{{itemId}}
</strong>
triggered
<timeago-tooltip :time="time" />
by
<user-avatar-link
:link-href="user.web_url"
:img-src="user.avatar_url"
:img-alt="userAvatarAltText"
:tooltip-text="user.name"
:img-size="24"
/>
<a
:href="user.web_url"
:title="user.email"
class="js-user-link commit-committer-link"
ref="tooltip">
{{user.name}}
</a>
</section>
<section
class="header-action-button nav-controls"
v-if="actions.length">
<template
v-for="action in actions">
<a
v-if="action.type === 'link'"
:href="action.path"
:class="action.cssClass">
{{action.label}}
</a>
<button
v-else="action.type === 'button'"
@click="onClickAction(action)"
:class="action.cssClass"
type="button">
{{action.label}}
</button>
</template>
</section>
</header>
</template>
<script>
import tooltipMixin from '../mixins/tooltip';
import timeagoMixin from '../mixins/timeago';
import '../../lib/utils/datetime_utility';
/**
* Port of ruby helper time_ago_with_tooltip
*/
export default {
props: {
time: {
type: String,
required: true,
},
tooltipPlacement: {
type: String,
required: false,
default: 'top',
},
shortFormat: {
type: Boolean,
required: false,
default: false,
},
cssClass: {
type: String,
required: false,
default: '',
},
},
mixins: [
tooltipMixin,
timeagoMixin,
],
computed: {
timeagoCssClass() {
return this.shortFormat ? 'js-short-timeago' : 'js-timeago';
},
},
};
</script>
<template>
<time
:class="[timeagoCssClass, cssClass]"
class="js-timeago js-timeago-render"
:title="tooltipTitle(time)"
:data-placement="tooltipPlacement"
data-container="body"
ref="tooltip">
{{timeFormated(time)}}
</time>
</template>
import '../../lib/utils/datetime_utility';
/**
* Mixin with time ago methods used in some vue components
*/
export default {
methods: {
timeFormated(time) {
const timeago = gl.utils.getTimeago();
return timeago.format(time);
},
tooltipTitle(time) {
return gl.utils.formatDate(time);
},
},
};
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
top: 0; top: 0;
margin-top: 3px; margin-top: 3px;
padding: $gl-padding; padding: $gl-padding;
z-index: 9; z-index: 300;
width: 300px; width: 300px;
font-size: 14px; font-size: 14px;
background-color: $white-light; background-color: $white-light;
...@@ -110,6 +110,7 @@ ...@@ -110,6 +110,7 @@
.award-control { .award-control {
margin: 0 5px 6px 0; margin: 0 5px 6px 0;
outline: 0; outline: 0;
position: relative;
&.disabled { &.disabled {
cursor: default; cursor: default;
...@@ -227,8 +228,8 @@ ...@@ -227,8 +228,8 @@
.award-control-icon-positive, .award-control-icon-positive,
.award-control-icon-super-positive { .award-control-icon-super-positive {
position: absolute; position: absolute;
left: 11px; left: 10px;
bottom: 7px; bottom: 6px;
opacity: 0; opacity: 0;
@include transition(opacity, transform); @include transition(opacity, transform);
} }
......
...@@ -66,10 +66,10 @@ ...@@ -66,10 +66,10 @@
&.video { &.video {
background: $file-image-bg; background: $file-image-bg;
text-align: center; text-align: center;
padding: 30px;
img, img,
video { video {
padding: 20px;
max-width: 80%; max-width: 80%;
} }
} }
......
...@@ -36,6 +36,10 @@ ...@@ -36,6 +36,10 @@
border-radius: 0; border-radius: 0;
} }
} }
&:empty {
margin: 0;
}
} }
@media (max-width: $screen-sm-max) { @media (max-width: $screen-sm-max) {
......
...@@ -3,12 +3,6 @@ ...@@ -3,12 +3,6 @@
margin: 0; margin: 0;
padding: 0; padding: 0;
.note-text {
p:last-child {
margin-bottom: 0 !important;
}
}
.system-note { .system-note {
.note-text { .note-text {
color: $gl-text-color !important; color: $gl-text-color !important;
......
...@@ -21,6 +21,10 @@ ...@@ -21,6 +21,10 @@
margin-top: 0; margin-top: 0;
} }
> :last-child {
margin-bottom: 0;
}
// Single code lines should wrap // Single code lines should wrap
code { code {
font-family: $monospace_font; font-family: $monospace_font;
...@@ -157,7 +161,7 @@ ...@@ -157,7 +161,7 @@
ul, ul,
ol { ol {
padding: 0; padding: 0;
margin: 0 0 16px !important; margin: 0 0 16px;
} }
ul:dir(rtl), ul:dir(rtl),
......
...@@ -247,7 +247,6 @@ $dark-diff-match-bg: rgba(255, 255, 255, 0.3); ...@@ -247,7 +247,6 @@ $dark-diff-match-bg: rgba(255, 255, 255, 0.3);
$dark-diff-match-color: rgba(255, 255, 255, 0.1); $dark-diff-match-color: rgba(255, 255, 255, 0.1);
$file-mode-changed: #777; $file-mode-changed: #777;
$file-mode-changed: #777; $file-mode-changed: #777;
$diff-image-bg: #ddd;
$diff-image-info-color: grey; $diff-image-info-color: grey;
$diff-swipe-border: #999; $diff-swipe-border: #999;
$diff-view-modes-color: grey; $diff-view-modes-color: grey;
......
...@@ -151,14 +151,10 @@ ...@@ -151,14 +151,10 @@
} }
} }
} }
.text-file.diff-wrap-lines table .line_holder td span {
white-space: pre-wrap;
}
} }
.image { .image {
background: $diff-image-bg; background: $file-image-bg;
text-align: center; text-align: center;
padding: 30px; padding: 30px;
......
...@@ -431,7 +431,7 @@ ...@@ -431,7 +431,7 @@
} }
.detail-page-description { .detail-page-description {
padding: 16px 0 0; padding: 16px 0;
small { small {
color: $gray-darkest; color: $gray-darkest;
...@@ -441,7 +441,7 @@ ...@@ -441,7 +441,7 @@
.edited-text { .edited-text {
color: $gray-darkest; color: $gray-darkest;
display: block; display: block;
margin: 0 0 16px; margin: 16px 0 0;
.author_link { .author_link {
color: $gray-darkest; color: $gray-darkest;
......
...@@ -184,4 +184,4 @@ ...@@ -184,4 +184,4 @@
} }
} }
} }
} }
\ No newline at end of file
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
.note-edit-form { .note-edit-form {
.note-form-actions { .note-form-actions {
position: relative; position: relative;
margin: $gl-padding 0; margin: $gl-padding 0 0;
} }
.note-preview-holder { .note-preview-holder {
...@@ -124,10 +124,18 @@ ...@@ -124,10 +124,18 @@
} }
.discussion-form { .discussion-form {
padding: $gl-padding-top $gl-padding; padding: $gl-padding-top $gl-padding $gl-padding;
background-color: $white-light; background-color: $white-light;
} }
.discussion-notes .disabled-comment {
padding: 6px 0;
}
.notes-form > li {
border: 0;
}
.note-edit-form { .note-edit-form {
display: none; display: none;
font-size: 14px; font-size: 14px;
......
...@@ -14,19 +14,6 @@ ul.notes { ...@@ -14,19 +14,6 @@ ul.notes {
margin: 0; margin: 0;
padding: 0; padding: 0;
.timeline-icon {
float: left;
svg {
width: 16px;
height: 16px;
fill: $gray-darkest;
position: absolute;
left: 0;
top: 16px;
}
}
.timeline-content { .timeline-content {
margin-left: 55px; margin-left: 55px;
...@@ -56,21 +43,22 @@ ul.notes { ...@@ -56,21 +43,22 @@ ul.notes {
position: relative; position: relative;
} }
.note { > li {
padding: $gl-padding $gl-btn-padding 0; padding: $gl-padding $gl-btn-padding;
display: block; display: block;
position: relative; position: relative;
border-bottom: 1px solid $white-normal; border-bottom: 1px solid $white-normal;
&:last-child {
// Override `.timeline > li:last-child { border-bottom: none; }`
border-bottom: 1px solid $white-normal;
}
&.being-posted { &.being-posted {
pointer-events: none; pointer-events: none;
opacity: 0.5; opacity: 0.5;
.dummy-avatar { .dummy-avatar {
display: inline-block;
height: 40px;
width: 40px;
border-radius: 50%;
background-color: $kdb-border; background-color: $kdb-border;
border: 1px solid darken($kdb-border, 25%); border: 1px solid darken($kdb-border, 25%);
} }
...@@ -126,7 +114,7 @@ ul.notes { ...@@ -126,7 +114,7 @@ ul.notes {
.note-awards { .note-awards {
.js-awards-block { .js-awards-block {
margin-bottom: 16px; margin-top: 16px;
} }
} }
...@@ -161,7 +149,7 @@ ul.notes { ...@@ -161,7 +149,7 @@ ul.notes {
.system-note { .system-note {
font-size: 14px; font-size: 14px;
padding: 0; padding-left: 0;
clear: both; clear: both;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
...@@ -198,11 +186,22 @@ ul.notes { ...@@ -198,11 +186,22 @@ ul.notes {
} }
} }
.timeline-content { .timeline-icon {
padding: 14px 10px; float: left;
svg {
width: 16px;
height: 16px;
fill: $gray-darkest;
position: absolute;
left: 0;
top: 2px;
}
}
.timeline-content {
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
margin-left: 20px; margin-left: 30px;
} }
} }
...@@ -385,6 +384,12 @@ ul.notes { ...@@ -385,6 +384,12 @@ ul.notes {
padding-bottom: 0; padding-bottom: 0;
} }
.note-header-author-name {
@media (max-width: $screen-xs-max) {
display: none;
}
}
.note-headline-light { .note-headline-light {
display: inline; display: inline;
...@@ -590,10 +595,15 @@ ul.notes { ...@@ -590,10 +595,15 @@ ul.notes {
.discussion-body, .discussion-body,
.diff-file { .diff-file {
.notes .note { .notes .note {
padding: 10px 15px; padding-left: $gl-padding;
padding-right: $gl-padding;
&.system-note { &.system-note {
padding: 0; padding-left: 0;
@media (min-width: $screen-sm-min) {
margin-left: 70px;
}
} }
} }
} }
...@@ -607,17 +617,11 @@ ul.notes { ...@@ -607,17 +617,11 @@ ul.notes {
} }
.disabled-comment { .disabled-comment {
margin-left: -$gl-padding-top;
margin-right: -$gl-padding-top;
background-color: $gray-light; background-color: $gray-light;
border-radius: $border-radius-base; border-radius: $border-radius-base;
border: 1px solid $border-gray-normal; border: 1px solid $border-gray-normal;
color: $note-disabled-comment-color; color: $note-disabled-comment-color;
line-height: 200px; padding: 90px 0;
.disabled-comment-text {
line-height: normal;
}
a { a {
color: $gl-link-color; color: $gl-link-color;
...@@ -667,7 +671,7 @@ ul.notes { ...@@ -667,7 +671,7 @@ ul.notes {
.line-resolve-all { .line-resolve-all {
vertical-align: middle; vertical-align: middle;
display: inline-block; display: inline-block;
padding: 6px 10px; padding: 5px 10px 6px;
background-color: $gray-light; background-color: $gray-light;
border: 1px solid $border-color; border: 1px solid $border-color;
border-radius: $border-radius-default; border-radius: $border-radius-default;
...@@ -680,6 +684,10 @@ ul.notes { ...@@ -680,6 +684,10 @@ ul.notes {
.line-resolve-btn { .line-resolve-btn {
margin-right: 5px; margin-right: 5px;
svg {
vertical-align: middle;
}
} }
} }
...@@ -716,6 +724,10 @@ ul.notes { ...@@ -716,6 +724,10 @@ ul.notes {
} }
} }
.line-resolve-text {
vertical-align: middle;
}
.discussion-next-btn { .discussion-next-btn {
svg { svg {
margin: 0; margin: 0;
...@@ -733,9 +745,8 @@ ul.notes { ...@@ -733,9 +745,8 @@ ul.notes {
// Merge request notes in diffs // Merge request notes in diffs
.diff-file { .diff-file {
// Diff is side by side // Diff is side by side
.notes_content.parallel .note-header .note-headline-light { .notes_content.parallel .note-header .note-header-author-name {
display: block; display: block;
position: relative;
} }
// Diff is inline // Diff is inline
.notes_content .note-header .note-headline-light { .notes_content .note-header .note-headline-light {
......
...@@ -384,10 +384,6 @@ a.deploy-project-label { ...@@ -384,10 +384,6 @@ a.deploy-project-label {
} }
} }
.last-push-widget {
margin-top: -1px;
}
.fork-namespaces { .fork-namespaces {
.row { .row {
-webkit-flex-wrap: wrap; -webkit-flex-wrap: wrap;
......
class Admin::HookLogsController < Admin::ApplicationController
include HooksExecution
before_action :hook, only: [:show, :retry]
before_action :hook_log, only: [:show, :retry]
respond_to :html
def show
end
def retry
status, message = hook.execute(hook_log.request_data, hook_log.trigger)
set_hook_execution_notice(status, message)
redirect_to edit_admin_hook_path(@hook)
end
private
def hook
@hook ||= SystemHook.find(params[:hook_id])
end
def hook_log
@hook_log ||= hook.web_hook_logs.find(params[:id])
end
end
class Admin::HooksController < Admin::ApplicationController class Admin::HooksController < Admin::ApplicationController
before_action :hook, only: :edit include HooksExecution
before_action :hook_logs, only: :edit
def index def index
@hooks = SystemHook.all @hooks = SystemHook.all
...@@ -36,15 +38,9 @@ class Admin::HooksController < Admin::ApplicationController ...@@ -36,15 +38,9 @@ class Admin::HooksController < Admin::ApplicationController
end end
def test def test
data = { status, message = hook.execute(sample_hook_data, 'system_hooks')
event_name: "project_create",
name: "Ruby", set_hook_execution_notice(status, message)
path: "ruby",
project_id: 1,
owner_name: "Someone",
owner_email: "example@gitlabhq.com"
}
hook.execute(data, 'system_hooks')
redirect_back_or_default redirect_back_or_default
end end
...@@ -55,6 +51,11 @@ class Admin::HooksController < Admin::ApplicationController ...@@ -55,6 +51,11 @@ class Admin::HooksController < Admin::ApplicationController
@hook ||= SystemHook.find(params[:id]) @hook ||= SystemHook.find(params[:id])
end end
def hook_logs
@hook_logs ||=
Kaminari.paginate_array(hook.web_hook_logs.order(created_at: :desc)).page(params[:page])
end
def hook_params def hook_params
params.require(:hook).permit( params.require(:hook).permit(
:enable_ssl_verification, :enable_ssl_verification,
...@@ -65,4 +66,15 @@ class Admin::HooksController < Admin::ApplicationController ...@@ -65,4 +66,15 @@ class Admin::HooksController < Admin::ApplicationController
:url :url
) )
end end
def sample_hook_data
{
event_name: "project_create",
name: "Ruby",
path: "ruby",
project_id: 1,
owner_name: "Someone",
owner_email: "example@gitlabhq.com"
}
end
end end
...@@ -11,6 +11,7 @@ class ApplicationController < ActionController::Base ...@@ -11,6 +11,7 @@ class ApplicationController < ActionController::Base
include EnforcesTwoFactorAuthentication include EnforcesTwoFactorAuthentication
before_action :authenticate_user_from_private_token! before_action :authenticate_user_from_private_token!
before_action :authenticate_user_from_rss_token!
before_action :authenticate_user! before_action :authenticate_user!
before_action :validate_user_service_ticket! before_action :validate_user_service_ticket!
before_action :check_password_expiration before_action :check_password_expiration
...@@ -72,13 +73,20 @@ class ApplicationController < ActionController::Base ...@@ -72,13 +73,20 @@ class ApplicationController < ActionController::Base
user = User.find_by_authentication_token(token) || User.find_by_personal_access_token(token) user = User.find_by_authentication_token(token) || User.find_by_personal_access_token(token)
if user && can?(user, :log_in) sessionless_sign_in(user)
# Notice we are passing store false, so the user is not end
# actually stored in the session and a token is needed
# for every request. If you want the token to work as a # This filter handles authentication for atom request with an rss_token
# sign in token, you can simply remove store: false. def authenticate_user_from_rss_token!
sign_in user, store: false return unless request.format.atom?
end
token = params[:rss_token].presence
return unless token.present?
user = User.find_by_rss_token(token)
sessionless_sign_in(user)
end end
def log_exception(exception) def log_exception(exception)
...@@ -275,11 +283,17 @@ class ApplicationController < ActionController::Base ...@@ -275,11 +283,17 @@ class ApplicationController < ActionController::Base
request.base_url request.base_url
end end
def set_locale def set_locale(&block)
Gitlab::I18n.set_locale(current_user) Gitlab::I18n.with_user_locale(current_user, &block)
end
yield def sessionless_sign_in(user)
ensure if user && can?(user, :log_in)
Gitlab::I18n.reset_locale # Notice we are passing store false, so the user is not
# actually stored in the session and a token is needed
# for every request. If you want the token to work as a
# sign in token, you can simply remove store: false.
sign_in user, store: false
end
end end
end end
...@@ -8,17 +8,6 @@ module DiffForPath ...@@ -8,17 +8,6 @@ module DiffForPath
return render_404 unless diff_file return render_404 unless diff_file
diff_commit = commit_for_diff(diff_file) render json: { html: view_to_html_string('projects/diffs/_content', diff_file: diff_file) }
blob = diff_file.blob(diff_commit)
locals = {
diff_file: diff_file,
diff_commit: diff_commit,
diff_refs: diffs.diff_refs,
blob: blob,
project: project
}
render json: { html: view_to_html_string('projects/diffs/_content', locals) }
end end
end end
module HooksExecution
extend ActiveSupport::Concern
private
def set_hook_execution_notice(status, message)
if status && status >= 200 && status < 400
flash[:notice] = "Hook executed successfully: HTTP #{status}"
elsif status
flash[:alert] = "Hook executed successfully but returned HTTP #{status} #{message}"
else
flash[:alert] = "Hook execution failed: #{message}"
end
end
end
...@@ -8,7 +8,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -8,7 +8,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = load_projects(params.merge(non_public: true)).page(params[:page]) @projects = load_projects(params.merge(non_public: true)).page(params[:page])
respond_to do |format| respond_to do |format|
format.html { @last_push = current_user.recent_push } format.html
format.atom do format.atom do
load_events load_events
render layout: false render layout: false
...@@ -25,7 +25,6 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -25,7 +25,6 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = load_projects(params.merge(starred: true)). @projects = load_projects(params.merge(starred: true)).
includes(:forked_from_project, :tags).page(params[:page]) includes(:forked_from_project, :tags).page(params[:page])
@last_push = current_user.recent_push
@groups = [] @groups = []
respond_to do |format| respond_to do |format|
......
...@@ -9,8 +9,6 @@ class DashboardController < Dashboard::ApplicationController ...@@ -9,8 +9,6 @@ class DashboardController < Dashboard::ApplicationController
respond_to :html respond_to :html
def activity def activity
@last_push = current_user.recent_push
respond_to do |format| respond_to do |format|
format.html format.html
......
...@@ -165,7 +165,6 @@ class GroupsController < Groups::ApplicationController ...@@ -165,7 +165,6 @@ class GroupsController < Groups::ApplicationController
def user_actions def user_actions
if current_user if current_user
@last_push = current_user.recent_push
@notification_setting = current_user.notification_settings_for(group) @notification_setting = current_user.notification_settings_for(group)
end end
end end
......
...@@ -40,6 +40,14 @@ class ProfilesController < Profiles::ApplicationController ...@@ -40,6 +40,14 @@ class ProfilesController < Profiles::ApplicationController
redirect_to profile_account_path redirect_to profile_account_path
end end
def reset_rss_token
if current_user.reset_rss_token!
flash[:notice] = "RSS token was successfully reset"
end
redirect_to profile_account_path
end
def audit_log def audit_log
@events = AuditEvent.where(entity_type: "User", entity_id: current_user.id). @events = AuditEvent.where(entity_type: "User", entity_id: current_user.id).
order("created_at DESC"). order("created_at DESC").
......
...@@ -51,13 +51,9 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -51,13 +51,9 @@ class Projects::CompareController < Projects::ApplicationController
if @compare if @compare
@commits = @compare.commits @commits = @compare.commits
@start_commit = @compare.start_commit
@commit = @compare.commit
@base_commit = @compare.base_commit
@diffs = @compare.diffs(diff_options) @diffs = @compare.diffs(diff_options)
environment_params = @repository.branch_exists?(@head_ref) ? { ref: @head_ref } : { commit: @commit } environment_params = @repository.branch_exists?(@head_ref) ? { ref: @head_ref } : { commit: @compare.commit }
@environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last @environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last
@diff_notes_disabled = true @diff_notes_disabled = true
......
class Projects::HookLogsController < Projects::ApplicationController
include HooksExecution
before_action :authorize_admin_project!
before_action :hook, only: [:show, :retry]
before_action :hook_log, only: [:show, :retry]
respond_to :html
layout 'project_settings'
def show
end
def retry
status, message = hook.execute(hook_log.request_data, hook_log.trigger)
set_hook_execution_notice(status, message)
redirect_to edit_namespace_project_hook_path(@project.namespace, @project, @hook)
end
private
def hook
@hook ||= @project.hooks.find(params[:hook_id])
end
def hook_log
@hook_log ||= hook.web_hook_logs.find(params[:id])
end
end
class Projects::HooksController < Projects::ApplicationController class Projects::HooksController < Projects::ApplicationController
include HooksExecution
# Authorize # Authorize
before_action :authorize_admin_project! before_action :authorize_admin_project!
before_action :hook, only: :edit before_action :hook_logs, only: :edit
respond_to :html respond_to :html
...@@ -34,13 +36,7 @@ class Projects::HooksController < Projects::ApplicationController ...@@ -34,13 +36,7 @@ class Projects::HooksController < Projects::ApplicationController
if !@project.empty_repo? if !@project.empty_repo?
status, message = TestHookService.new.execute(hook, current_user) status, message = TestHookService.new.execute(hook, current_user)
if status && status >= 200 && status < 400 set_hook_execution_notice(status, message)
flash[:notice] = "Hook executed successfully: HTTP #{status}"
elsif status
flash[:alert] = "Hook executed successfully but returned HTTP #{status} #{message}"
else
flash[:alert] = "Hook execution failed: #{message}"
end
else else
flash[:alert] = 'Hook execution failed. Ensure the project has commits.' flash[:alert] = 'Hook execution failed. Ensure the project has commits.'
end end
...@@ -60,6 +56,11 @@ class Projects::HooksController < Projects::ApplicationController ...@@ -60,6 +56,11 @@ class Projects::HooksController < Projects::ApplicationController
@hook ||= @project.hooks.find(params[:id]) @hook ||= @project.hooks.find(params[:id])
end end
def hook_logs
@hook_logs ||=
Kaminari.paginate_array(hook.web_hook_logs.order(created_at: :desc)).page(params[:page])
end
def hook_params def hook_params
params.require(:hook).permit( params.require(:hook).permit(
:job_events, :job_events,
......
...@@ -14,7 +14,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -14,7 +14,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
] ]
before_action :validates_merge_request, only: [:show, :diffs, :commits, :pipelines] before_action :validates_merge_request, only: [:show, :diffs, :commits, :pipelines]
before_action :define_show_vars, only: [:diffs, :commits, :conflicts, :conflict_for_path, :builds, :pipelines] before_action :define_show_vars, only: [:diffs, :commits, :conflicts, :conflict_for_path, :builds, :pipelines]
before_action :define_commit_vars, only: [:diffs]
before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :conflict_for_path, :pipelines] before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :conflict_for_path, :pipelines]
before_action :close_merge_request_without_source_project, only: [:show, :diffs, :commits, :builds, :pipelines] before_action :close_merge_request_without_source_project, only: [:show, :diffs, :commits, :builds, :pipelines]
before_action :check_if_can_be_merged, only: :show before_action :check_if_can_be_merged, only: :show
...@@ -130,8 +129,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -130,8 +129,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@diff_notes_disabled = true @diff_notes_disabled = true
end end
define_commit_vars
render_diff_for_path(@diffs) render_diff_for_path(@diffs)
end end
...@@ -500,11 +497,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -500,11 +497,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes)) @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes))
end end
def define_commit_vars
@commit = @merge_request.diff_head_commit
@base_commit = @merge_request.diff_base_commit || @merge_request.likely_diff_base_commit
end
def define_diff_vars def define_diff_vars
@merge_request_diff = @merge_request_diff =
if params[:diff_id] if params[:diff_id]
...@@ -569,7 +561,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -569,7 +561,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@source_project = merge_request.source_project @source_project = merge_request.source_project
@commits = @merge_request.compare_commits.reverse @commits = @merge_request.compare_commits.reverse
@commit = @merge_request.diff_head_commit @commit = @merge_request.diff_head_commit
@base_commit = @merge_request.diff_base_commit
@note_counts = Note.where(commit_id: @commits.map(&:id)). @note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count group(:commit_id).count
......
...@@ -74,6 +74,6 @@ class Projects::RefsController < Projects::ApplicationController ...@@ -74,6 +74,6 @@ class Projects::RefsController < Projects::ApplicationController
private private
def validate_ref_id def validate_ref_id
return not_found! if params[:id].present? && params[:id] !~ Gitlab::Regex.git_reference_regex return not_found! if params[:id].present? && params[:id] !~ Gitlab::PathRegex.git_reference_regex
end end
end end
...@@ -15,16 +15,6 @@ module CommitsHelper ...@@ -15,16 +15,6 @@ module CommitsHelper
commit_person_link(commit, options.merge(source: :committer)) commit_person_link(commit, options.merge(source: :committer))
end end
def image_diff_class(diff)
if diff.deleted_file
"deleted"
elsif diff.new_file
"added"
else
nil
end
end
def commit_to_html(commit, ref, project) def commit_to_html(commit, ref, project)
render 'projects/commits/commit', render 'projects/commits/commit',
commit: commit, commit: commit,
......
...@@ -102,14 +102,14 @@ module DiffHelper ...@@ -102,14 +102,14 @@ module DiffHelper
].join(' ').html_safe ].join(' ').html_safe
end end
def commit_for_diff(diff_file) def diff_file_blob_raw_path(diff_file)
return diff_file.content_commit if diff_file.content_commit namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.content_sha, diff_file.file_path))
end
if diff_file.deleted_file def diff_file_old_blob_raw_path(diff_file)
@base_commit || @commit.parent || @commit sha = diff_file.old_content_sha
else return unless sha
@commit namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.old_content_sha, diff_file.old_path))
end
end end
def diff_file_html_data(project, diff_file_path, diff_commit_id) def diff_file_html_data(project, diff_file_path, diff_commit_id)
...@@ -120,8 +120,8 @@ module DiffHelper ...@@ -120,8 +120,8 @@ module DiffHelper
} }
end end
def editable_diff?(diff) def editable_diff?(diff_file)
!diff.deleted_file && @merge_request && @merge_request.source_project !diff_file.deleted_file? && @merge_request && @merge_request.source_project
end end
private private
......
...@@ -69,13 +69,12 @@ module LabelsHelper ...@@ -69,13 +69,12 @@ module LabelsHelper
end end
def render_colored_label(label, label_suffix = '', tooltip: true) def render_colored_label(label, label_suffix = '', tooltip: true)
label_color = label.color || Label::DEFAULT_COLOR text_color = text_color_for_bg(label.color)
text_color = text_color_for_bg(label_color)
# Intentionally not using content_tag here so that this method can be called # Intentionally not using content_tag here so that this method can be called
# by LabelReferenceFilter # by LabelReferenceFilter
span = %(<span class="label color-label #{"has-tooltip" if tooltip}" ) + span = %(<span class="label color-label #{"has-tooltip" if tooltip}" ) +
%(style="background-color: #{label_color}; color: #{text_color}" ) + %(style="background-color: #{label.color}; color: #{text_color}" ) +
%(title="#{escape_once(label.description)}" data-container="body">) + %(title="#{escape_once(label.description)}" data-container="body">) +
%(#{escape_once(label.name)}#{label_suffix}</span>) %(#{escape_once(label.name)}#{label_suffix}</span>)
......
...@@ -116,6 +116,7 @@ module ProjectsHelper ...@@ -116,6 +116,7 @@ module ProjectsHelper
def last_push_event def last_push_event
return unless current_user return unless current_user
return current_user.recent_push unless @project
project_ids = [@project.id] project_ids = [@project.id]
if fork = current_user.fork_of(@project) if fork = current_user.fork_of(@project)
......
module RssHelper module RssHelper
def rss_url_options def rss_url_options
{ format: :atom, private_token: current_user.try(:private_token) } { format: :atom, rss_token: current_user.try(:rss_token) }
end end
end end
class BaseMailer < ActionMailer::Base class BaseMailer < ActionMailer::Base
around_action :render_with_default_locale
helper ApplicationHelper helper ApplicationHelper
helper MarkupHelper helper MarkupHelper
...@@ -14,6 +16,10 @@ class BaseMailer < ActionMailer::Base ...@@ -14,6 +16,10 @@ class BaseMailer < ActionMailer::Base
private private
def render_with_default_locale(&block)
Gitlab::I18n.with_default_locale(&block)
end
def default_sender_address def default_sender_address
address = Mail::Address.new(Gitlab.config.gitlab.email_from) address = Mail::Address.new(Gitlab.config.gitlab.email_from)
address.display_name = Gitlab.config.gitlab.email_display_name address.display_name = Gitlab.config.gitlab.email_display_name
......
...@@ -33,14 +33,4 @@ module NoteOnDiff ...@@ -33,14 +33,4 @@ module NoteOnDiff
def created_at_diff?(diff_refs) def created_at_diff?(diff_refs)
false false
end end
private
def noteable_diff_refs
if noteable.respond_to?(:diff_sha_refs)
noteable.diff_sha_refs
else
noteable.diff_refs
end
end
end end
...@@ -63,7 +63,7 @@ class DiffNote < Note ...@@ -63,7 +63,7 @@ class DiffNote < Note
return false unless supported? return false unless supported?
return true if for_commit? return true if for_commit?
diff_refs ||= noteable_diff_refs diff_refs ||= noteable.diff_refs
self.position.diff_refs == diff_refs self.position.diff_refs == diff_refs
end end
...@@ -99,7 +99,7 @@ class DiffNote < Note ...@@ -99,7 +99,7 @@ class DiffNote < Note
self.project, self.project,
nil, nil,
old_diff_refs: self.position.diff_refs, old_diff_refs: self.position.diff_refs,
new_diff_refs: noteable_diff_refs, new_diff_refs: noteable.diff_refs,
paths: self.position.paths paths: self.position.paths
).execute(self) ).execute(self)
end end
......
...@@ -2,6 +2,6 @@ class ServiceHook < WebHook ...@@ -2,6 +2,6 @@ class ServiceHook < WebHook
belongs_to :service belongs_to :service
def execute(data) def execute(data)
super(data, 'service_hook') WebHookService.new(self, data, 'service_hook').execute
end end
end end
...@@ -3,8 +3,4 @@ class SystemHook < WebHook ...@@ -3,8 +3,4 @@ class SystemHook < WebHook
default_value_for :push_events, false default_value_for :push_events, false
default_value_for :repository_update_events, true default_value_for :repository_update_events, true
def async_execute(data, hook_name)
Sidekiq::Client.enqueue(SystemHookWorker, id, data, hook_name)
end
end end
class WebHook < ActiveRecord::Base class WebHook < ActiveRecord::Base
include Sortable include Sortable
include HTTParty
default_value_for :push_events, true default_value_for :push_events, true
default_value_for :issues_events, false default_value_for :issues_events, false
...@@ -13,52 +12,18 @@ class WebHook < ActiveRecord::Base ...@@ -13,52 +12,18 @@ class WebHook < ActiveRecord::Base
default_value_for :repository_update_events, false default_value_for :repository_update_events, false
default_value_for :enable_ssl_verification, true default_value_for :enable_ssl_verification, true
has_many :web_hook_logs, dependent: :destroy
scope :push_hooks, -> { where(push_events: true) } scope :push_hooks, -> { where(push_events: true) }
scope :tag_push_hooks, -> { where(tag_push_events: true) } scope :tag_push_hooks, -> { where(tag_push_events: true) }
# HTTParty timeout
default_timeout Gitlab.config.gitlab.webhook_timeout
validates :url, presence: true, url: true validates :url, presence: true, url: true
def execute(data, hook_name) def execute(data, hook_name)
parsed_url = URI.parse(url) WebHookService.new(self, data, hook_name).execute
if parsed_url.userinfo.blank?
response = WebHook.post(url,
body: data.to_json,
headers: build_headers(hook_name),
verify: enable_ssl_verification)
else
post_url = url.gsub("#{parsed_url.userinfo}@", '')
auth = {
username: CGI.unescape(parsed_url.user),
password: CGI.unescape(parsed_url.password)
}
response = WebHook.post(post_url,
body: data.to_json,
headers: build_headers(hook_name),
verify: enable_ssl_verification,
basic_auth: auth)
end
[response.code, response.to_s]
rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
logger.error("WebHook Error => #{e}")
[false, e.to_s]
end end
def async_execute(data, hook_name) def async_execute(data, hook_name)
Sidekiq::Client.enqueue(ProjectWebHookWorker, id, data, hook_name) WebHookService.new(self, data, hook_name).async_execute
end
private
def build_headers(hook_name)
headers = {
'Content-Type' => 'application/json',
'X-Gitlab-Event' => hook_name.singularize.titleize
}
headers['X-Gitlab-Token'] = token if token.present?
headers
end end
end end
class WebHookLog < ActiveRecord::Base
belongs_to :web_hook
serialize :request_headers, Hash
serialize :request_data, Hash
serialize :response_headers, Hash
validates :web_hook, presence: true
def success?
response_status =~ /^2/
end
end
...@@ -133,6 +133,10 @@ class Label < ActiveRecord::Base ...@@ -133,6 +133,10 @@ class Label < ActiveRecord::Base
template template
end end
def color
super || DEFAULT_COLOR
end
def text_color def text_color
LabelsHelper.text_color_for_bg(self.color) LabelsHelper.text_color_for_bg(self.color)
end end
......
...@@ -61,7 +61,7 @@ class LegacyDiffNote < Note ...@@ -61,7 +61,7 @@ class LegacyDiffNote < Note
return true if for_commit? return true if for_commit?
return true unless diff_line return true unless diff_line
return false unless noteable return false unless noteable
return false if diff_refs && diff_refs != noteable_diff_refs return false if diff_refs && diff_refs != noteable.diff_refs
noteable_diff = find_noteable_diff noteable_diff = find_noteable_diff
......
...@@ -245,19 +245,6 @@ class MergeRequest < ActiveRecord::Base ...@@ -245,19 +245,6 @@ class MergeRequest < ActiveRecord::Base
end end
end end
# MRs created before 8.4 don't store a MergeRequestDiff#base_commit_sha,
# but we need to get a commit for the "View file @ ..." link by deleted files,
# so we find the likely one if we can't get the actual one.
# This will not be the actual base commit if the target branch was merged into
# the source branch after the merge request was created, but it is good enough
# for the specific purpose of linking to a commit.
# It is not good enough for use in `Gitlab::Git::DiffRefs`, which needs the
# true base commit, so we can't simply have `#diff_base_commit` fall back on
# this method.
def likely_diff_base_commit
first_commit.try(:parent) || first_commit
end
def diff_start_commit def diff_start_commit
if persisted? if persisted?
merge_request_diff.start_commit merge_request_diff.start_commit
...@@ -322,21 +309,14 @@ class MergeRequest < ActiveRecord::Base ...@@ -322,21 +309,14 @@ class MergeRequest < ActiveRecord::Base
end end
def diff_refs def diff_refs
return unless diff_start_commit || diff_base_commit if persisted?
Gitlab::Diff::DiffRefs.new(
base_sha: diff_base_sha,
start_sha: diff_start_sha,
head_sha: diff_head_sha
)
end
# Return diff_refs instance trying to not touch the git repository
def diff_sha_refs
if merge_request_diff && merge_request_diff.diff_refs_by_sha?
merge_request_diff.diff_refs merge_request_diff.diff_refs
else else
diff_refs Gitlab::Diff::DiffRefs.new(
base_sha: diff_base_sha,
start_sha: diff_start_sha,
head_sha: diff_head_sha
)
end end
end end
...@@ -870,7 +850,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -870,7 +850,7 @@ class MergeRequest < ActiveRecord::Base
end end
def has_complete_diff_refs? def has_complete_diff_refs?
diff_sha_refs && diff_sha_refs.complete? diff_refs && diff_refs.complete?
end end
def update_diff_notes_positions(old_diff_refs:, new_diff_refs:, current_user: nil) def update_diff_notes_positions(old_diff_refs:, new_diff_refs:, current_user: nil)
......
...@@ -150,6 +150,29 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -150,6 +150,29 @@ class MergeRequestDiff < ActiveRecord::Base
) )
end end
# MRs created before 8.4 don't store their true diff refs (start and base),
# but we need to get a commit SHA for the "View file @ ..." link by a file,
# so we use an approximation of the diff refs if we can't get the actual one.
#
# These will not be the actual diff refs if the target branch was merged into
# the source branch after the merge request was created, but it is good enough
# for the specific purpose of linking to a commit.
#
# It is not good enough for highlighting diffs, so we can't simply pass
# these as `diff_refs.`
def fallback_diff_refs
real_refs = diff_refs
return real_refs if real_refs
likely_base_commit_sha = (first_commit&.parent || first_commit)&.sha
Gitlab::Diff::DiffRefs.new(
base_sha: likely_base_commit_sha,
start_sha: safe_start_commit_sha,
head_sha: head_commit_sha
)
end
def diff_refs_by_sha? def diff_refs_by_sha?
base_commit_sha? && head_commit_sha? && start_commit_sha? base_commit_sha? && head_commit_sha? && start_commit_sha?
end end
......
...@@ -205,8 +205,8 @@ class Project < ActiveRecord::Base ...@@ -205,8 +205,8 @@ class Project < ActiveRecord::Base
presence: true, presence: true,
dynamic_path: true, dynamic_path: true,
length: { maximum: 255 }, length: { maximum: 255 },
format: { with: Gitlab::Regex.project_path_format_regex, format: { with: Gitlab::PathRegex.project_path_format_regex,
message: Gitlab::Regex.project_path_regex_message }, message: Gitlab::PathRegex.project_path_format_message },
uniqueness: { scope: :namespace_id } uniqueness: { scope: :namespace_id }
validates :namespace, presence: true validates :namespace, presence: true
...@@ -380,11 +380,9 @@ class Project < ActiveRecord::Base ...@@ -380,11 +380,9 @@ class Project < ActiveRecord::Base
end end
def reference_pattern def reference_pattern
name_pattern = Gitlab::Regex::FULL_NAMESPACE_REGEX_STR
%r{ %r{
((?<namespace>#{name_pattern})\/)? ((?<namespace>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX})\/)?
(?<project>#{name_pattern}) (?<project>#{Gitlab::PathRegex::PROJECT_PATH_FORMAT_REGEX})
}x }x
end end
......
...@@ -2,9 +2,10 @@ class JiraService < IssueTrackerService ...@@ -2,9 +2,10 @@ class JiraService < IssueTrackerService
include Gitlab::Routing.url_helpers include Gitlab::Routing.url_helpers
validates :url, url: true, presence: true, if: :activated? validates :url, url: true, presence: true, if: :activated?
validates :api_url, url: true, allow_blank: true
validates :project_key, presence: true, if: :activated? validates :project_key, presence: true, if: :activated?
prop_accessor :username, :password, :url, :project_key, prop_accessor :username, :password, :url, :api_url, :project_key,
:jira_issue_transition_id, :title, :description :jira_issue_transition_id, :title, :description
before_update :reset_password before_update :reset_password
...@@ -25,20 +26,18 @@ class JiraService < IssueTrackerService ...@@ -25,20 +26,18 @@ class JiraService < IssueTrackerService
super do super do
self.properties = { self.properties = {
title: issues_tracker['title'], title: issues_tracker['title'],
url: issues_tracker['url'] url: issues_tracker['url'],
api_url: issues_tracker['api_url']
} }
end end
end end
def reset_password def reset_password
# don't reset the password if a new one is provided self.password = nil if reset_password?
if url_changed? && !password_touched?
self.password = nil
end
end end
def options def options
url = URI.parse(self.url) url = URI.parse(client_url)
{ {
username: self.username, username: self.username,
...@@ -87,7 +86,8 @@ class JiraService < IssueTrackerService ...@@ -87,7 +86,8 @@ class JiraService < IssueTrackerService
def fields def fields
[ [
{ type: 'text', name: 'url', title: 'URL', placeholder: 'https://jira.example.com' }, { type: 'text', name: 'url', title: 'Web URL', placeholder: 'https://jira.example.com' },
{ type: 'text', name: 'api_url', title: 'JIRA API URL', placeholder: 'If different from Web URL' },
{ type: 'text', name: 'project_key', placeholder: 'Project Key' }, { type: 'text', name: 'project_key', placeholder: 'Project Key' },
{ type: 'text', name: 'username', placeholder: '' }, { type: 'text', name: 'username', placeholder: '' },
{ type: 'password', name: 'password', placeholder: '' }, { type: 'password', name: 'password', placeholder: '' },
...@@ -186,7 +186,7 @@ class JiraService < IssueTrackerService ...@@ -186,7 +186,7 @@ class JiraService < IssueTrackerService
end end
def test_settings def test_settings
return unless url.present? return unless client_url.present?
# Test settings by getting the project # Test settings by getting the project
jira_request { jira_project.present? } jira_request { jira_project.present? }
end end
...@@ -236,13 +236,13 @@ class JiraService < IssueTrackerService ...@@ -236,13 +236,13 @@ class JiraService < IssueTrackerService
end end
def send_message(issue, message, remote_link_props) def send_message(issue, message, remote_link_props)
return unless url.present? return unless client_url.present?
jira_request do jira_request do
if issue.comments.build.save!(body: message) if issue.comments.build.save!(body: message)
remote_link = issue.remotelink.build remote_link = issue.remotelink.build
remote_link.save!(remote_link_props) remote_link.save!(remote_link_props)
result_message = "#{self.class.name} SUCCESS: Successfully posted to #{url}." result_message = "#{self.class.name} SUCCESS: Successfully posted to #{client_url}."
end end
Rails.logger.info(result_message) Rails.logger.info(result_message)
...@@ -295,7 +295,20 @@ class JiraService < IssueTrackerService ...@@ -295,7 +295,20 @@ class JiraService < IssueTrackerService
yield yield
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED, URI::InvalidURIError, JIRA::HTTPError, OpenSSL::SSL::SSLError => e rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED, URI::InvalidURIError, JIRA::HTTPError, OpenSSL::SSL::SSLError => e
Rails.logger.info "#{self.class.name} Send message ERROR: #{url} - #{e.message}" Rails.logger.info "#{self.class.name} Send message ERROR: #{client_url} - #{e.message}"
nil nil
end end
def client_url
api_url.present? ? api_url : url
end
def reset_password?
# don't reset the password if a new one is provided
return false if password_touched?
return true if api_url_changed?
return false if api_url.present?
url_changed?
end
end end
...@@ -77,6 +77,14 @@ class KubernetesService < DeploymentService ...@@ -77,6 +77,14 @@ class KubernetesService < DeploymentService
] ]
end end
def actual_namespace
if namespace.present?
namespace
else
default_namespace
end
end
# Check we can connect to the Kubernetes API # Check we can connect to the Kubernetes API
def test(*args) def test(*args)
kubeclient = build_kubeclient! kubeclient = build_kubeclient!
...@@ -91,7 +99,7 @@ class KubernetesService < DeploymentService ...@@ -91,7 +99,7 @@ class KubernetesService < DeploymentService
variables = [ variables = [
{ key: 'KUBE_URL', value: api_url, public: true }, { key: 'KUBE_URL', value: api_url, public: true },
{ key: 'KUBE_TOKEN', value: token, public: false }, { key: 'KUBE_TOKEN', value: token, public: false },
{ key: 'KUBE_NAMESPACE', value: namespace_variable, public: true } { key: 'KUBE_NAMESPACE', value: actual_namespace, public: true }
] ]
if ca_pem.present? if ca_pem.present?
...@@ -110,7 +118,7 @@ class KubernetesService < DeploymentService ...@@ -110,7 +118,7 @@ class KubernetesService < DeploymentService
with_reactive_cache do |data| with_reactive_cache do |data|
pods = data.fetch(:pods, nil) pods = data.fetch(:pods, nil)
filter_pods(pods, app: environment.slug). filter_pods(pods, app: environment.slug).
flat_map { |pod| terminals_for_pod(api_url, namespace, pod) }. flat_map { |pod| terminals_for_pod(api_url, actual_namespace, pod) }.
each { |terminal| add_terminal_auth(terminal, terminal_auth) } each { |terminal| add_terminal_auth(terminal, terminal_auth) }
end end
end end
...@@ -124,7 +132,7 @@ class KubernetesService < DeploymentService ...@@ -124,7 +132,7 @@ class KubernetesService < DeploymentService
# Store as hashes, rather than as third-party types # Store as hashes, rather than as third-party types
pods = begin pods = begin
kubeclient.get_pods(namespace: namespace).as_json kubeclient.get_pods(namespace: actual_namespace).as_json
rescue KubeException => err rescue KubeException => err
raise err unless err.error_code == 404 raise err unless err.error_code == 404
[] []
...@@ -142,20 +150,12 @@ class KubernetesService < DeploymentService ...@@ -142,20 +150,12 @@ class KubernetesService < DeploymentService
default_namespace || TEMPLATE_PLACEHOLDER default_namespace || TEMPLATE_PLACEHOLDER
end end
def namespace_variable
if namespace.present?
namespace
else
default_namespace
end
end
def default_namespace def default_namespace
"#{project.path}-#{project.id}" if project.present? "#{project.path}-#{project.id}" if project.present?
end end
def build_kubeclient!(api_path: 'api', api_version: 'v1') def build_kubeclient!(api_path: 'api', api_version: 'v1')
raise "Incomplete settings" unless api_url && namespace && token raise "Incomplete settings" unless api_url && actual_namespace && token
::Kubeclient::Client.new( ::Kubeclient::Client.new(
join_api_url(api_path), join_api_url(api_path),
......
...@@ -15,6 +15,7 @@ class User < ActiveRecord::Base ...@@ -15,6 +15,7 @@ class User < ActiveRecord::Base
add_authentication_token_field :authentication_token add_authentication_token_field :authentication_token
add_authentication_token_field :incoming_email_token add_authentication_token_field :incoming_email_token
add_authentication_token_field :rss_token
default_value_for :admin, false default_value_for :admin, false
default_value_for(:external) { current_application_settings.user_default_external } default_value_for(:external) { current_application_settings.user_default_external }
...@@ -367,7 +368,7 @@ class User < ActiveRecord::Base ...@@ -367,7 +368,7 @@ class User < ActiveRecord::Base
def reference_pattern def reference_pattern
%r{ %r{
#{Regexp.escape(reference_prefix)} #{Regexp.escape(reference_prefix)}
(?<user>#{Gitlab::Regex::FULL_NAMESPACE_REGEX_STR}) (?<user>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX})
}x }x
end end
...@@ -918,13 +919,13 @@ class User < ActiveRecord::Base ...@@ -918,13 +919,13 @@ class User < ActiveRecord::Base
end end
def assigned_open_merge_requests_count(force: false) def assigned_open_merge_requests_count(force: false)
Rails.cache.fetch(['users', id, 'assigned_open_merge_requests_count'], force: force) do Rails.cache.fetch(['users', id, 'assigned_open_merge_requests_count'], force: force, expires_in: 20.minutes) do
MergeRequestsFinder.new(self, assignee_id: self.id, state: 'opened').execute.count MergeRequestsFinder.new(self, assignee_id: self.id, state: 'opened').execute.count
end end
end end
def assigned_open_issues_count(force: false) def assigned_open_issues_count(force: false)
Rails.cache.fetch(['users', id, 'assigned_open_issues_count'], force: force) do Rails.cache.fetch(['users', id, 'assigned_open_issues_count'], force: force, expires_in: 20.minutes) do
IssuesFinder.new(self, assignee_id: self.id, state: 'opened').execute.count IssuesFinder.new(self, assignee_id: self.id, state: 'opened').execute.count
end end
end end
...@@ -1004,6 +1005,13 @@ class User < ActiveRecord::Base ...@@ -1004,6 +1005,13 @@ class User < ActiveRecord::Base
save save
end end
# each existing user needs to have an `rss_token`.
# we do this on read since migrating all existing users is not a feasible
# solution.
def rss_token
ensure_rss_token!
end
protected protected
# override, from Devise::Validatable # override, from Devise::Validatable
......
...@@ -28,6 +28,7 @@ module Issues ...@@ -28,6 +28,7 @@ module Issues
notification_service.close_issue(issue, current_user) if notifications notification_service.close_issue(issue, current_user) if notifications
todo_service.close_issue(issue, current_user) todo_service.close_issue(issue, current_user)
execute_hooks(issue, 'close') execute_hooks(issue, 'close')
invalidate_cache_counts(issue.assignees, issue)
end end
issue issue
......
...@@ -8,6 +8,7 @@ module Issues ...@@ -8,6 +8,7 @@ module Issues
create_note(issue) create_note(issue)
notification_service.reopen_issue(issue, current_user) notification_service.reopen_issue(issue, current_user)
execute_hooks(issue, 'reopen') execute_hooks(issue, 'reopen')
invalidate_cache_counts(issue.assignees, issue)
end end
issue issue
......
...@@ -13,6 +13,7 @@ module MergeRequests ...@@ -13,6 +13,7 @@ module MergeRequests
notification_service.close_mr(merge_request, current_user) notification_service.close_mr(merge_request, current_user)
todo_service.close_merge_request(merge_request, current_user) todo_service.close_merge_request(merge_request, current_user)
execute_hooks(merge_request, 'close') execute_hooks(merge_request, 'close')
invalidate_cache_counts(merge_request.assignees, merge_request)
end end
merge_request merge_request
......
...@@ -13,6 +13,7 @@ module MergeRequests ...@@ -13,6 +13,7 @@ module MergeRequests
create_note(merge_request) create_note(merge_request)
notification_service.merge_mr(merge_request, current_user) notification_service.merge_mr(merge_request, current_user)
execute_hooks(merge_request, 'merge') execute_hooks(merge_request, 'merge')
invalidate_cache_counts(merge_request.assignees, merge_request)
end end
private private
......
...@@ -10,6 +10,7 @@ module MergeRequests ...@@ -10,6 +10,7 @@ module MergeRequests
execute_hooks(merge_request, 'reopen') execute_hooks(merge_request, 'reopen')
merge_request.reload_diff(current_user) merge_request.reload_diff(current_user)
merge_request.mark_as_unchecked merge_request.mark_as_unchecked
invalidate_cache_counts(merge_request.assignees, merge_request)
end end
merge_request merge_request
......
class WebHookService
class InternalErrorResponse
attr_reader :body, :headers, :code
def initialize
@headers = HTTParty::Response::Headers.new({})
@body = ''
@code = 'internal error'
end
end
include HTTParty
# HTTParty timeout
default_timeout Gitlab.config.gitlab.webhook_timeout
attr_accessor :hook, :data, :hook_name
def initialize(hook, data, hook_name)
@hook = hook
@data = data
@hook_name = hook_name
end
def execute
start_time = Time.now
response = if parsed_url.userinfo.blank?
make_request(hook.url)
else
make_request_with_auth
end
log_execution(
trigger: hook_name,
url: hook.url,
request_data: data,
response: response,
execution_duration: Time.now - start_time
)
[response.code, response.to_s]
rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
log_execution(
trigger: hook_name,
url: hook.url,
request_data: data,
response: InternalErrorResponse.new,
execution_duration: Time.now - start_time,
error_message: e.to_s
)
Rails.logger.error("WebHook Error => #{e}")
[nil, e.to_s]
end
def async_execute
Sidekiq::Client.enqueue(WebHookWorker, hook.id, data, hook_name)
end
private
def parsed_url
@parsed_url ||= URI.parse(hook.url)
end
def make_request(url, basic_auth = false)
self.class.post(url,
body: data.to_json,
headers: build_headers(hook_name),
verify: hook.enable_ssl_verification,
basic_auth: basic_auth)
end
def make_request_with_auth
post_url = hook.url.gsub("#{parsed_url.userinfo}@", '')
basic_auth = {
username: CGI.unescape(parsed_url.user),
password: CGI.unescape(parsed_url.password)
}
make_request(post_url, basic_auth)
end
def log_execution(trigger:, url:, request_data:, response:, execution_duration:, error_message: nil)
# logging for ServiceHook's is not available
return if hook.is_a?(ServiceHook)
WebHookLog.create(
web_hook: hook,
trigger: trigger,
url: url,
execution_duration: execution_duration,
request_headers: build_headers(hook_name),
request_data: request_data,
response_headers: format_response_headers(response),
response_body: response.body,
response_status: response.code,
internal_error_message: error_message
)
end
def build_headers(hook_name)
@headers ||= begin
{
'Content-Type' => 'application/json',
'X-Gitlab-Event' => hook_name.singularize.titleize
}.tap do |hash|
hash['X-Gitlab-Token'] = hook.token if hook.token.present?
end
end
end
# Make response headers more stylish
# Net::HTTPHeader has downcased hash with arrays: { 'content-type' => ['text/html; charset=utf-8'] }
# This method format response to capitalized hash with strings: { 'Content-Type' => 'text/html; charset=utf-8' }
def format_response_headers(response)
response.headers.each_capitalized.to_h
end
end
...@@ -3,16 +3,20 @@ ...@@ -3,16 +3,20 @@
# Custom validator for GitLab path values. # Custom validator for GitLab path values.
# These paths are assigned to `Namespace` (& `Group` as a subclass) & `Project` # These paths are assigned to `Namespace` (& `Group` as a subclass) & `Project`
# #
# Values are checked for formatting and exclusion from a list of reserved path # Values are checked for formatting and exclusion from a list of illegal path
# names. # names.
class DynamicPathValidator < ActiveModel::EachValidator class DynamicPathValidator < ActiveModel::EachValidator
class << self class << self
def valid_namespace_path?(path) def valid_user_path?(path)
"#{path}/" =~ Gitlab::Regex.full_namespace_path_regex "#{path}/" =~ Gitlab::PathRegex.root_namespace_path_regex
end
def valid_group_path?(path)
"#{path}/" =~ Gitlab::PathRegex.full_namespace_path_regex
end end
def valid_project_path?(path) def valid_project_path?(path)
"#{path}/" =~ Gitlab::Regex.full_project_path_regex "#{path}/" =~ Gitlab::PathRegex.full_project_path_regex
end end
end end
...@@ -24,14 +28,16 @@ class DynamicPathValidator < ActiveModel::EachValidator ...@@ -24,14 +28,16 @@ class DynamicPathValidator < ActiveModel::EachValidator
case record case record
when Project when Project
self.class.valid_project_path?(full_path) self.class.valid_project_path?(full_path)
else when Group
self.class.valid_namespace_path?(full_path) self.class.valid_group_path?(full_path)
else # User or non-Group Namespace
self.class.valid_user_path?(full_path)
end end
end end
def validate_each(record, attribute, value) def validate_each(record, attribute, value)
unless value =~ Gitlab::Regex.namespace_regex unless value =~ Gitlab::PathRegex.namespace_format_regex
record.errors.add(attribute, Gitlab::Regex.namespace_regex_message) record.errors.add(attribute, Gitlab::PathRegex.namespace_format_message)
return return
end end
......
.row.prepend-top-default.append-bottom-default
.col-lg-3
%h4.prepend-top-0
Recent Deliveries
%p When an event in GitLab triggers a webhook, you can use the request details to figure out if something went wrong.
.col-lg-9
- if hook_logs.any?
%table.table
%thead
%tr
%th Status
%th Trigger
%th URL
%th Elapsed time
%th Request time
%th
- hook_logs.each do |hook_log|
%tr
%td
= render partial: 'shared/hook_logs/status_label', locals: { hook_log: hook_log }
%td.hidden-xs
%span.label.label-gray.deploy-project-label
= hook_log.trigger.singularize.titleize
%td
= truncate(hook_log.url, length: 50)
%td.light
#{number_with_precision(hook_log.execution_duration, precision: 2)} ms
%td.light
= time_ago_with_tooltip(hook_log.created_at)
%td
= link_to 'View details', admin_hook_hook_log_path(hook, hook_log)
= paginate hook_logs, theme: 'gitlab'
- else
.settings-message.text-center
You don't have any webhooks deliveries
- page_title 'Request details'
%h3.page-title
Request details
%hr
= link_to 'Resend Request', retry_admin_hook_hook_log_path(@hook, @hook_log), class: "btn btn-default pull-right prepend-left-10"
= render partial: 'shared/hook_logs/content', locals: { hook_log: @hook_log }
...@@ -12,3 +12,9 @@ ...@@ -12,3 +12,9 @@
= render partial: 'form', locals: { form: f, hook: @hook } = render partial: 'form', locals: { form: f, hook: @hook }
.form-actions .form-actions
= f.submit 'Save changes', class: 'btn btn-create' = f.submit 'Save changes', class: 'btn btn-create'
= link_to 'Test hook', test_admin_hook_path(@hook), class: 'btn btn-default'
= link_to 'Remove', admin_hook_path(@hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' }
%hr
= render partial: 'admin/hook_logs/index', locals: { hook: @hook, hook_logs: @hook_logs }
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
%ul.content-list %ul.content-list
- profiles.each do |profile| - profiles.each do |profile|
%li %li
= link_to profile.time.to_s(:long), admin_requests_profile_path(profile), data: {no_turbolink: true} = link_to profile.time.to_s(:long), admin_requests_profile_path(profile)
- else - else
%p %p
No profiles found No profiles found
.hidden-xs
= render "events/event_last_push", event: @last_push
.nav-block.activities .nav-block.activities
.controls .controls
= link_to dashboard_projects_path(rss_url_options), class: 'btn rss-btn has-tooltip', title: 'Subscribe' do = link_to dashboard_projects_path(rss_url_options), class: 'btn rss-btn has-tooltip', title: 'Subscribe' do
......
- @no_container = true
= content_for :meta_tags do = content_for :meta_tags do
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity") = auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
- page_title "Activity" - page_title "Activity"
- header_title "Activity", activity_dashboard_path - header_title "Activity", activity_dashboard_path
= render 'dashboard/activity_head' .hidden-xs
= render "projects/last_push"
%div{ class: container_class }
= render 'dashboard/activity_head'
%section.activities %section.activities
= render 'activities' = render 'activities'
- @no_container = true
= content_for :meta_tags do = content_for :meta_tags do
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity") = auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
- page_title "Projects" - page_title "Projects"
- header_title "Projects", dashboard_projects_path - header_title "Projects", dashboard_projects_path
- unless show_user_callout? = render "projects/last_push"
= render 'shared/user_callout'
- if @projects.any? || params[:name] %div{ class: container_class }
= render 'dashboard/projects_head' - unless show_user_callout?
= render 'shared/user_callout'
- if @last_push - if @projects.any? || params[:name]
= render "events/event_last_push", event: @last_push = render 'dashboard/projects_head'
- if @projects.any? || params[:name] - if @projects.any? || params[:name]
= render 'projects' = render 'projects'
- else - else
= render "zero_authorized_projects" = render "zero_authorized_projects"
- @no_container = true
- page_title "Starred Projects" - page_title "Starred Projects"
- header_title "Projects", dashboard_projects_path - header_title "Projects", dashboard_projects_path
= render 'dashboard/projects_head' = render "projects/last_push"
- if @last_push %div{ class: container_class }
= render "events/event_last_push", event: @last_push = render 'dashboard/projects_head'
- if @projects.any? || params[:filter_projects] - if @projects.any? || params[:filter_projects]
= render 'projects' = render 'projects'
- else - else
%h3 You don't have starred projects yet %h3 You don't have starred projects yet
%p.slead Visit project page and press on star icon and it will appear on this page. %p.slead Visit project page and press on star icon and it will appear on this page.
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
= f.text_field :name, class: "form-control top", required: true, title: "This field is required." = f.text_field :name, class: "form-control top", required: true, title: "This field is required."
.username.form-group .username.form-group
= f.label :username = f.label :username
= f.text_field :username, class: "form-control middle", pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_JS, required: true, title: 'Please create a username with only alphanumeric characters.' = f.text_field :username, class: "form-control middle", pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: 'Please create a username with only alphanumeric characters.'
%p.validation-error.hide Username is already taken. %p.validation-error.hide Username is already taken.
%p.validation-success.hide Username is available. %p.validation-success.hide Username is available.
%p.validation-pending.hide Checking username availability... %p.validation-pending.hide Checking username availability...
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
.diff-file.file-holder .diff-file.file-holder
.js-file-title.file-title .js-file-title.file-title
= render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_file.content_commit, project: discussion.project, url: discussion_path(discussion), show_toggle: false = render "projects/diffs/file_header", diff_file: diff_file, url: discussion_path(discussion), show_toggle: false
.diff-content.code.js-syntax-highlight .diff-content.code.js-syntax-highlight
%table %table
......
.discussion-notes .discussion-notes
%ul.notes{ data: { discussion_id: discussion.id } } %ul.notes{ data: { discussion_id: discussion.id } }
= render partial: "shared/notes/note", collection: discussion.notes, as: :note = render partial: "shared/notes/note", collection: discussion.notes, as: :note
.flash-container
- if current_user .flash-container
.discussion-reply-holder
.discussion-reply-holder
- if can_create_note?
- if discussion.potentially_resolvable? - if discussion.potentially_resolvable?
- line_type = local_assigns.fetch(:line_type, nil) - line_type = local_assigns.fetch(:line_type, nil)
...@@ -19,3 +20,10 @@ ...@@ -19,3 +20,10 @@
= render "discussions/jump_to_next", discussion: discussion = render "discussions/jump_to_next", discussion: discussion
- else - else
= link_to_reply_discussion(discussion) = link_to_reply_discussion(discussion)
- elsif !current_user
.disabled-comment.text-center
Please
= link_to "register", new_session_path(:user, redirect_to_referer: 'yes')
or
= link_to "sign in", new_session_path(:user, redirect_to_referer: 'yes')
to reply
- if show_last_push_widget?(event)
.row-content-block.clear-block.last-push-widget
.event-last-push
.event-last-push-text
%span You pushed to
= link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name), title: h(event.project.name) do
%strong= event.ref_name
%span at
%strong= link_to_project event.project
#{time_ago_with_tooltip(event.created_at)}
.pull-right
= link_to new_mr_path_from_push_event(event), title: "New merge request", class: "btn btn-info btn-sm" do
Create merge request
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
%span.pushed #{event.action_name} #{event.ref_type} %span.pushed #{event.action_name} #{event.ref_type}
%strong %strong
- commits_link = namespace_project_commits_path(project.namespace, project, event.ref_name) - commits_link = namespace_project_commits_path(project.namespace, project, event.ref_name)
= link_to_if project.repository.branch_exists?(event.ref_name), event.ref_name, commits_link = link_to_if project.repository.branch_exists?(event.ref_name), event.ref_name, commits_link, class: 'ref-name'
= render "events/event_scope", event: event = render "events/event_scope", event: event
......
.hidden-xs
= render "events/event_last_push", event: @last_push
.nav-block .nav-block
.controls .controls
= link_to group_path(@group, rss_url_options), class: 'btn rss-btn has-tooltip' , title: 'Subscribe' do = link_to group_path(@group, rss_url_options), class: 'btn rss-btn has-tooltip' , title: 'Subscribe' do
......
...@@ -12,3 +12,6 @@ ...@@ -12,3 +12,6 @@
= link_to activity_group_path(@group), title: 'Activity' do = link_to activity_group_path(@group), title: 'Activity' do
%span %span
Activity Activity
.hidden-xs
= render "projects/last_push"
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
= render 'groups/head' = render 'groups/head'
= render 'groups/home_panel' = render 'groups/home_panel'
.groups-header{ class: container_class } .groups-header{ class: container_class }
.top-area .top-area
= render 'groups/show_nav' = render 'groups/show_nav'
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
= link_to admin_broadcast_messages_path, title: 'Messages' do = link_to admin_broadcast_messages_path, title: 'Messages' do
%span %span
Messages Messages
= nav_link(controller: :hooks) do = nav_link(controller: [:hooks, :hook_logs]) do
= link_to admin_hooks_path, title: 'Hooks' do = link_to admin_hooks_path, title: 'Hooks' do
%span %span
System Hooks System Hooks
......
...@@ -27,40 +27,38 @@ ...@@ -27,40 +27,38 @@
%h4 #{pluralize @message.diffs_count, "changed file"}: %h4 #{pluralize @message.diffs_count, "changed file"}:
%ul %ul
- @message.diffs.each do |diff| - @message.diffs.each do |diff_file|
%li.file-stats %li.file-stats
%a{ href: "#{@message.target_url if @message.disable_diffs?}##{hexdigest(diff.file_path)}" } %a{ href: "#{@message.target_url if @message.disable_diffs?}##{hexdigest(diff_file.file_path)}" }
- if diff.deleted_file - if diff_file.deleted_file?
%span.deleted-file %span.deleted-file
&minus; &minus;
= diff.old_path = diff_file.old_path
- elsif diff.renamed_file - elsif diff_file.renamed_file?
= diff.old_path = diff_file.old_path
&rarr; &rarr;
= diff.new_path = diff_file.new_path
- elsif diff.new_file - elsif diff_file.new_file?
%span.new-file %span.new-file
&#43; &#43;
= diff.new_path = diff_file.new_path
- else - else
= diff.new_path = diff_file.new_path
- unless @message.disable_diffs? - unless @message.disable_diffs?
- diff_files = @message.diffs
- if @message.compare_timeout - if @message.compare_timeout
%h5 The diff was not included because it is too large. %h5 The diff was not included because it is too large.
- else - else
%h4 Changes: %h4 Changes:
- diff_files.each do |diff_file| - @message.diffs.each do |diff_file|
- file_hash = hexdigest(diff_file.file_path) - file_hash = hexdigest(diff_file.file_path)
%li{ id: file_hash } %li{ id: file_hash }
%a{ href: @message.target_url + "##{file_hash}" }< %a{ href: @message.target_url + "##{file_hash}" }<
- if diff_file.deleted_file - if diff_file.deleted_file?
%strong< %strong<
= diff_file.old_path = diff_file.old_path
deleted deleted
- elsif diff_file.renamed_file - elsif diff_file.renamed_file?
%strong< %strong<
= diff_file.old_path = diff_file.old_path
&rarr; &rarr;
......
...@@ -15,15 +15,15 @@ ...@@ -15,15 +15,15 @@
\ \
#{pluralize @message.diffs_count, "changed file"}: #{pluralize @message.diffs_count, "changed file"}:
\ \
- @message.diffs.each do |diff| - @message.diffs.each do |diff_file|
- if diff.deleted_file - if diff_file.deleted_file?
\- − #{diff.old_path} \- − #{diff_file.old_path}
- elsif diff.renamed_file - elsif diff_file.renamed_file?
\- #{diff.old_path}#{diff.new_path} \- #{diff_file.old_path}#{diff_file.new_path}
- elsif diff.new_file - elsif diff_file.new_file?
\- + #{diff.new_path} \- + #{diff_file.new_path}
- else - else
\- #{diff.new_path} \- #{diff_file.new_path}
- unless @message.disable_diffs? - unless @message.disable_diffs?
- if @message.compare_timeout - if @message.compare_timeout
\ \
...@@ -36,9 +36,9 @@ ...@@ -36,9 +36,9 @@
- @message.diffs.each do |diff_file| - @message.diffs.each do |diff_file|
\ \
\===================================== \=====================================
- if diff_file.deleted_file - if diff_file.deleted_file?
#{diff_file.old_path} deleted #{diff_file.old_path} deleted
- elsif diff_file.renamed_file - elsif diff_file.renamed_file?
#{diff_file.old_path}#{diff_file.new_path} #{diff_file.old_path}#{diff_file.new_path}
- else - else
= diff_file.new_path = diff_file.new_path
......
- name = label.parameterize
- attribute = name.underscore
.reset-action
%p.cgray
= label_tag name, label, class: "label-light"
= text_field_tag name, current_user.send(attribute), class: 'form-control', readonly: true, onclick: 'this.select()'
%p.help-block
= help_text
.prepend-top-default
= link_to button_label, [:reset, attribute, :profile], method: :put, data: { confirm: 'Are you sure?' }, class: 'btn btn-default private-token'
...@@ -8,35 +8,17 @@ ...@@ -8,35 +8,17 @@
.row.prepend-top-default .row.prepend-top-default
.col-lg-3.profile-settings-sidebar .col-lg-3.profile-settings-sidebar
%h4.prepend-top-0 %h4.prepend-top-0
= incoming_email_token_enabled? ? "Private Tokens" : "Private Token" Private Tokens
%p %p
Keep Keep these tokens secret, anyone with access to them can interact with
= incoming_email_token_enabled? ? "these tokens" : "this token" GitLab as if they were you.
secret, anyone with access to them can interact with GitLab as if they were you.
.col-lg-9.private-tokens-reset .col-lg-9.private-tokens-reset
.reset-action = render partial: 'reset_token', locals: { label: 'Private token', button_label: 'Reset private token', help_text: 'Your private token is used to access the API and Atom feeds without username/password authentication.' }
%p.cgray
- if current_user.private_token = render partial: 'reset_token', locals: { label: 'RSS token', button_label: 'Reset RSS token', help_text: 'Your RSS token is used to create urls for personalized RSS feeds.' }
= label_tag "private-token", "Private token", class: "label-light"
= text_field_tag "private-token", current_user.private_token, class: "form-control", readonly: true, onclick: "this.select()"
- else
%span You don't have one yet. Click generate to fix it.
%p.help-block
Your private token is used to access the API and Atom feeds without username/password authentication.
.prepend-top-default
- if current_user.private_token
= link_to 'Reset private token', reset_private_token_profile_path, method: :put, data: { confirm: "Are you sure?" }, class: "btn btn-default private-token"
- else
= f.submit 'Generate', class: "btn btn-default"
- if incoming_email_token_enabled? - if incoming_email_token_enabled?
.reset-action = render partial: 'reset_token', locals: { label: 'Incoming email token', button_label: 'Reset incoming email token', help_text: 'Your incoming email token is used to create new issues by email, and is included in your project-specific email addresses.' }
%p.cgray
= label_tag "incoming-email-token", "Incoming Email Token", class: 'label-light'
= text_field_tag "incoming-email-token", current_user.incoming_email_token, class: "form-control", readonly: true, onclick: "this.select()"
%p.help-block
Your incoming email token is used to create new issues by email, and is included in your project-specific email addresses.
.prepend-top-default
= link_to 'Reset incoming email token', reset_incoming_email_token_profile_path, method: :put, data: { confirm: "Are you sure?" }, class: "btn btn-default incoming-email-token"
%hr %hr
.row.prepend-top-default .row.prepend-top-default
......
- @no_container = true
%div{ class: container_class } %div{ class: container_class }
.nav-block.activity-filter-block.activities .nav-block.activity-filter-block.activities
.controls .controls
......
- if event = last_push_event - event = last_push_event
- if show_last_push_widget?(event) - if event && show_last_push_widget?(event)
.row-content-block.top-block.hidden-xs.white .row-content-block.top-block.hidden-xs.white
%div{ class: container_class } .event-last-push
.event-last-push .event-last-push-text
.event-last-push-text %span You pushed to
%span You pushed to %strong
= link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name, class: 'commit-sha') do = link_to event.ref_name, namespace_project_commits_path(event.project.namespace, event.project, event.ref_name), class: 'ref-name'
%strong= event.ref_name
- if @project && event.project != @project
%span at
%strong= link_to_project event.project
= clipboard_button(text: event.ref_name, class: 'btn-clipboard btn-transparent', title: 'Copy branch to clipboard')
#{time_ago_with_tooltip(event.created_at)}
.pull-right - if event.project != @project
= link_to new_mr_path_from_push_event(event), title: "New merge request", class: "btn btn-info btn-sm" do %span at
Create merge request %strong= link_to_project event.project
#{time_ago_with_tooltip(event.created_at)}
.pull-right
= link_to new_mr_path_from_push_event(event), title: "New merge request", class: "btn btn-info btn-sm" do
Create merge request
- @no_container = true
- page_title "Activity" - page_title "Activity"
= render "projects/head" = render "projects/head"
......
- @no_container = true - @no_container = true
- page_title @blob.path, @ref - page_title @blob.path, @ref
= render "projects/commits/head" = render "projects/commits/head"
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('blob') = page_specific_javascript_bundle_tag('blob')
%div{ class: container_class } = render 'projects/last_push'
= render 'projects/last_push'
%div{ class: container_class }
#tree-holder.tree-holder #tree-holder.tree-holder
= render 'blob', blob: @blob = render 'blob', blob: @blob
......
.diff-content.diff-wrap-lines - blob = diff_file.blob
-# Skip all non non-supported blobs
- return unless blob.respond_to?(:text?) .diff-content
- if diff_file.too_large? - if diff_file.too_large?
.nothing-here-block This diff could not be displayed because it is too large. .nothing-here-block This diff could not be displayed because it is too large.
- elsif blob.too_large? - elsif blob.too_large?
.nothing-here-block The file could not be displayed because it is too large. .nothing-here-block The file could not be displayed because it is too large.
- elsif blob.readable_text? - elsif blob.readable_text?
- if !project.repository.diffable?(blob) - if !diff_file.repository.diffable?(blob)
.nothing-here-block This diff was suppressed by a .gitattributes entry. .nothing-here-block This diff was suppressed by a .gitattributes entry.
- elsif diff_file.collapsed? - elsif diff_file.collapsed?
- url = url_for(params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path, file_identifier: diff_file.file_identifier)) - url = url_for(params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path, file_identifier: diff_file.file_identifier))
...@@ -15,20 +15,13 @@ ...@@ -15,20 +15,13 @@
%a.click-to-expand %a.click-to-expand
Click to expand it. Click to expand it.
- elsif diff_file.diff_lines.length > 0 - elsif diff_file.diff_lines.length > 0
- total_lines = 0 = render "projects/diffs/viewers/text", diff_file: diff_file
- if blob.lines.any?
- total_lines = blob.lines.last.chomp == '' ? blob.lines.size - 1 : blob.lines.size
- if diff_view == :parallel
= render "projects/diffs/parallel_view", diff_file: diff_file, total_lines: total_lines
- else
= render "projects/diffs/text_file", diff_file: diff_file, total_lines: total_lines
- else - else
- if diff_file.mode_changed? - if diff_file.mode_changed?
.nothing-here-block File mode changed .nothing-here-block File mode changed
- elsif diff_file.renamed_file - elsif diff_file.renamed_file?
.nothing-here-block File moved .nothing-here-block File moved
- elsif blob.image? - elsif blob.image?
- old_blob = diff_file.old_blob(diff_file.old_content_commit || @base_commit) = render "projects/diffs/viewers/image", diff_file: diff_file
= render "projects/diffs/image", diff_file: diff_file, old_file: old_blob, file: blob
- else - else
.nothing-here-block No preview for this file type .nothing-here-block No preview for this file type
...@@ -23,12 +23,4 @@ ...@@ -23,12 +23,4 @@
= render 'projects/diffs/warning', diff_files: diffs = render 'projects/diffs/warning', diff_files: diffs
.files{ data: { can_create_note: can_create_note } } .files{ data: { can_create_note: can_create_note } }
- diff_files.each_with_index do |diff_file| = render partial: 'projects/diffs/file', collection: diff_files, as: :diff_file, locals: { project: diffs.project, environment: environment }
- diff_commit = commit_for_diff(diff_file)
- blob = diff_file.blob(diff_commit)
- next unless blob
- blob.load_all_data!(diffs.project.repository) unless blob.too_large?
- file_hash = hexdigest(diff_file.file_path)
= render 'projects/diffs/file', file_hash: file_hash, project: diffs.project,
diff_file: diff_file, diff_commit: diff_commit, blob: blob, environment: environment
- environment = local_assigns.fetch(:environment, nil) - environment = local_assigns.fetch(:environment, nil)
.diff-file.file-holder{ id: file_hash, data: diff_file_html_data(project, diff_file.file_path, diff_commit.id) } - file_hash = hexdigest(diff_file.file_path)
.diff-file.file-holder{ id: file_hash, data: diff_file_html_data(project, diff_file.file_path, diff_file.content_sha) }
.js-file-title.file-title-flex-parent .js-file-title.file-title-flex-parent
.file-header-content .file-header-content
= render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_commit, project: project, url: "##{file_hash}" = render "projects/diffs/file_header", diff_file: diff_file, url: "##{file_hash}"
- unless diff_file.submodule? - unless diff_file.submodule?
- blob = diff_file.blob
.file-actions.hidden-xs .file-actions.hidden-xs
- if blob.readable_text? - if blob.readable_text?
= link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip', title: "Toggle comments for this file", disabled: @diff_notes_disabled do = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip', title: "Toggle comments for this file", disabled: @diff_notes_disabled do
...@@ -15,9 +17,9 @@ ...@@ -15,9 +17,9 @@
= edit_blob_link(@merge_request.source_project, @merge_request.source_branch, diff_file.new_path, = edit_blob_link(@merge_request.source_project, @merge_request.source_branch, diff_file.new_path,
blob: blob, link_opts: link_opts) blob: blob, link_opts: link_opts)
= view_file_button(diff_commit.id, diff_file.new_path, project) = view_file_button(diff_file.content_sha, diff_file.file_path, project)
= view_on_environment_button(diff_commit.id, diff_file.new_path, environment) if environment = view_on_environment_button(diff_file.content_sha, diff_file.file_path, environment) if environment
= render 'projects/fork_suggestion' = render 'projects/fork_suggestion'
= render 'projects/diffs/content', diff_file: diff_file, diff_commit: diff_commit, blob: blob, project: project = render 'projects/diffs/content', diff_file: diff_file
...@@ -3,19 +3,20 @@ ...@@ -3,19 +3,20 @@
- if show_toggle - if show_toggle
%i.fa.diff-toggle-caret.fa-fw %i.fa.diff-toggle-caret.fa-fw
- if defined?(blob) && blob && diff_file.submodule? - if diff_file.submodule?
- blob = diff_file.blob
%span %span
= icon('archive fw') = icon('archive fw')
%strong.file-title-name %strong.file-title-name
= submodule_link(blob, diff_commit.id, project.repository) = submodule_link(blob, diff_file.content_sha, diff_file.repository)
= copy_file_path_button(blob.path) = copy_file_path_button(blob.path)
- else - else
= conditional_link_to url.present?, url do = conditional_link_to url.present?, url do
= blob_icon diff_file.b_mode, diff_file.file_path = blob_icon diff_file.b_mode, diff_file.file_path
- if diff_file.renamed_file - if diff_file.renamed_file?
- old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path) - old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
%strong.file-title-name.has-tooltip{ data: { title: diff_file.old_path, container: 'body' } } %strong.file-title-name.has-tooltip{ data: { title: diff_file.old_path, container: 'body' } }
= old_path = old_path
...@@ -23,12 +24,13 @@ ...@@ -23,12 +24,13 @@
%strong.file-title-name.has-tooltip{ data: { title: diff_file.new_path, container: 'body' } } %strong.file-title-name.has-tooltip{ data: { title: diff_file.new_path, container: 'body' } }
= new_path = new_path
- else - else
%strong.file-title-name.has-tooltip{ data: { title: diff_file.new_path, container: 'body' } } %strong.file-title-name.has-tooltip{ data: { title: diff_file.file_path, container: 'body' } }
= diff_file.new_path = diff_file.file_path
- if diff_file.deleted_file
- if diff_file.deleted_file?
deleted deleted
= copy_file_path_button(diff_file.new_path) = copy_file_path_button(diff_file.file_path)
- if diff_file.mode_changed? - if diff_file.mode_changed?
%small %small
......
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
- 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
- if !diff_file.new_file && !diff_file.deleted_file && diff_file.diff_lines.any? - if !diff_file.new_file? && !diff_file.deleted_file? && diff_file.diff_lines.any?
- last_line = diff_file.diff_lines.last - last_line = diff_file.diff_lines.last
- if last_line.new_pos < total_lines - if last_line.new_pos < total_lines
%tr.line_holder.parallel %tr.line_holder.parallel
......
...@@ -12,19 +12,19 @@ ...@@ -12,19 +12,19 @@
- diff_files.each do |diff_file| - diff_files.each do |diff_file|
- file_hash = hexdigest(diff_file.file_path) - file_hash = hexdigest(diff_file.file_path)
%li %li
- if diff_file.deleted_file - if diff_file.deleted_file?
%span.deleted-file %span.deleted-file
%a{ href: "##{file_hash}" } %a{ href: "##{file_hash}" }
%i.fa.fa-minus %i.fa.fa-minus
= diff_file.old_path = diff_file.old_path
- elsif diff_file.renamed_file - elsif diff_file.renamed_file?
%span.renamed-file %span.renamed-file
%a{ href: "##{file_hash}" } %a{ href: "##{file_hash}" }
%i.fa.fa-minus %i.fa.fa-minus
= diff_file.old_path = diff_file.old_path
&rarr; &rarr;
= diff_file.new_path = diff_file.new_path
- elsif diff_file.new_file - elsif diff_file.new_file?
%span.new-file %span.new-file
%a{ href: "##{file_hash}" } %a{ href: "##{file_hash}" }
%i.fa.fa-plus %i.fa.fa-plus
......
...@@ -3,13 +3,13 @@ ...@@ -3,13 +3,13 @@
.suppressed-container .suppressed-container
%a.show-suppressed-diff.js-show-suppressed-diff Changes suppressed. Click to show. %a.show-suppressed-diff.js-show-suppressed-diff Changes suppressed. Click to show.
%table.text-file.code.js-syntax-highlight{ data: diff_view_data, class: too_big ? 'hide' : '' } %table.text-file.diff-wrap-lines.code.js-syntax-highlight{ data: diff_view_data, class: too_big ? 'hide' : '' }
= render partial: "projects/diffs/line", = render partial: "projects/diffs/line",
collection: diff_file.highlighted_diff_lines, collection: diff_file.highlighted_diff_lines,
as: :line, as: :line,
locals: { diff_file: diff_file, discussions: @grouped_diff_discussions } locals: { diff_file: diff_file, discussions: @grouped_diff_discussions }
- if !diff_file.new_file && !diff_file.deleted_file && diff_file.highlighted_diff_lines.any? - if !diff_file.new_file? && !diff_file.deleted_file? && diff_file.highlighted_diff_lines.any?
- last_line = diff_file.highlighted_diff_lines.last - last_line = diff_file.highlighted_diff_lines.last
- if last_line.new_pos < total_lines - if last_line.new_pos < total_lines
%tr.line_holder %tr.line_holder
......
- diff = diff_file.diff - blob = diff_file.blob
- file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.new_ref, diff.new_path)) - old_blob = diff_file.old_blob
// diff_refs will be nil for orphaned commits (e.g. first commit in repo) - blob_raw_path = diff_file_blob_raw_path(diff_file)
- if diff_file.old_ref - old_blob_raw_path = diff_file_old_blob_raw_path(diff_file)
- old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.old_ref, diff.old_path))
- if diff.renamed_file || diff.new_file || diff.deleted_file - if diff_file.new_file? || diff_file.deleted_file?
.image .image
%span.wrap %span.wrap
.frame{ class: image_diff_class(diff) } .frame{ class: (diff_file.deleted_file? ? 'deleted' : 'added') }
%img{ src: diff.deleted_file ? old_file_raw_path : file_raw_path, alt: diff.new_path } %img{ src: blob_raw_path, alt: diff_file.file_path }
%p.image-info= number_to_human_size(file.size) %p.image-info= number_to_human_size(blob.size)
- else - else
.image .image
.two-up.view .two-up.view
%span.wrap %span.wrap
.frame.deleted .frame.deleted
%a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.old_ref, diff.old_path)) } %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.old_content_sha, diff_file.old_path)) }
%img{ src: old_file_raw_path, alt: diff.old_path } %img{ src: old_blob_raw_path, alt: diff_file.old_path }
%p.image-info.hide %p.image-info.hide
%span.meta-filesize= number_to_human_size(old_file.size) %span.meta-filesize= number_to_human_size(old_blob.size)
| |
%b W: %b W:
%span.meta-width %span.meta-width
...@@ -27,10 +26,10 @@ ...@@ -27,10 +26,10 @@
%span.meta-height %span.meta-height
%span.wrap %span.wrap
.frame.added .frame.added
%a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.new_ref, diff.new_path)) } %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.content_sha, diff_file.new_path)) }
%img{ src: file_raw_path, alt: diff.new_path } %img{ src: blob_raw_path, alt: diff_file.new_path }
%p.image-info.hide %p.image-info.hide
%span.meta-filesize= number_to_human_size(file.size) %span.meta-filesize= number_to_human_size(blob.size)
| |
%b W: %b W:
%span.meta-width %span.meta-width
...@@ -41,10 +40,10 @@ ...@@ -41,10 +40,10 @@
.swipe.view.hide .swipe.view.hide
.swipe-frame .swipe-frame
.frame.deleted .frame.deleted
%img{ src: old_file_raw_path, alt: diff.old_path } %img{ src: old_blob_raw_path, alt: diff_file.old_path }
.swipe-wrap .swipe-wrap
.frame.added .frame.added
%img{ src: file_raw_path, alt: diff.new_path } %img{ src: blob_raw_path, alt: diff_file.new_path }
%span.swipe-bar %span.swipe-bar
%span.top-handle %span.top-handle
%span.bottom-handle %span.bottom-handle
...@@ -52,9 +51,9 @@ ...@@ -52,9 +51,9 @@
.onion-skin.view.hide .onion-skin.view.hide
.onion-skin-frame .onion-skin-frame
.frame.deleted .frame.deleted
%img{ src: old_file_raw_path, alt: diff.old_path } %img{ src: old_blob_raw_path, alt: diff_file.old_path }
.frame.added .frame.added
%img{ src: file_raw_path, alt: diff.new_path } %img{ src: blob_raw_path, alt: diff_file.new_path }
.controls .controls
.transparent .transparent
.drag-track .drag-track
......
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.
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