Commit 831eb946 authored by Achilleas Pipinellis's avatar Achilleas Pipinellis

Merge branch 'master' into doc_refactor_commits_api

parents b4d61ac4 8be4e545
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.5.0 (unreleased) v 8.5.0 (unreleased)
- Add "visibility" flag to GET /projects api endpoint
- Upgrade gitlab_git to 7.2.23 to fix commit message mentions in first branch push
- New UI for pagination
v 8.4.0 (unreleased) v 8.4.0 (unreleased)
- Allow LDAP users to change their email if it was not set by the LDAP server
- Ensure Gravatar host looks like an actual host - Ensure Gravatar host looks like an actual host
- Consider re-assign as a mention from a notification point of view
- Add pagination headers to already paginated API resources - Add pagination headers to already paginated API resources
- Properly generate diff of orphan commits, like the first commit in a repository - Properly generate diff of orphan commits, like the first commit in a repository
- Improve the consistency of commit titles, branch names, tag names, issue/MR titles, on their respective project pages - Improve the consistency of commit titles, branch names, tag names, issue/MR titles, on their respective project pages
- Autocomplete data is now always loaded, instead of when focusing a comment text area (Yorick Peterse) - Autocomplete data is now always loaded, instead of when focusing a comment text area
- Improved performance of finding issues for an entire group (Yorick Peterse) - Improved performance of finding issues for an entire group
- Added custom application performance measuring system powered by InfluxDB (Yorick Peterse) - Added custom application performance measuring system powered by InfluxDB
- Add syntax highlighting to diffs
- Gracefully handle invalid UTF-8 sequences in Markdown links (Stan Hu)
- Bump fog to 1.36.0 (Stan Hu) - Bump fog to 1.36.0 (Stan Hu)
- Add user's last used IP addresses to admin page (Stan Hu) - Add user's last used IP addresses to admin page (Stan Hu)
- Add housekeeping function to project settings page - Add housekeeping function to project settings page
...@@ -60,11 +67,16 @@ v 8.4.0 (unreleased) ...@@ -60,11 +67,16 @@ v 8.4.0 (unreleased)
- Autosize Markdown textareas - Autosize Markdown textareas
- Import GitHub wiki into GitLab - Import GitHub wiki into GitLab
- Add reporters ability to download and browse build artifacts (Andrew Johnson) - Add reporters ability to download and browse build artifacts (Andrew Johnson)
- Autofill referring url in message box when reporting user abuse. (Josh Frye) - Autofill referring url in message box when reporting user abuse.
- Remove leading comma on award emoji when the user is the first to award the emoji (Zeger-Jan van de Weg) - Remove leading comma on award emoji when the user is the first to award the emoji (Zeger-Jan van de Weg)
- Add build artifacts browser - Add build artifacts browser
- Improve UX in builds artifacts browser - Improve UX in builds artifacts browser
- Increase default size of `data` column in `events` table when using MySQL - Increase default size of `data` column in `events` table when using MySQL
- Expose button to CI Lint tool on project builds page
- Fix: Creator should be added as a master of the project on creation
- Added X-GitLab-... headers to emails from CI and Email On Push services (Anton Baklanov)
- Add IP check against DNSBLs at account sign-up
- Added cache:key to .gitlab-ci.yml allowing to fine tune the caching
v 8.3.4 v 8.3.4
- Use gitlab-workhorse 0.5.4 (fixes API routing bug) - Use gitlab-workhorse 0.5.4 (fixes API routing bug)
......
...@@ -49,7 +49,7 @@ gem "browser", '~> 1.0.0' ...@@ -49,7 +49,7 @@ gem "browser", '~> 1.0.0'
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
gem "gitlab_git", '~> 7.2.22' gem "gitlab_git", '~> 7.2.23'
# LDAP Auth # LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes # GitLab fork with several improvements to original library. For full list of changes
...@@ -293,6 +293,9 @@ end ...@@ -293,6 +293,9 @@ end
group :production do group :production do
gem "gitlab_meta", '7.0' gem "gitlab_meta", '7.0'
# Sentry integration
gem 'sentry-raven'
end end
gem "newrelic_rpm", '~> 3.9.4.245' gem "newrelic_rpm", '~> 3.9.4.245'
......
...@@ -356,7 +356,7 @@ GEM ...@@ -356,7 +356,7 @@ GEM
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab_emoji (0.2.0) gitlab_emoji (0.2.0)
gemojione (~> 2.1) gemojione (~> 2.1)
gitlab_git (7.2.22) gitlab_git (7.2.23)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
...@@ -725,6 +725,8 @@ GEM ...@@ -725,6 +725,8 @@ GEM
activesupport (>= 3.1, < 4.3) activesupport (>= 3.1, < 4.3)
select2-rails (3.5.9.3) select2-rails (3.5.9.3)
thor (~> 0.14) thor (~> 0.14)
sentry-raven (0.15.3)
faraday (>= 0.7.6)
settingslogic (2.0.9) settingslogic (2.0.9)
sexp_processor (4.6.0) sexp_processor (4.6.0)
sham_rack (1.3.6) sham_rack (1.3.6)
...@@ -932,7 +934,7 @@ DEPENDENCIES ...@@ -932,7 +934,7 @@ DEPENDENCIES
github-markup (~> 1.3.1) github-markup (~> 1.3.1)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab_emoji (~> 0.2.0) gitlab_emoji (~> 0.2.0)
gitlab_git (~> 7.2.22) gitlab_git (~> 7.2.23)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1) gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.1.0) gollum-lib (~> 4.1.0)
...@@ -1008,6 +1010,7 @@ DEPENDENCIES ...@@ -1008,6 +1010,7 @@ DEPENDENCIES
sdoc (~> 0.3.20) sdoc (~> 0.3.20)
seed-fu (~> 2.3.5) seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9) select2-rails (~> 3.5.9)
sentry-raven
settingslogic (~> 2.0.9) settingslogic (~> 2.0.9)
sham_rack sham_rack
shoulda-matchers (~> 2.8.0) shoulda-matchers (~> 2.8.0)
......
File mode changed from 100644 to 100755
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
...@@ -44,7 +44,6 @@ class @AwardsHandler ...@@ -44,7 +44,6 @@ class @AwardsHandler
decrementCounter: (emoji) -> decrementCounter: (emoji) ->
counter = @findEmojiIcon(emoji).siblings(".counter") counter = @findEmojiIcon(emoji).siblings(".counter")
emojiIcon = counter.parent() emojiIcon = counter.parent()
if parseInt(counter.text()) > 1 if parseInt(counter.text()) > 1
counter.text(parseInt(counter.text()) - 1) counter.text(parseInt(counter.text()) - 1)
emojiIcon.removeClass("active") emojiIcon.removeClass("active")
...@@ -60,20 +59,18 @@ class @AwardsHandler ...@@ -60,20 +59,18 @@ class @AwardsHandler
removeMeFromAuthorList: (emoji) -> removeMeFromAuthorList: (emoji) ->
award_block = @findEmojiIcon(emoji).parent() award_block = @findEmojiIcon(emoji).parent()
authors = award_block.attr("data-original-title").split(", ") authors = award_block.attr("data-original-title").split(", ")
authors = _.without(authors, "me").join(", ") authors.splice(authors.indexOf("me"),1)
award_block.attr("title", authors) award_block.closest(".award").attr("data-original-title", authors.join(", "))
@resetTooltip(award_block) @resetTooltip(award_block)
addMeToAuthorList: (emoji) -> addMeToAuthorList: (emoji) ->
award_block = @findEmojiIcon(emoji).parent() award_block = @findEmojiIcon(emoji).parent()
authors = _.compact(award_block.attr("data-original-title").split(", ")) origTitle = award_block.attr("data-original-title").trim()
authors = []
if origTitle
authors = origTitle.split(', ')
authors.push("me") authors.push("me")
award_block.attr("title", authors.join(", "))
if authors.length == 1
award_block.attr("title", "me")
else
award_block.attr("title", authors.join(", "))
@resetTooltip(award_block) @resetTooltip(award_block)
resetTooltip: (award) -> resetTooltip: (award) ->
......
class @BuildArtifacts
constructor: () ->
@disablePropagation()
@setupEntryClick()
disablePropagation: ->
$('.top-block').on 'click', '.download', (e) ->
e.stopPropagation()
$('.tree-holder').on 'click', 'tr[data-link] a', (e) ->
e.stopImmediatePropagation()
setupEntryClick: ->
$('.tree-holder').on 'click', 'tr[data-link]', (e) ->
window.location = @dataset.link
...@@ -87,7 +87,6 @@ class Dispatcher ...@@ -87,7 +87,6 @@ class Dispatcher
new GroupAvatar() new GroupAvatar()
when 'projects:tree:show' when 'projects:tree:show'
new TreeView() new TreeView()
shortcut_handler = new ShortcutsTree()
when 'projects:find_file:show' when 'projects:find_file:show'
shortcut_handler = true shortcut_handler = true
when 'projects:blob:show' when 'projects:blob:show'
...@@ -101,6 +100,8 @@ class Dispatcher ...@@ -101,6 +100,8 @@ class Dispatcher
shortcut_handler = true shortcut_handler = true
when 'projects:forks:new' when 'projects:forks:new'
new ProjectFork() new ProjectFork()
when 'projects:artifacts:browse'
new BuildArtifacts()
when 'users:show' when 'users:show'
new User() new User()
new Activities() new Activities()
......
...@@ -4,6 +4,7 @@ class @Shortcuts ...@@ -4,6 +4,7 @@ class @Shortcuts
Mousetrap.reset() Mousetrap.reset()
Mousetrap.bind('?', @selectiveHelp) Mousetrap.bind('?', @selectiveHelp)
Mousetrap.bind('s', Shortcuts.focusSearch) Mousetrap.bind('s', Shortcuts.focusSearch)
Mousetrap.bind('t', -> Turbolinks.visit(findFileURL)) if findFileURL?
selectiveHelp: (e) => selectiveHelp: (e) =>
Shortcuts.showHelp(e, @enabledHelp) Shortcuts.showHelp(e, @enabledHelp)
......
class @ShortcutsTree extends ShortcutsNavigation
constructor: ->
super()
Mousetrap.bind('t', -> ShortcutsTree.findAndFollowLink('.shortcuts-find-file'))
...@@ -6,7 +6,7 @@ class @Star ...@@ -6,7 +6,7 @@ class @Star
$starIcon = $this.find('i') $starIcon = $this.find('i')
toggleStar = (isStarred) -> toggleStar = (isStarred) ->
$this.parent().find('span.count').text data.star_count $this.parent().find('.star-count').text data.star_count
if isStarred if isStarred
$starSpan.removeClass('starred').text 'Star' $starSpan.removeClass('starred').text 'Star'
$starIcon.removeClass('fa-star').addClass 'fa-star-o' $starIcon.removeClass('fa-star').addClass 'fa-star-o'
...@@ -19,4 +19,4 @@ class @Star ...@@ -19,4 +19,4 @@ class @Star
return return
).on 'ajax:error', (e, xhr, status, error) -> ).on 'ajax:error', (e, xhr, status, error) ->
new Flash('Star toggle failed. Try again later.', 'alert') new Flash('Star toggle failed. Try again later.', 'alert')
return return
\ No newline at end of file
...@@ -120,14 +120,6 @@ span.update-author { ...@@ -120,14 +120,6 @@ span.update-author {
display: inline; display: inline;
} }
.line_holder {
&:hover {
td {
background: #FFFFCF !important;
}
}
}
p.time { p.time {
color: #999; color: #999;
font-size: 90%; font-size: 90%;
......
...@@ -92,15 +92,6 @@ ...@@ -92,15 +92,6 @@
&:last-child { &:last-child {
border-right: none; border-right: none;
} }
background: #fff;
}
.lines {
pre {
padding: 0;
margin: 0;
background: none;
border: none;
}
} }
img.avatar { img.avatar {
border: 0 none; border: 0 none;
...@@ -116,18 +107,18 @@ ...@@ -116,18 +107,18 @@
color: #888; color: #888;
} }
} }
td.blame-numbers { td.line-numbers {
pre { float: none;
color: #AAA;
white-space: pre;
}
background: #f1f1f1;
border-left: 1px solid #DDD; border-left: 1px solid #DDD;
} }
td.lines { td.lines {
padding: 0;
code { code {
font-family: $monospace_font; font-family: $monospace_font;
} }
pre {
margin: 0;
}
} }
} }
......
...@@ -53,3 +53,14 @@ ...@@ -53,3 +53,14 @@
color: #333; color: #333;
} }
} }
.ui-sortable-handle {
cursor: move;
cursor: -webkit-grab;
cursor: -moz-grab;
&:active {
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
}
}
.gl-pagination { .gl-pagination {
text-align: center;
border-top: 1px solid $border-color; border-top: 1px solid $border-color;
background-color: $background-color; margin: 0;
margin: -$gl-padding;
margin-top: 0; margin-top: 0;
.pagination { .pagination {
padding: 0; padding: 0;
margin: 0;
display: block;
li.first,
li.last,
li.next,
li.prev {
> a {
color: $link-color;
&:hover {
color: #fff;
}
}
}
li > a,
li > span {
border: none;
margin: 0;
@include border-radius(0 !important);
padding: 13px 19px;
border-right: 1px solid $border-color;
}
} }
} }
......
...@@ -66,20 +66,20 @@ $legend-color: $text-color; ...@@ -66,20 +66,20 @@ $legend-color: $text-color;
//## //##
$pagination-color: $gl-gray; $pagination-color: $gl-gray;
$pagination-bg: $background-color; $pagination-bg: #fff;
$pagination-border: transparent; $pagination-border: $border-color;
$pagination-hover-color: #fff; $pagination-hover-color: $gl-gray;
$pagination-hover-bg: $brand-info; $pagination-hover-bg: $hover;
$pagination-hover-border: transparent; $pagination-hover-border: $border-color;
$pagination-active-color: #fff; $pagination-active-color: $blue-dark;
$pagination-active-bg: $brand-info; $pagination-active-bg: #fff;
$pagination-active-border: transparent; $pagination-active-border: $border-color;
$pagination-disabled-color: #fff; $pagination-disabled-color: #cdcdcd;
$pagination-disabled-bg: lighten($brand-info, 15%); $pagination-disabled-bg: $background-color;
$pagination-disabled-border: transparent; $pagination-disabled-border: $border-color;
//== Form states and alerts //== Form states and alerts
......
...@@ -26,6 +26,7 @@ $gl-vert-padding: 6px; ...@@ -26,6 +26,7 @@ $gl-vert-padding: 6px;
$gl-padding-top:10px; $gl-padding-top:10px;
$gl-avatar-size: 46px; $gl-avatar-size: 46px;
$secondary-text: #7f8fa4; $secondary-text: #7f8fa4;
$error-exclamation-point: #E62958;
/* /*
* Color schema * Color schema
......
/* https://github.com/MozMorris/tomorrow-pygments */ /* https://github.com/MozMorris/tomorrow-pygments */
.code.dark { .code.dark {
// Line numbers
.line-numbers, .diff-line-num {
background-color: #1d1f21;
}
background-color: #1d1f21 !important; .diff-line-num, .diff-line-num a {
color: #c5c8c6 !important; color: rgba(255, 255, 255, 0.3);
pre.highlight,
.line-numbers,
.line-numbers a {
background-color: #1d1f21 !important;
color: #c5c8c6 !important;
} }
// Code itself
pre.code { pre.code {
border-left: 1px solid #666; border-left: 1px solid #666;
} }
&, pre.code, .line_holder .line_content {
background-color: #1d1f21;
color: #c5c8c6;
}
// Diff line
.line_holder {
.diff-line-num.new, .line_content.new {
@include diff_background(rgba(51, 255, 51, 0.1), rgba(51, 255, 51, 0.3), #808080);
}
.diff-line-num.old, .line_content.old {
@include diff_background(rgba(255, 51, 51, 0.2), rgba(255, 51, 51, 0.3), #808080);
}
.line_content.match {
color: rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.1);
}
}
// highlight line via anchor // highlight line via anchor
pre .hll { pre .hll {
background-color: #557 !important; background-color: #557 !important;
......
/* https://github.com/richleland/pygments-css/blob/master/monokai.css */ /* https://github.com/richleland/pygments-css/blob/master/monokai.css */
.code.monokai { .code.monokai {
// Line numbers
.line-numbers, .diff-line-num {
background-color: #272822;
}
background-color: #272822 !important; .diff-line-num, .diff-line-num a {
color: #f8f8f2 !important; color: rgba(255, 255, 255, 0.3);
pre.highlight,
.line-numbers,
.line-numbers a {
background-color :#272822 !important;
color: #f8f8f2 !important;
} }
// Code itself
pre.code { pre.code {
border-left: 1px solid #555; border-left: 1px solid #555;
} }
&, pre.code, .line_holder .line_content {
background-color: #272822;
color: #f8f8f2;
}
// Diff line
.line_holder {
.diff-line-num.new, .line_content.new {
@include diff_background(rgba(166, 226, 46, 0.2), rgba(166, 226, 46, 0.3), #808080);
}
.diff-line-num.old, .line_content.old {
@include diff_background(rgba(254, 147, 140, 0.2), rgba(254, 147, 140, 0.3), #808080);
}
.line_content.match {
color: rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.1);
}
}
// highlight line via anchor // highlight line via anchor
pre .hll { pre .hll {
background-color: #49483e !important; background-color: #49483e !important;
......
/* https://gist.github.com/qguv/7936275 */ /* https://gist.github.com/qguv/7936275 */
.code.solarized-dark { .code.solarized-dark {
// Line numbers
.line-numbers, .diff-line-num {
background-color: #002b36;
}
background-color: #002b36 !important; .diff-line-num, .diff-line-num a {
color: #93a1a1 !important; color: rgba(255, 255, 255, 0.3);
pre.highlight,
.line-numbers,
.line-numbers a {
background-color: #002b36 !important;
color: #93a1a1 !important;
} }
// Code itself
pre.code { pre.code {
border-left: 1px solid #113b46; border-left: 1px solid #113b46;
} }
&, pre.code, .line_holder .line_content {
background-color: #002b36;
color: #93a1a1;
}
// Diff line
.line_holder {
.diff-line-num.new, .line_content.new {
@include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.3), #808080);
}
.diff-line-num.old, .line_content.old {
@include diff_background(rgba(220, 50, 47, 0.2), rgba(220, 50, 47, 0.3), #808080);
}
.line_content.match {
color: rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.1);
}
}
// highlight line via anchor // highlight line via anchor
pre .hll { pre .hll {
background-color: #174652 !important; background-color: #174652 !important;
......
/* https://gist.github.com/qguv/7936275 */ /* https://gist.github.com/qguv/7936275 */
.code.solarized-light { .code.solarized-light {
// Line numbers
.line-numbers, .diff-line-num {
background-color: #fdf6e3;
}
background-color: #fdf6e3 !important; .diff-line-num, .diff-line-num a {
color: #586e75 !important; color: rgba(0, 0, 0, 0.3);
pre.highlight,
.line-numbers,
.line-numbers a {
background-color: #fdf6e3 !important;
color: #586e75 !important;
} }
// Code itself
pre.code { pre.code {
border-left: 1px solid #c5d0d4; border-left: 1px solid #c5d0d4;
} }
&, pre.code, .line_holder .line_content {
background-color: #fdf6e3;
color: #586e75;
}
// Diff line
.line_holder {
.diff-line-num.new, .line_content.new {
@include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.3), #FAF3DD);
}
.diff-line-num.old, .line_content.old {
@include diff_background(rgba(220, 50, 47, 0.2), rgba(220, 50, 47, 0.3), #FAF3DD);
}
.line_content.match {
color: rgba(0, 0, 0, 0.3);
background: rgba(255, 255, 255, 0.4);
}
}
// highlight line via anchor // highlight line via anchor
pre .hll { pre .hll {
background-color: #ddd8c5 !important; background-color: #ddd8c5 !important;
......
/* https://github.com/aahan/pygments-github-style */ /* https://github.com/aahan/pygments-github-style */
.code.white { .code.white {
// Line numbers
.line-numbers, .diff-line-num {
background-color: $background-color;
}
background-color: #f8fafc !important; .diff-line-num, .diff-line-num a {
color: #5b6169 !important; color: rgba(0, 0, 0, 0.3);
pre.highlight,
.line-numbers,
.line-numbers a {
background-color: $background-color !important;
color: $gl-gray !important;
} }
// Code itself
pre.code { pre.code {
border-left: 1px solid $border-color; border-left: 1px solid $border-color;
background-color: #fff !important; }
color: #333 !important;
&, pre.code, .line_holder .line_content {
background-color: #fff;
color: #333;
}
// Diff line
.line_holder {
.diff-line-num {
&.old {
background: #ffdddd;
border-color: #f1c0c0;
}
&.new {
background: #dbffdb;
border-color: #c1e9c1;
}
}
.line_content {
&.old {
background: #ffecec;
span.idiff {
background-color: #f8cbcb;
}
}
&.new {
background: #eaffea;
span.idiff {
background-color: #a6f3a6;
}
}
&.match {
color: rgba(0, 0, 0, 0.3);
background: #fafafa;
}
}
} }
// highlight line via anchor // highlight line via anchor
......
...@@ -32,16 +32,6 @@ ...@@ -32,16 +32,6 @@
background: #FFF; background: #FFF;
color: #333; color: #333;
.old {
span.idiff {
background-color: #f8cbcb;
}
}
.new {
span.idiff {
background-color: #a6f3a6;
}
}
.unfold { .unfold {
cursor: pointer; cursor: pointer;
} }
...@@ -76,7 +66,7 @@ ...@@ -76,7 +66,7 @@
} }
tr.line_holder.parallel { tr.line_holder.parallel {
.old_line, .new_line, .diff_line { .old_line, .new_line {
min-width: 50px; min-width: 50px;
} }
...@@ -85,7 +75,7 @@ ...@@ -85,7 +75,7 @@
} }
} }
.old_line, .new_line, .diff_line { .old_line, .new_line {
margin: 0px; margin: 0px;
padding: 0px; padding: 0px;
border: none; border: none;
...@@ -107,43 +97,12 @@ ...@@ -107,43 +97,12 @@
text-decoration: underline; text-decoration: underline;
} }
} }
&.new {
background: #CFD;
}
&.old {
background: #FDD;
}
}
.diff_line {
padding: 0;
}
.line_holder {
&.old .old_line,
&.old .new_line {
background: #ffdddd;
border-color: #f1c0c0;
}
&.new .old_line,
&.new .new_line {
background: #dbffdb;
border-color: #c1e9c1;
}
} }
.line_content { .line_content {
display: block; display: block;
margin: 0px; margin: 0px;
padding: 0px 0.5em; padding: 0px 0.5em;
border: none; border: none;
&.new {
background: #eaffea;
}
&.old {
background: #ffecec;
}
&.matched {
color: $border-color;
background: #fafafa;
}
&.parallel { &.parallel {
display: table-cell; display: table-cell;
} }
...@@ -393,3 +352,15 @@ ...@@ -393,3 +352,15 @@
right: 15px; right: 15px;
} }
} }
@mixin diff_background($background, $idiff, $border) {
background: $background;
&.line_content span.idiff {
background: $idiff;
}
&.diff-line-num {
border-color: $border;
}
}
...@@ -201,3 +201,39 @@ ...@@ -201,3 +201,39 @@
.mr-source-target { .mr-source-target {
line-height: 31px; line-height: 31px;
} }
.disabled-comment-area {
padding: 16px 0;
.disabled-profile {
width: 40px;
height: 40px;
background: $border-gray-dark;
border-radius: 20px;
display: inline-block;
margin-right: 10px;
}
.disabled-comment {
background: $gray-light;
display: inline-block;
vertical-align: top;
height: 200px;
border-radius: 4px;
border: 1px solid $border-gray-normal;
padding-top: 90px;
text-align: center;
right: 20px;
position: absolute;
left: 70px;
margin-bottom: 20px;
span {
color: #B2B2B2;
a {
color: $md-link-color;
}
}
}
}
\ No newline at end of file
...@@ -10,18 +10,6 @@ ...@@ -10,18 +10,6 @@
margin: 10px $gl-padding; margin: 10px $gl-padding;
} }
.diff-file .diff-content { .diff-file .diff-content {
tr.line_holder:hover {
&> td.line_content {
background: $hover !important;
border-color: darken($hover, 10%) !important;
}
&> td.new_line,
&> td.old_line {
background: darken($hover, 4%) !important;
border-color: darken($hover, 10%) !important;
}
}
tr.line_holder:hover > td .line_note_link { tr.line_holder:hover > td .line_note_link {
opacity: 1.0; opacity: 1.0;
filter: alpha(opacity=100); filter: alpha(opacity=100);
......
...@@ -242,11 +242,8 @@ ul.notes { ...@@ -242,11 +242,8 @@ ul.notes {
// "show" the icon also if we just hover somewhere over the line // "show" the icon also if we just hover somewhere over the line
&:hover > td { &:hover > td {
background: $hover !important;
.add-diff-note { .add-diff-note {
@include show-add-diff-note; @include show-add-diff-note;
} }
} }
} }
...@@ -558,3 +558,9 @@ pre.light-well { ...@@ -558,3 +558,9 @@ pre.light-well {
width: 101%; width: 101%;
} }
} }
.cannot-be-merged,
.cannot-be-merged:hover {
color: #E62958;
margin-top: 2px;
}
...@@ -3,10 +3,6 @@ ...@@ -3,10 +3,6 @@
border-bottom: 1px solid #DDD; border-bottom: 1px solid #DDD;
padding-bottom: 15px; padding-bottom: 15px;
margin-bottom: 15px; margin-bottom: 15px;
.term {
height: 22px;
}
} }
} }
......
...@@ -74,9 +74,13 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -74,9 +74,13 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:metrics_timeout, :metrics_timeout,
:metrics_method_call_threshold, :metrics_method_call_threshold,
:metrics_sample_interval, :metrics_sample_interval,
:ip_blocking_enabled,
:dnsbl_servers_list,
:recaptcha_enabled, :recaptcha_enabled,
:recaptcha_site_key, :recaptcha_site_key,
:recaptcha_private_key, :recaptcha_private_key,
:sentry_enabled,
:sentry_dsn,
restricted_visibility_levels: [], restricted_visibility_levels: [],
import_sources: [] import_sources: []
) )
......
...@@ -15,6 +15,7 @@ class ApplicationController < ActionController::Base ...@@ -15,6 +15,7 @@ class ApplicationController < ActionController::Base
before_action :check_password_expiration before_action :check_password_expiration
before_action :check_2fa_requirement before_action :check_2fa_requirement
before_action :ldap_security_check before_action :ldap_security_check
before_action :sentry_user_context
before_action :default_headers before_action :default_headers
before_action :add_gon_variables before_action :add_gon_variables
before_action :configure_permitted_parameters, if: :devise_controller? before_action :configure_permitted_parameters, if: :devise_controller?
...@@ -24,6 +25,7 @@ class ApplicationController < ActionController::Base ...@@ -24,6 +25,7 @@ class ApplicationController < ActionController::Base
helper_method :abilities, :can?, :current_application_settings helper_method :abilities, :can?, :current_application_settings
helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled? helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?
helper_method :repository
rescue_from Encoding::CompatibilityError do |exception| rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception) log_exception(exception)
...@@ -41,6 +43,16 @@ class ApplicationController < ActionController::Base ...@@ -41,6 +43,16 @@ class ApplicationController < ActionController::Base
protected protected
def sentry_user_context
if Rails.env.production? && current_application_settings.sentry_enabled && current_user
Raven.user_context(
id: current_user.id,
email: current_user.email,
username: current_user.username,
)
end
end
# From https://github.com/plataformatec/devise/wiki/How-To:-Simple-Token-Authentication-Example # From https://github.com/plataformatec/devise/wiki/How-To:-Simple-Token-Authentication-Example
# https://gist.github.com/josevalim/fb706b1e933ef01e4fb6 # https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
def authenticate_user_from_token! def authenticate_user_from_token!
......
...@@ -97,7 +97,7 @@ module CreatesCommit ...@@ -97,7 +97,7 @@ module CreatesCommit
# Merge request from fork to this project # Merge request from fork to this project
@mr_source_project = @tree_edit_project @mr_source_project = @tree_edit_project
@mr_target_project = @project @mr_target_project = @project
@mr_target_branch = @mr_target_project.repository.root_ref @mr_target_branch = @ref
end end
end end
end end
...@@ -52,7 +52,9 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -52,7 +52,9 @@ class Projects::BlobController < Projects::ApplicationController
def preview def preview
@content = params[:content] @content = params[:content]
diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', include_diff_info: true) diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', include_diff_info: true)
@diff_lines = Gitlab::Diff::Parser.new.parse(diffy.diff.scan(/.*\n/)) diff_lines = diffy.diff.scan(/.*\n/)[2..-1]
diff_lines = Gitlab::Diff::Parser.new.parse(diff_lines)
@diff_lines = Gitlab::Diff::Highlight.new(diff_lines).highlight
render layout: false render layout: false
end end
...@@ -65,8 +67,9 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -65,8 +67,9 @@ class Projects::BlobController < Projects::ApplicationController
end end
def diff def diff
@form = UnfoldForm.new(params) @form = UnfoldForm.new(params)
@lines = @blob.data.lines[@form.since - 1..@form.to - 1] @lines = Gitlab::Highlight.highlight_lines(repository, @ref, @path)
@lines = @lines[@form.since - 1..@form.to - 1]
if @form.bottom? if @form.bottom?
@match_line = '' @match_line = ''
......
...@@ -72,6 +72,7 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -72,6 +72,7 @@ class Projects::CommitController < Projects::ApplicationController
@diffs = commit.diffs @diffs = commit.diffs
end end
@diff_refs = [commit.parent || commit, commit]
@notes_count = commit.notes.count @notes_count = commit.notes.count
@statuses = ci_commit.statuses if ci_commit @statuses = ci_commit.statuses if ci_commit
......
...@@ -21,7 +21,8 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -21,7 +21,8 @@ class Projects::CompareController < Projects::ApplicationController
@commits = Commit.decorate(compare_result.commits, @project) @commits = Commit.decorate(compare_result.commits, @project)
@diffs = compare_result.diffs @diffs = compare_result.diffs
@commit = @project.commit(head_ref) @commit = @project.commit(head_ref)
@first_commit = @project.commit(base_ref) @base_commit = @project.commit(base_ref)
@diff_refs = [@base_commit, @commit]
@line_notes = [] @line_notes = []
end end
end end
......
...@@ -58,7 +58,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -58,7 +58,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def diffs def diffs
@commit = @merge_request.last_commit @commit = @merge_request.last_commit
@first_commit = @merge_request.first_commit @base_commit = @merge_request.diff_base_commit
# MRs created before 8.4 don't have a diff_base_commit,
# but we need it for the "View file @ ..." link by deleted files
@base_commit ||= @merge_request.first_commit.parent || @merge_request.first_commit
@comments_allowed = @reply_allowed = true @comments_allowed = @reply_allowed = true
@comments_target = { @comments_target = {
...@@ -102,7 +106,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -102,7 +106,7 @@ 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.last_commit @commit = @merge_request.last_commit
@first_commit = @merge_request.first_commit @base_commit = @merge_request.diff_base_commit
@diffs = @merge_request.compare_diffs @diffs = @merge_request.compare_diffs
@ci_commit = @merge_request.ci_commit @ci_commit = @merge_request.ci_commit
......
...@@ -8,6 +8,11 @@ class RegistrationsController < Devise::RegistrationsController ...@@ -8,6 +8,11 @@ class RegistrationsController < Devise::RegistrationsController
def create def create
if !Gitlab::Recaptcha.load_configurations! || verify_recaptcha if !Gitlab::Recaptcha.load_configurations! || verify_recaptcha
if Gitlab::IpCheck.new(request.remote_ip).spam?
flash[:alert] = 'Could not create an account. This IP is listed for spam.'
return render action: 'new'
end
super super
else else
flash[:alert] = "There was an error with the reCAPTCHA code below. Please re-enter the code." flash[:alert] = "There was an error with the reCAPTCHA code below. Please re-enter the code."
......
module BlobHelper module BlobHelper
def highlight(blob_name, blob_content, nowrap: false, continue: false) def highlighter(blob_name, blob_content, nowrap: false)
@formatter ||= Rouge::Formatters::HTMLGitlab.new( Gitlab::Highlight.new(blob_name, blob_content, nowrap: nowrap)
nowrap: nowrap, end
cssclass: 'code highlight',
lineanchors: true,
lineanchorsid: 'LC'
)
begin
@lexer ||= Rouge::Lexer.guess(filename: blob_name, source: blob_content).new
result = @formatter.format(@lexer.lex(blob_content, continue: continue)).html_safe
rescue
@lexer = Rouge::Lexers::PlainText
result = @formatter.format(@lexer.lex(blob_content)).html_safe
end
result def highlight(blob_name, blob_content, nowrap: false)
Gitlab::Highlight.highlight(blob_name, blob_content, nowrap: nowrap)
end end
def no_highlight_files def no_highlight_files
...@@ -37,10 +26,10 @@ module BlobHelper ...@@ -37,10 +26,10 @@ module BlobHelper
tree_join(ref, path), tree_join(ref, path),
link_opts) link_opts)
if !on_top_of_branch? if !on_top_of_branch?(project, ref)
button_tag "Edit", class: "btn btn-default disabled has_tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' } button_tag "Edit", class: "btn btn-default disabled has_tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' }
elsif can_edit_blob?(blob) elsif can_edit_blob?(blob, project, ref)
link_to "Edit", edit_path, class: 'btn btn-small' link_to "Edit", edit_path, class: 'btn'
elsif can?(current_user, :fork_project, project) elsif can?(current_user, :fork_project, project)
continue_params = { continue_params = {
to: edit_path, to: edit_path,
...@@ -50,7 +39,7 @@ module BlobHelper ...@@ -50,7 +39,7 @@ module BlobHelper
fork_path = namespace_project_fork_path(project.namespace, project, namespace_key: current_user.namespace.id, fork_path = namespace_project_fork_path(project.namespace, project, namespace_key: current_user.namespace.id,
continue: continue_params) continue: continue_params)
link_to "Edit", fork_path, class: 'btn btn-small', method: :post link_to "Edit", fork_path, class: 'btn', method: :post
end end
end end
...@@ -61,11 +50,11 @@ module BlobHelper ...@@ -61,11 +50,11 @@ module BlobHelper
return unless blob return unless blob
if !on_top_of_branch? if !on_top_of_branch?(project, ref)
button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' } button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' }
elsif blob.lfs_pointer? elsif blob.lfs_pointer?
button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' } button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' }
elsif can_edit_blob?(blob) elsif can_edit_blob?(blob, project, ref)
button_tag label, class: "btn btn-#{btn_class}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal' button_tag label, class: "btn btn-#{btn_class}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal'
elsif can?(current_user, :fork_project, project) elsif can?(current_user, :fork_project, project)
continue_params = { continue_params = {
......
...@@ -166,7 +166,7 @@ module CommitsHelper ...@@ -166,7 +166,7 @@ module CommitsHelper
link_to( link_to(
namespace_project_blob_path(project.namespace, project, namespace_project_blob_path(project.namespace, project,
tree_join(commit_sha, diff.new_path)), tree_join(commit_sha, diff.new_path)),
class: 'btn btn-small view-file js-view-file' class: 'btn view-file js-view-file'
) do ) do
raw('View file @') + content_tag(:span, commit_sha[0..6], raw('View file @') + content_tag(:span, commit_sha[0..6],
class: 'commit-short-id') class: 'commit-short-id')
......
...@@ -19,13 +19,13 @@ module DiffHelper ...@@ -19,13 +19,13 @@ module DiffHelper
end end
end end
def safe_diff_files(diffs) def safe_diff_files(diffs, diff_refs)
lines = 0 lines = 0
safe_files = [] safe_files = []
diffs.first(allowed_diff_size).each do |diff| diffs.first(allowed_diff_size).each do |diff|
lines += diff.diff.lines.count lines += diff.diff.lines.count
break if lines > allowed_diff_lines break if lines > allowed_diff_lines
safe_files << Gitlab::Diff::File.new(diff) safe_files << Gitlab::Diff::File.new(diff, diff_refs)
end end
safe_files safe_files
end end
...@@ -43,64 +43,6 @@ module DiffHelper ...@@ -43,64 +43,6 @@ module DiffHelper
Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos) Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos)
end end
def parallel_diff(diff_file, index)
lines = []
skip_next = false
# Building array of lines
#
# [
# left_type, left_line_number, left_line_content, left_line_code,
# right_line_type, right_line_number, right_line_content, right_line_code
# ]
#
diff_file.diff_lines.each do |line|
full_line = line.text
type = line.type
line_code = generate_line_code(diff_file.file_path, line)
line_new = line.new_pos
line_old = line.old_pos
next_line = diff_file.next_line(line.index)
if next_line
next_line_code = generate_line_code(diff_file.file_path, next_line)
next_type = next_line.type
next_line = next_line.text
end
if type == 'match' || type.nil?
# line in the right panel is the same as in the left one
line = [type, line_old, full_line, line_code, type, line_new, full_line, line_code]
lines.push(line)
elsif type == 'old'
if next_type == 'new'
# Left side has text removed, right side has text added
line = [type, line_old, full_line, line_code, next_type, line_new, next_line, next_line_code]
lines.push(line)
skip_next = true
elsif next_type == 'old' || next_type.nil?
# Left side has text removed, right side doesn't have any change
# No next line code, no new line number, no new line text
line = [type, line_old, full_line, line_code, next_type, nil, "&nbsp;", nil]
lines.push(line)
end
elsif type == 'new'
if skip_next
# Change has been already included in previous line so no need to do it again
skip_next = false
next
else
# Change is only on the right side, left side has no change
line = [nil, nil, "&nbsp;", line_code, type, line_new, full_line, line_code]
lines.push(line)
end
end
end
lines
end
def unfold_bottom_class(bottom) def unfold_bottom_class(bottom)
(bottom) ? 'js-unfold-bottom' : '' (bottom) ? 'js-unfold-bottom' : ''
end end
...@@ -111,9 +53,9 @@ module DiffHelper ...@@ -111,9 +53,9 @@ module DiffHelper
def diff_line_content(line) def diff_line_content(line)
if line.blank? if line.blank?
" &nbsp;" " &nbsp;".html_safe
else else
line line.html_safe
end end
end end
...@@ -160,8 +102,7 @@ module DiffHelper ...@@ -160,8 +102,7 @@ module DiffHelper
def commit_for_diff(diff) def commit_for_diff(diff)
if diff.deleted_file if diff.deleted_file
first_commit = @first_commit || @commit @base_commit || @commit.parent || @commit
first_commit.parent || @first_commit
else else
@commit @commit
end end
......
...@@ -3,13 +3,26 @@ module Emails ...@@ -3,13 +3,26 @@ module Emails
def build_fail_email(build_id, to) def build_fail_email(build_id, to)
@build = Ci::Build.find(build_id) @build = Ci::Build.find(build_id)
@project = @build.project @project = @build.project
add_project_headers
add_build_headers
headers['X-GitLab-Build-Status'] = "failed"
mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha)) mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha))
end end
def build_success_email(build_id, to) def build_success_email(build_id, to)
@build = Ci::Build.find(build_id) @build = Ci::Build.find(build_id)
@project = @build.project @project = @build.project
add_project_headers
add_build_headers
headers['X-GitLab-Build-Status'] = "success"
mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha)) mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha))
end end
private
def add_build_headers
headers['X-GitLab-Build-Id'] = @build.id
headers['X-GitLab-Build-Ref'] = @build.ref
end
end end
end end
...@@ -43,7 +43,7 @@ module Emails ...@@ -43,7 +43,7 @@ module Emails
@current_user = @created_by = User.find(created_by_id) @current_user = @created_by = User.find(created_by_id)
@access_level = access_level @access_level = access_level
@invite_email = invite_email @invite_email = invite_email
@target_url = namespace_project_url(@project.namespace, @project) @target_url = namespace_project_url(@project.namespace, @project)
mail(to: @created_by.notification_email, mail(to: @created_by.notification_email,
...@@ -65,6 +65,10 @@ module Emails ...@@ -65,6 +65,10 @@ module Emails
# used in notify layout # used in notify layout
@target_url = @message.target_url @target_url = @message.target_url
@project = Project.find project_id
add_project_headers
headers['X-GitLab-Author'] = @message.author_username
mail(from: sender(@message.author_id, @message.send_from_committer_email?), mail(from: sender(@message.author_id, @message.send_from_committer_email?),
reply_to: @message.reply_to, reply_to: @message.reply_to,
......
...@@ -100,12 +100,7 @@ class Notify < BaseMailer ...@@ -100,12 +100,7 @@ class Notify < BaseMailer
end end
def mail_thread(model, headers = {}) def mail_thread(model, headers = {})
if @project add_project_headers
headers['X-GitLab-Project'] = @project.name
headers['X-GitLab-Project-Id'] = @project.id
headers['X-GitLab-Project-Path'] = @project.path_with_namespace
end
headers["X-GitLab-#{model.class.name}-ID"] = model.id headers["X-GitLab-#{model.class.name}-ID"] = model.id
headers['X-GitLab-Reply-Key'] = reply_key headers['X-GitLab-Reply-Key'] = reply_key
...@@ -152,4 +147,12 @@ class Notify < BaseMailer ...@@ -152,4 +147,12 @@ class Notify < BaseMailer
def reply_key def reply_key
@reply_key ||= SentNotification.reply_key @reply_key ||= SentNotification.reply_key
end end
def add_project_headers
return unless @project
headers['X-GitLab-Project'] = @project.name
headers['X-GitLab-Project-Id'] = @project.id
headers['X-GitLab-Project-Path'] = @project.path_with_namespace
end
end end
...@@ -41,6 +41,10 @@ ...@@ -41,6 +41,10 @@
# recaptcha_site_key :string # recaptcha_site_key :string
# recaptcha_private_key :string # recaptcha_private_key :string
# metrics_port :integer default(8089) # metrics_port :integer default(8089)
# sentry_enabled :boolean default(FALSE)
# sentry_dsn :string
# ip_blocking_enabled :boolean default(FALSE)
# dns_blacklist_threshold :float default(0.33)
# #
class ApplicationSetting < ActiveRecord::Base class ApplicationSetting < ActiveRecord::Base
...@@ -82,6 +86,10 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -82,6 +86,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true, presence: true,
if: :recaptcha_enabled if: :recaptcha_enabled
validates :sentry_dsn,
presence: true,
if: :sentry_enabled
validates_each :restricted_visibility_levels do |record, attr, value| validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil? unless value.nil?
value.each do |level| value.each do |level|
......
...@@ -346,17 +346,17 @@ module Ci ...@@ -346,17 +346,17 @@ module Ci
end end
def artifacts_browse_url def artifacts_browse_url
if artifacts_browser_supported? if artifacts_metadata?
browse_namespace_project_build_artifacts_path(project.namespace, project, self) browse_namespace_project_build_artifacts_path(project.namespace, project, self)
end end
end end
def artifacts_browser_supported? def artifacts_metadata?
artifacts? && artifacts_metadata.exists? artifacts? && artifacts_metadata.exists?
end end
def artifacts_metadata_entry(path) def artifacts_metadata_entry(path, **options)
Gitlab::Ci::Build::Artifacts::Metadata.new(artifacts_metadata.path, path).to_entry Gitlab::Ci::Build::Artifacts::Metadata.new(artifacts_metadata.path, path, **options).to_entry
end end
private private
......
...@@ -91,7 +91,7 @@ class Member < ActiveRecord::Base ...@@ -91,7 +91,7 @@ class Member < ActiveRecord::Base
member.invite_email = user member.invite_email = user
end end
if can_update_member?(current_user, member) if can_update_member?(current_user, member) || project_creator?(member, access_level)
member.created_by ||= current_user member.created_by ||= current_user
member.access_level = access_level member.access_level = access_level
...@@ -107,6 +107,11 @@ class Member < ActiveRecord::Base ...@@ -107,6 +107,11 @@ class Member < ActiveRecord::Base
current_user.can?(:update_group_member, member) || current_user.can?(:update_group_member, member) ||
current_user.can?(:update_project_member, member) current_user.can?(:update_project_member, member)
end end
def project_creator?(member, access_level)
member.new_record? && member.owner? &&
access_level.to_i == ProjectMember::MASTER
end
end end
def invite? def invite?
......
...@@ -84,7 +84,7 @@ class ProjectMember < Member ...@@ -84,7 +84,7 @@ class ProjectMember < Member
def truncate_teams(project_ids) def truncate_teams(project_ids)
ProjectMember.transaction do ProjectMember.transaction do
members = ProjectMember.where(source_id: project_ids) members = ProjectMember.where(source_id: project_ids)
members.each do |member| members.each do |member|
member.destroy member.destroy
end end
...@@ -133,13 +133,13 @@ class ProjectMember < Member ...@@ -133,13 +133,13 @@ class ProjectMember < Member
event_service.join_project(self.project, self.user) event_service.join_project(self.project, self.user)
notification_service.new_project_member(self) notification_service.new_project_member(self)
end end
super super
end end
def post_update_hook def post_update_hook
if access_level_changed? if access_level_changed?
notification_service.update_project_member(self) notification_service.update_project_member(self)
end end
super super
......
...@@ -180,6 +180,14 @@ class MergeRequest < ActiveRecord::Base ...@@ -180,6 +180,14 @@ class MergeRequest < ActiveRecord::Base
merge_request_diff ? merge_request_diff.first_commit : compare_commits.first merge_request_diff ? merge_request_diff.first_commit : compare_commits.first
end end
def diff_base_commit
if merge_request_diff
merge_request_diff.base_commit
else
self.target_project.commit(self.target_branch)
end
end
def last_commit_short_sha def last_commit_short_sha
last_commit.short_id last_commit.short_id
end end
...@@ -254,7 +262,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -254,7 +262,7 @@ class MergeRequest < ActiveRecord::Base
end end
def mergeable? def mergeable?
return false unless open? && !work_in_progress? return false unless open? && !work_in_progress? && !broken?
check_if_can_be_merged check_if_can_be_merged
...@@ -477,8 +485,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -477,8 +485,7 @@ class MergeRequest < ActiveRecord::Base
end end
def target_sha def target_sha
@target_sha ||= target_project. @target_sha ||= target_project.repository.commit(target_branch).sha
repository.commit(target_branch).sha
end end
def source_sha def source_sha
...@@ -517,4 +524,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -517,4 +524,10 @@ class MergeRequest < ActiveRecord::Base
def ci_commit def ci_commit
@ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project @ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project
end end
def diff_refs
return nil unless diff_base_commit
[diff_base_commit, last_commit]
end
end end
...@@ -73,6 +73,12 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -73,6 +73,12 @@ class MergeRequestDiff < ActiveRecord::Base
commits.last commits.last
end end
def base_commit
return nil unless self.base_commit_sha
merge_request.target_project.commit(self.base_commit_sha)
end
def last_commit_short_sha def last_commit_short_sha
@last_commit_short_sha ||= last_commit.short_id @last_commit_short_sha ||= last_commit.short_id
end end
...@@ -156,6 +162,9 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -156,6 +162,9 @@ class MergeRequestDiff < ActiveRecord::Base
end end
self.st_diffs = new_diffs self.st_diffs = new_diffs
self.base_commit_sha = merge_request.target_project.commit(target_branch).try(:sha)
self.save self.save
end end
......
...@@ -33,7 +33,7 @@ class Note < ActiveRecord::Base ...@@ -33,7 +33,7 @@ class Note < ActiveRecord::Base
participant :author participant :author
belongs_to :project belongs_to :project
belongs_to :noteable, polymorphic: true belongs_to :noteable, polymorphic: true, touch: true
belongs_to :author, class_name: "User" belongs_to :author, class_name: "User"
belongs_to :updated_by, class_name: "User" belongs_to :updated_by, class_name: "User"
...@@ -244,7 +244,7 @@ class Note < ActiveRecord::Base ...@@ -244,7 +244,7 @@ class Note < ActiveRecord::Base
prev_match_line = nil prev_match_line = nil
prev_lines = [] prev_lines = []
diff_lines.each do |line| highlighted_diff_lines.each do |line|
if line.type == "match" if line.type == "match"
prev_lines.clear prev_lines.clear
prev_match_line = line prev_match_line = line
...@@ -261,7 +261,11 @@ class Note < ActiveRecord::Base ...@@ -261,7 +261,11 @@ class Note < ActiveRecord::Base
end end
def diff_lines def diff_lines
@diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.lines.to_a) @diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.lines)
end
def highlighted_diff_lines
Gitlab::Diff::Highlight.new(diff_lines).highlight
end end
def discussion_id def discussion_id
......
...@@ -272,6 +272,10 @@ class Project < ActiveRecord::Base ...@@ -272,6 +272,10 @@ class Project < ActiveRecord::Base
query: "%#{query.try(:downcase)}%") query: "%#{query.try(:downcase)}%")
end end
def search_by_visibility(level)
where(visibility_level: Gitlab::VisibilityLevel.const_get(level.upcase))
end
def search_by_title(query) def search_by_title(query)
where('projects.archived = ?', false).where('LOWER(projects.name) LIKE :query', query: "%#{query.downcase}%") where('projects.archived = ?', false).where('LOWER(projects.name) LIKE :query', query: "%#{query.downcase}%")
end end
...@@ -468,12 +472,9 @@ class Project < ActiveRecord::Base ...@@ -468,12 +472,9 @@ class Project < ActiveRecord::Base
!external_issue_tracker !external_issue_tracker
end end
def external_issues_trackers
services.select(&:issue_tracker?).reject(&:default?)
end
def external_issue_tracker def external_issue_tracker
@external_issues_tracker ||= external_issues_trackers.find(&:activated?) @external_issue_tracker ||=
services.issue_trackers.active.without_defaults.first
end end
def can_have_issues_tracker_id? def can_have_issues_tracker_id?
......
...@@ -23,14 +23,12 @@ ...@@ -23,14 +23,12 @@
# List methods you need to implement to get your CI service # List methods you need to implement to get your CI service
# working with GitLab Merge Requests # working with GitLab Merge Requests
class CiService < Service class CiService < Service
def category default_value_for :category, 'ci'
:ci
end
def valid_token?(token) def valid_token?(token)
self.respond_to?(:token) && self.token.present? && self.token == token self.respond_to?(:token) && self.token.present? && self.token == token
end end
def supported_events def supported_events
%w(push) %w(push)
end end
......
...@@ -24,9 +24,7 @@ class GitlabIssueTrackerService < IssueTrackerService ...@@ -24,9 +24,7 @@ class GitlabIssueTrackerService < IssueTrackerService
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
def default? default_value_for :default, true
true
end
def to_param def to_param
'gitlab' 'gitlab'
......
...@@ -23,12 +23,10 @@ class IssueTrackerService < Service ...@@ -23,12 +23,10 @@ class IssueTrackerService < Service
validates :project_url, :issues_url, :new_issue_url, presence: true, if: :activated? validates :project_url, :issues_url, :new_issue_url, presence: true, if: :activated?
def category default_value_for :category, 'issue_tracker'
:issue_tracker
end
def default? def default?
false default
end end
def issue_url(iid) def issue_url(iid)
......
...@@ -43,6 +43,9 @@ class Service < ActiveRecord::Base ...@@ -43,6 +43,9 @@ class Service < ActiveRecord::Base
validates :project_id, presence: true, unless: Proc.new { |service| service.template? } validates :project_id, presence: true, unless: Proc.new { |service| service.template? }
scope :visible, -> { where.not(type: ['GitlabIssueTrackerService', 'GitlabCiService']) } scope :visible, -> { where.not(type: ['GitlabIssueTrackerService', 'GitlabCiService']) }
scope :issue_trackers, -> { where(category: 'issue_tracker') }
scope :active, -> { where(active: true) }
scope :without_defaults, -> { where(default: false) }
scope :push_hooks, -> { where(push_events: true, active: true) } scope :push_hooks, -> { where(push_events: true, active: true) }
scope :tag_push_hooks, -> { where(tag_push_events: true, active: true) } scope :tag_push_hooks, -> { where(tag_push_events: true, active: true) }
...@@ -51,6 +54,8 @@ class Service < ActiveRecord::Base ...@@ -51,6 +54,8 @@ class Service < ActiveRecord::Base
scope :note_hooks, -> { where(note_events: true, active: true) } scope :note_hooks, -> { where(note_events: true, active: true) }
scope :build_hooks, -> { where(build_events: true, active: true) } scope :build_hooks, -> { where(build_events: true, active: true) }
default_value_for :category, 'common'
def activated? def activated?
active active
end end
...@@ -60,7 +65,7 @@ class Service < ActiveRecord::Base ...@@ -60,7 +65,7 @@ class Service < ActiveRecord::Base
end end
def category def category
:common read_attribute(:category).to_sym
end end
def initialize_properties def initialize_properties
...@@ -153,7 +158,7 @@ class Service < ActiveRecord::Base ...@@ -153,7 +158,7 @@ class Service < ActiveRecord::Base
# Returns a hash of the properties that have been assigned a new value since last save, # Returns a hash of the properties that have been assigned a new value since last save,
# indicating their original values (attr => original value). # indicating their original values (attr => original value).
# ActiveRecord does not provide a mechanism to track changes in serialized keys, # ActiveRecord does not provide a mechanism to track changes in serialized keys,
# so we need a specific implementation for service properties. # so we need a specific implementation for service properties.
# This allows to track changes to properties set with the accessor methods, # This allows to track changes to properties set with the accessor methods,
# but not direct manipulation of properties hash. # but not direct manipulation of properties hash.
...@@ -164,7 +169,7 @@ class Service < ActiveRecord::Base ...@@ -164,7 +169,7 @@ class Service < ActiveRecord::Base
def reset_updated_properties def reset_updated_properties
@updated_properties = nil @updated_properties = nil
end end
def async_execute(data) def async_execute(data)
return unless supported_events.include?(data[:object_kind]) return unless supported_events.include?(data[:object_kind])
......
...@@ -664,7 +664,10 @@ class User < ActiveRecord::Base ...@@ -664,7 +664,10 @@ class User < ActiveRecord::Base
end end
def all_emails def all_emails
[self.email, *self.emails.map(&:email)] all_emails = []
all_emails << self.email unless self.temp_oauth_email?
all_emails.concat(self.emails.map(&:email))
all_emails
end end
def hook_attrs def hook_attrs
......
...@@ -376,10 +376,10 @@ class NotificationService ...@@ -376,10 +376,10 @@ class NotificationService
end end
def reassign_resource_email(target, project, current_user, method) def reassign_resource_email(target, project, current_user, method)
previous_assignee_id = previous_record(target, "assignee_id") previous_assignee_id = previous_record(target, 'assignee_id')
previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
recipients = build_recipients(target, project, current_user, [previous_assignee]) recipients = build_recipients(target, project, current_user, action: :reassign, previous_assignee: previous_assignee)
recipients.each do |recipient| recipients.each do |recipient|
mailer.send( mailer.send(
...@@ -400,22 +400,27 @@ class NotificationService ...@@ -400,22 +400,27 @@ class NotificationService
end end
end end
def build_recipients(target, project, current_user, extra_recipients = nil) def build_recipients(target, project, current_user, action: nil, previous_assignee: nil)
recipients = target.participants(current_user) recipients = target.participants(current_user)
recipients = recipients.concat(extra_recipients).compact.uniq if extra_recipients
recipients = add_project_watchers(recipients, project) recipients = add_project_watchers(recipients, project)
recipients = reject_mention_users(recipients, project) recipients = reject_mention_users(recipients, project)
recipients = reject_muted_users(recipients, project)
# Re-assign is considered as a mention of the new assignee so we add the
# new assignee to the list of recipients after we rejected users with
# the "on mention" notification level
if action == :reassign
recipients << previous_assignee if previous_assignee
recipients << target.assignee
end
recipients = reject_muted_users(recipients, project)
recipients = add_subscribed_users(recipients, target) recipients = add_subscribed_users(recipients, target)
recipients = reject_unsubscribed_users(recipients, target) recipients = reject_unsubscribed_users(recipients, target)
recipients.delete(current_user) recipients.delete(current_user)
recipients = recipients.uniq
recipients recipients.uniq
end end
def mailer def mailer
......
...@@ -212,6 +212,22 @@ ...@@ -212,6 +212,22 @@
%fieldset %fieldset
%legend Spam and Anti-bot Protection %legend Spam and Anti-bot Protection
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :ip_blocking_enabled do
= f.check_box :ip_blocking_enabled
Enable IP check against blacklist at sign-up
.help-block Helps preventing accounts creation from 'known spam sources'
.form-group
= f.label :dnsbl_servers_list, class: 'control-label col-sm-2' do
DNSBL servers list
.col-sm-10
= f.text_field :dnsbl_servers_list, class: 'form-control'
.help-block
Please enter DNSBL servers separated with comma
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
.checkbox .checkbox
...@@ -226,11 +242,30 @@ ...@@ -226,11 +242,30 @@
= f.text_field :recaptcha_site_key, class: 'form-control' = f.text_field :recaptcha_site_key, class: 'form-control'
.help-block .help-block
Generate site and private keys here: Generate site and private keys here:
%a{ href: 'http://www.google.com/recaptcha', target: 'blank'} http://www.google.com/recaptcha %a{ href: 'http://www.google.com/recaptcha', target: '_blank'} http://www.google.com/recaptcha
.form-group .form-group
= f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'control-label col-sm-2' = f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= f.text_field :recaptcha_private_key, class: 'form-control' = f.text_field :recaptcha_private_key, class: 'form-control'
%fieldset
%legend Error Reporting and Logging
%p
These settings require a restart to take effect.
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :sentry_enabled do
= f.check_box :sentry_enabled
Enable Sentry
.help-block
Sentry is an error reporting and logging tool which is currently not shipped with GitLab, get it here:
%a{ href: 'https://getsentry.com', target: '_blank' } https://getsentry.com
.form-group
= f.label :sentry_dsn, 'Sentry DSN', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :sentry_dsn, class: 'form-control'
.form-actions .form-actions
= f.submit 'Save', class: 'btn btn-primary' = f.submit 'Save', class: 'btn btn-primary'
...@@ -5,5 +5,9 @@ ...@@ -5,5 +5,9 @@
-# num_pages: total number of pages -# num_pages: total number of pages
-# per_page: number of items to fetch per page -# per_page: number of items to fetch per page
-# remote: data-remote -# remote: data-remote
%li.next - if current_page.last?
= link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, rel: 'next', remote: remote %li{ class: "next disabled" }
%span= raw(t 'views.pagination.next')
- else
%li{ class: "next" }
= link_to raw(t 'views.pagination.next'), url, rel: 'next', remote: remote
...@@ -10,13 +10,13 @@ ...@@ -10,13 +10,13 @@
%ul.pagination.clearfix %ul.pagination.clearfix
- unless current_page.first? - unless current_page.first?
= first_page_tag unless num_pages < 5 # As kaminari will always show the first 5 pages = first_page_tag unless num_pages < 5 # As kaminari will always show the first 5 pages
= prev_page_tag = prev_page_tag
- each_page do |page| - each_page do |page|
- if page.left_outer? || page.right_outer? || page.inside_window? - if page.left_outer? || page.right_outer? || page.inside_window?
= page_tag page = page_tag page
- elsif !page.was_truncated? - elsif !page.was_truncated?
= gap_tag = gap_tag
= next_page_tag
- unless current_page.last? - unless current_page.last?
= next_page_tag
= last_page_tag unless num_pages < 5 = last_page_tag unless num_pages < 5
...@@ -5,5 +5,9 @@ ...@@ -5,5 +5,9 @@
-# num_pages: total number of pages -# num_pages: total number of pages
-# per_page: number of items to fetch per page -# per_page: number of items to fetch per page
-# remote: data-remote -# remote: data-remote
%li{class: "prev" } - if current_page.first?
= link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remote %li{ class: "prev disabled" }
%span= raw(t 'views.pagination.previous')
- else
%li{ class: "prev" }
= link_to raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remote
...@@ -37,3 +37,6 @@ ...@@ -37,3 +37,6 @@
%h1.title= title %h1.title= title
= render 'shared/outdated_browser' = render 'shared/outdated_browser'
- if @project && !@project.empty_repo?
:javascript
var findFileURL = "#{namespace_project_find_file_path(@project.namespace, @project, @ref || @project.repository.root_ref)}";
\ No newline at end of file
...@@ -21,10 +21,10 @@ ...@@ -21,10 +21,10 @@
.form-group .form-group
= f.label :email, class: "control-label" = f.label :email, class: "control-label"
.col-sm-10 .col-sm-10
- if @user.ldap_user? - if @user.ldap_user? && @user.ldap_email?
= f.text_field :email, class: "form-control", required: true, readonly: true = f.text_field :email, class: "form-control", required: true, readonly: true
%span.help-block.light %span.help-block.light
Email is read-only for LDAP user Your email address was automatically set based on the LDAP server.
- else - else
- if @user.temp_oauth_email? - if @user.temp_oauth_email?
= f.text_field :email, class: "form-control", required: true, value: nil = f.text_field :email, class: "form-control", required: true, value: nil
......
...@@ -6,4 +6,3 @@ ...@@ -6,4 +6,3 @@
%span.str-truncated %span.str-truncated
= link_to directory.name, path_to_directory = link_to directory.name, path_to_directory
%td %td
%td
...@@ -7,5 +7,3 @@ ...@@ -7,5 +7,3 @@
= link_to file.name, path_to_file = link_to file.name, path_to_file
%td %td
= number_to_human_size(file.metadata[:size], precision: 2) = number_to_human_size(file.metadata[:size], precision: 2)
%td
= number_to_human_size(file.metadata[:zipped], precision: 2)
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.top-block.gray-content-block.clearfix .top-block.gray-content-block.clearfix
.pull-right .pull-right
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build), = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build),
class: 'btn btn-default' do class: 'btn btn-default download' do
= icon('download') = icon('download')
Download artifacts archive Download artifacts archive
...@@ -15,14 +15,8 @@ ...@@ -15,14 +15,8 @@
%tr %tr
%th Name %th Name
%th Size %th Size
%th Compressed to
= render partial: 'tree_directory', collection: @entry.directories(parent: true), as: :directory = render partial: 'tree_directory', collection: @entry.directories(parent: true), as: :directory
= render partial: 'tree_file', collection: @entry.files, as: :file = render partial: 'tree_file', collection: @entry.files, as: :file
- if @entry.empty? - if @entry.empty?
.center Empty .center Empty
:javascript
$(document).on('click', 'tr[data-link]', function(e) {
window.location = this.dataset.link;
});
...@@ -12,9 +12,10 @@ ...@@ -12,9 +12,10 @@
%small= number_to_human_size @blob.size %small= number_to_human_size @blob.size
.file-actions .file-actions
= render "projects/blob/actions" = render "projects/blob/actions"
.file-content.blame.highlight .file-content.blame.code.js-syntax-highlight
%table %table
- current_line = 1 - current_line = 1
- blame_highlighter = highlighter(@blob.name, @blob.data, nowrap: true)
- @blame.each do |blame_group| - @blame.each do |blame_group|
%tr %tr
%td.blame-commit %td.blame-commit
...@@ -30,16 +31,15 @@ ...@@ -30,16 +31,15 @@
= commit_author_link(commit, avatar: false) = commit_author_link(commit, avatar: false)
authored authored
#{time_ago_with_tooltip(commit.committed_date, skip_js: true)} #{time_ago_with_tooltip(commit.committed_date, skip_js: true)}
%td.lines.blame-numbers %td.line-numbers
%pre - line_count = blame_group[:lines].count
- line_count = blame_group[:lines].count - (current_line...(current_line + line_count)).each do |i|
- (current_line...(current_line + line_count)).each do |i| %a.diff-line-num= i
= i \
\ - current_line += line_count
- current_line += line_count
%td.lines %td.lines
%pre{class: 'code highlight white'} %pre{class: 'code highlight'}
%code %code
- blame_group[:lines].each do |line| - blame_group[:lines].each do |line|
:erb :preserve
<%= highlight(@blob.name, line, nowrap: true, continue: true).html_safe %> #{blame_highlighter.highlight(line)}
...@@ -10,8 +10,9 @@ ...@@ -10,8 +10,9 @@
%tr.line_holder %tr.line_holder
%td.old_line.diff-line-num{data: {linenumber: line_old}} %td.old_line.diff-line-num{data: {linenumber: line_old}}
= link_to raw(line_old), "#" = link_to raw(line_old), "#"
%td.new_line= link_to raw(line_new) , "#" %td.new_line.diff-line-num
%td.line_content.noteable_line= ' ' * @form.indent + line = link_to raw(line_new) , "#"
%td.line_content.noteable_line==#{' ' * @form.indent}#{line}
- if @form.unfold? && @form.bottom? && @form.to < @blob.loc - if @form.unfold? && @form.bottom? && @form.to < @blob.loc
%tr.line_holder{ id: @form.to } %tr.line_holder{ id: @form.to }
......
...@@ -14,12 +14,12 @@ ...@@ -14,12 +14,12 @@
- @diff_lines.each do |line| - @diff_lines.each do |line|
%tr.line_holder{ class: "#{line.type}" } %tr.line_holder{ class: "#{line.type}" }
- if line.type == "match" - if line.type == "match"
%td.old_line= "..." %td.old_line.diff-line-num= "..."
%td.new_line= "..." %td.new_line.diff-line-num= "..."
%td.line_content.matched= line.text %td.line_content.match= line.text
- else - else
%td.old_line %td.old_line.diff-line-num
%td.new_line %td.new_line.diff-line-num
%td.line_content{class: "#{line.type}"}= raw diff_line_content(line.text) %td.line_content{class: "#{line.type}"}= diff_line_content(line.text)
- else - else
.nothing-here-block No changes. .nothing-here-block No changes.
...@@ -6,7 +6,12 @@ ...@@ -6,7 +6,12 @@
- if can?(current_user, :manage_builds, @project) - if can?(current_user, :manage_builds, @project)
.pull-left.hidden-xs .pull-left.hidden-xs
- if @all_builds.running_or_pending.any? - if @all_builds.running_or_pending.any?
= link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post = link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project),
data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
= link_to ci_lint_path, class: 'btn btn-default' do
= icon('wrench')
%span CI Lint
%ul.nav-links %ul.nav-links
%li{class: ('active' if @scope.nil?)} %li{class: ('active' if @scope.nil?)}
......
...@@ -96,7 +96,7 @@ ...@@ -96,7 +96,7 @@
.center .center
.btn-group{ role: :group } .btn-group{ role: :group }
= link_to "Download", @build.artifacts_download_url, class: 'btn btn-sm btn-primary' = link_to "Download", @build.artifacts_download_url, class: 'btn btn-sm btn-primary'
- if @build.artifacts_browser_supported? - if @build.artifacts_metadata?
= link_to "Browse", @build.artifacts_browse_url, class: 'btn btn-sm btn-primary' = link_to "Browse", @build.artifacts_browse_url, class: 'btn btn-sm btn-primary'
.build-widget .build-widget
......
...@@ -9,5 +9,6 @@ ...@@ -9,5 +9,6 @@
= render "ci_menu" = render "ci_menu"
- else - else
%div.block-connector %div.block-connector
= render "projects/diffs/diffs", diffs: @diffs, project: @project = render "projects/diffs/diffs", diffs: @diffs, project: @project,
diff_refs: @diff_refs
= render "projects/notes/notes_with_form" = render "projects/notes/notes_with_form"
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
- if @commits.present? - if @commits.present?
.prepend-top-default .prepend-top-default
= render "projects/commits/commit_list" = render "projects/commits/commit_list"
= render "projects/diffs/diffs", diffs: @diffs, project: @project = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @diff_refs
- else - else
.light-well.prepend-top-default .light-well.prepend-top-default
.center .center
......
- if diff_view == 'parallel' - if diff_view == 'parallel'
- fluid_layout true - fluid_layout true
- diff_files = safe_diff_files(diffs) - diff_files = safe_diff_files(diffs, diff_refs)
.content-block.oneline-block .content-block.oneline-block
.inline-parallel-buttons .inline-parallel-buttons
......
.diff-file{id: "diff-#{i}", data: diff_file_html_data(project, diff_commit, diff_file)} .diff-file.file-holder{id: "diff-#{i}", data: diff_file_html_data(project, diff_commit, diff_file)}
.diff-header{id: "file-path-#{hexdigest(diff_file.file_path)}"} .file-title{id: "file-path-#{hexdigest(diff_file.file_path)}"}
- if diff_file.diff.submodule? - if diff_file.diff.submodule?
%span %span
= icon('archive fw') = icon('archive fw')
%strong %strong
= submodule_link(blob, @commit.id, project.repository) = submodule_link(blob, @commit.id, project.repository)
- else - else
%span = blob_icon blob.mode, blob.name
= blob_icon blob.mode, blob.name = link_to "#diff-#{i}" do
= link_to "#diff-#{i}" do %strong
%strong = diff_file.new_path
= diff_file.new_path
- if diff_file.deleted_file - if diff_file.deleted_file
deleted deleted
- elsif diff_file.renamed_file - elsif diff_file.renamed_file
renamed from renamed from
%strong %strong
= diff_file.old_path = diff_file.old_path
- if diff_file.mode_changed? - if diff_file.mode_changed?
%small %small
= "#{diff_file.diff.a_mode}#{diff_file.diff.b_mode}" = "#{diff_file.diff.a_mode}#{diff_file.diff.b_mode}"
.diff-controls .file-actions.hidden-xs
- if blob_text_viewable?(blob) - if blob_text_viewable?(blob)
= link_to '#', class: 'js-toggle-diff-comments btn btn-sm active has_tooltip', title: "Toggle comments for this file" do = link_to '#', class: 'js-toggle-diff-comments btn active has_tooltip', title: "Toggle comments for this file" do
%i.fa.fa-comments = icon('comments')
&nbsp; \
- if editable_diff?(diff_file) - if editable_diff?(diff_file)
= edit_blob_link(@merge_request.source_project, = edit_blob_link(@merge_request.source_project,
@merge_request.source_branch, diff_file.new_path, @merge_request.source_branch, diff_file.new_path,
from_merge_request_id: @merge_request.id) from_merge_request_id: @merge_request.id)
&nbsp;
= view_file_btn(diff_commit.id, diff_file, project) = view_file_btn(diff_commit.id, diff_file, project)
......
...@@ -4,4 +4,4 @@ ...@@ -4,4 +4,4 @@
%td.new_line.diff-line-num{data: {linenumber: line_new}, %td.new_line.diff-line-num{data: {linenumber: line_new},
class: [unfold_bottom_class(bottom), unfold_class(!new_file)]} class: [unfold_bottom_class(bottom), unfold_class(!new_file)]}
\... \...
%td.line_content.matched= line %td.line_content.match= line
%td.old_line %td.old_line.diff-line-num
%td.line_content.parallel.matched= line %td.line_content.parallel.match= line
%td.new_line %td.new_line.diff-line-num
%td.line_content.parallel.matched= line %td.line_content.parallel.match= line
/ Side-by-side diff view / Side-by-side diff view
%div.text-file.diff-wrap-lines %div.text-file.diff-wrap-lines.code.file-content.js-syntax-highlight
%table %table
- parallel_diff(diff_file, index).each do |line| - diff_file.parallel_diff_lines.each do |line|
- type_left = line[0] - left = line[:left]
- line_number_left = line[1] - right = line[:right]
- line_content_left = line[2]
- line_code_left = line[3]
- type_right = line[4]
- line_number_right = line[5]
- line_content_right = line[6]
- line_code_right = line[7]
%tr.line_holder.parallel %tr.line_holder.parallel
- if type_left == 'match' - if left[:type] == 'match'
= render "projects/diffs/match_line_parallel", { line: line_content_left, = render "projects/diffs/match_line_parallel", { line: left[:text],
line_old: line_number_left, line_new: line_number_right } line_old: left[:number], line_new: right[:number] }
- elsif type_left == 'old' || type_left.nil? - elsif left[:type] == 'nonewline'
%td.old_line{id: line_code_left, class: "#{type_left}"} %td.old_line.diff-line-num
= link_to raw(line_number_left), "##{line_code_left}", id: line_code_left %td.line_content.parallel.match= left[:text]
%td.new_line.diff-line-num
%td.line_content.parallel.match= left[:text]
- else
%td.old_line.diff-line-num{id: left[:line_code], class: "#{left[:type]}"}
= link_to raw(left[:number]), "##{left[:line_code]}", id: left[:line_code]
- if @comments_allowed && can?(current_user, :create_note, @project) - if @comments_allowed && can?(current_user, :create_note, @project)
= link_to_new_diff_note(line_code_left, 'old') = link_to_new_diff_note(left[:line_code], 'old')
%td.line_content{class: "parallel noteable_line #{type_left} #{line_code_left}", "line_code" => line_code_left }= raw line_content_left %td.line_content{class: "parallel noteable_line #{left[:type]} #{left[:line_code]}", data: { line_code: left[:line_code] }}= diff_line_content(left[:text])
- if type_right == 'new' - if right[:type] == 'new'
- new_line_class = 'new' - new_line_class = 'new'
- new_line_code = line_code_right - new_line_code = right[:line_code]
- else - else
- new_line_class = nil - new_line_class = nil
- new_line_code = line_code_left - new_line_code = left[:line_code]
%td.new_line{id: new_line_code, class: "#{new_line_class}", data: { linenumber: line_number_right }} %td.new_line.diff-line-num{id: new_line_code, class: "#{new_line_class}", data: { linenumber: right[:number] }}
= link_to raw(line_number_right), "##{new_line_code}", id: new_line_code = link_to raw(right[:number]), "##{new_line_code}", id: new_line_code
- if @comments_allowed && can?(current_user, :create_note, @project) - if @comments_allowed && can?(current_user, :create_note, @project)
= link_to_new_diff_note(line_code_right, 'new') = link_to_new_diff_note(right[:line_code], 'new')
%td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code}", "line_code" => new_line_code}= raw line_content_right %td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code}", data: { line_code: new_line_code }}= diff_line_content(right[:text])
- if @reply_allowed - if @reply_allowed
- comments_left, comments_right = organize_comments(type_left, type_right, line_code_left, line_code_right) - comments_left, comments_right = organize_comments(left[:type], right[:type], left[:line_code], right[:line_code])
- if comments_left.present? || comments_right.present? - if comments_left.present? || comments_right.present?
= render "projects/notes/diff_notes_with_reply_parallel", notes_left: comments_left, notes_right: comments_right = render "projects/notes/diff_notes_with_reply_parallel", notes_left: comments_left, notes_right: comments_right
......
...@@ -3,9 +3,11 @@ ...@@ -3,9 +3,11 @@
.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{class: "#{'hide' if too_big}"} %table.text-file.code.js-syntax-highlight{ class: too_big ? 'hide' : '' }
- last_line = 0 - last_line = 0
- diff_file.diff_lines.each_with_index do |line, index| - raw_diff_lines = diff_file.diff_lines
- diff_file.highlighted_diff_lines.each_with_index do |line, index|
- type = line.type - type = line.type
- last_line = line.new_pos - last_line = line.new_pos
- line_code = generate_line_code(diff_file.file_path, line) - line_code = generate_line_code(diff_file.file_path, line)
...@@ -14,19 +16,23 @@ ...@@ -14,19 +16,23 @@
- if type == "match" - if type == "match"
= render "projects/diffs/match_line", {line: line.text, = render "projects/diffs/match_line", {line: line.text,
line_old: line_old, line_new: line.new_pos, bottom: false, new_file: diff_file.new_file} line_old: line_old, line_new: line.new_pos, bottom: false, new_file: diff_file.new_file}
- elsif type == 'nonewline'
%td.old_line.diff-line-num
%td.new_line.diff-line-num
%td.line_content.match= line.text
- else - else
%td.old_line %td.old_line.diff-line-num{class: type}
= link_to raw(type == "new" ? "&nbsp;" : line_old), "##{line_code}", id: line_code = link_to raw(type == "new" ? "&nbsp;" : line_old), "##{line_code}", id: line_code
- if @comments_allowed && can?(current_user, :create_note, @project) - if @comments_allowed && can?(current_user, :create_note, @project)
= link_to_new_diff_note(line_code) = link_to_new_diff_note(line_code)
%td.new_line{data: {linenumber: line.new_pos}} %td.new_line.diff-line-num{class: type, data: {linenumber: line.new_pos}}
= link_to raw(type == "old" ? "&nbsp;" : line.new_pos) , "##{line_code}", id: line_code = link_to raw(type == "old" ? "&nbsp;" : line.new_pos), "##{line_code}", id: line_code
%td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line.text) %td.line_content{class: "noteable_line #{type} #{line_code}", data: { line_code: line_code }}= diff_line_content(line.text)
- if @reply_allowed - if @reply_allowed
- comments = @line_notes.select { |n| n.line_code == line_code && n.active? }.sort_by(&:created_at) - comments = @line_notes.select { |n| n.line_code == line_code && n.active? }.sort_by(&:created_at)
- unless comments.empty? - unless comments.empty?
= render "projects/notes/diff_notes_with_reply", notes: comments, line: line.text = render "projects/notes/diff_notes_with_reply", notes: comments, line: raw_diff_lines[index].text
- if last_line > 0 - if last_line > 0
= render "projects/diffs/match_line", {line: "", = render "projects/diffs/match_line", {line: "",
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
= link_to namespace_project_tree_path(@project.namespace, @project, @ref) do = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
= @project.path = @project.path
%li.file-finder %li.file-finder
%input#file_find.form-control.file-finder-input{type: "text", placeholder: 'Find by path'} %input#file_find.form-control.file-finder-input{type: "text", placeholder: 'Find by path', autocomplete: 'off'}
%div.tree-content-holder %div.tree-content-holder
.table-holder .table-holder
......
...@@ -5,9 +5,4 @@ ...@@ -5,9 +5,4 @@
.nothing-here-block No issues to show .nothing-here-block No issues to show
- if @issues.present? - if @issues.present?
.issuable-filter-count
%span.pull-right
= number_with_delimiter(@issues.total_count)
issues for this filter
= paginate @issues, theme: "gitlab" = paginate @issues, theme: "gitlab"
...@@ -5,10 +5,5 @@ ...@@ -5,10 +5,5 @@
.nothing-here-block No merge requests to show .nothing-here-block No merge requests to show
- if @merge_requests.present? - if @merge_requests.present?
.issuable-filter-count
%span.pull-right
= number_with_delimiter(@merge_requests.total_count)
merge requests for this filter
= paginate @merge_requests, theme: "gitlab" = paginate @merge_requests, theme: "gitlab"
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
= render "projects/merge_requests/show/commits" = render "projects/merge_requests/show/commits"
#diffs.diffs.tab-pane.active #diffs.diffs.tab-pane.active
- if @diffs.present? - if @diffs.present?
= render "projects/diffs/diffs", diffs: @diffs, project: @project = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @merge_request.diff_refs
- elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
.alert.alert-danger .alert.alert-danger
%h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits. %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits.
......
- if @merge_request_diff.collected? - if @merge_request_diff.collected?
= render "projects/diffs/diffs", diffs: params[:w] == '1' ? @merge_request.diffs_no_whitespace : @merge_request.diffs, project: @merge_request.project = render "projects/diffs/diffs", diffs: params[:w] == '1' ? @merge_request.diffs_no_whitespace : @merge_request.diffs,
project: @merge_request.project, diff_refs: @merge_request.diff_refs
- elsif @merge_request_diff.empty? - elsif @merge_request_diff.empty?
.nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch} .nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
- else - else
......
...@@ -5,6 +5,16 @@ ...@@ -5,6 +5,16 @@
.js-main-target-form .js-main-target-form
- if can? current_user, :create_note, @project - if can? current_user, :create_note, @project
= render "projects/notes/form", view: diff_view = render "projects/notes/form", view: diff_view
- else
.disabled-comment-area
.disabled-profile
.disabled-comment
%span
Please
= link_to "register",new_user_session_path
or
= link_to "login",new_user_session_path
to post a comment
:javascript :javascript
var notes = new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}") var notes = new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}")
...@@ -16,15 +16,15 @@ ...@@ -16,15 +16,15 @@
- line_code = generate_line_code(note.file_path, line) - line_code = generate_line_code(note.file_path, line)
%tr.line_holder{ id: line_code, class: "#{type}" } %tr.line_holder{ id: line_code, class: "#{type}" }
- if type == "match" - if type == "match"
%td.old_line= "..." %td.old_line.diff-line-num= "..."
%td.new_line= "..." %td.new_line.diff-line-num= "..."
%td.line_content.matched= line.text %td.line_content.match= line.text
- else - else
%td.old_line %td.old_line.diff-line-num
= raw(type == "new" ? "&nbsp;" : line.old_pos) = raw(type == "new" ? "&nbsp;" : line.old_pos)
%td.new_line %td.new_line.diff-line-num
= raw(type == "old" ? "&nbsp;" : line.new_pos) = raw(type == "old" ? "&nbsp;" : line.new_pos)
%td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line.text) %td.line_content{class: "noteable_line #{type} #{line_code}", line_code: line_code}= diff_line_content(line.text)
- if line_code == note.line_code - if line_code == note.line_code
= render "projects/notes/diff_notes_with_reply", notes: discussion_notes = render "projects/notes/diff_notes_with_reply", notes: discussion_notes
...@@ -22,29 +22,27 @@ ...@@ -22,29 +22,27 @@
.file-content.code .file-content.code
.nothing-here-block Empty file .nothing-here-block Empty file
- else - else
.file-content.code .file-content.code.js-syntax-highlight
%div.highlighted-data{ class: user_color_scheme } .line-numbers
.line-numbers - snippet_blob[:snippet_chunks].each do |snippet|
- unless snippet[:data].empty?
- snippet[:data].lines.to_a.size.times do |index|
- offset = defined?(snippet[:start_line]) ? snippet[:start_line] : 1
- i = index + offset
= link_to snippet_path+"#L#{i}", id: "L#{i}", rel: "#L#{i}", class: "diff-line-num" do
%i.fa.fa-link
= i
- unless snippet == snippet_blob[:snippet_chunks].last
%a.diff-line-num
= "."
%pre.code
%code
- snippet_blob[:snippet_chunks].each do |snippet| - snippet_blob[:snippet_chunks].each do |snippet|
- unless snippet[:data].empty? - unless snippet[:data].empty?
- snippet[:data].lines.to_a.size.times do |index| = snippet[:data]
- offset = defined?(snippet[:start_line]) ? snippet[:start_line] : 1
- i = index + offset
= link_to snippet_path+"#L#{i}", id: "L#{i}", rel: "#L#{i}" do
%i.fa.fa-link
= i
- unless snippet == snippet_blob[:snippet_chunks].last - unless snippet == snippet_blob[:snippet_chunks].last
%a %a
= "." = "..."
.highlight.term - else
%pre .file-content.code
%code .nothing-here-block Empty file
- snippet_blob[:snippet_chunks].each do |snippet|
- unless snippet[:data].empty?
= snippet[:data]
- unless snippet == snippet_blob[:snippet_chunks].last
%a
= "..."
- else
.file-content.code
.nothing-here-block Empty file
.file-content.code.js-syntax-highlight{ class: user_color_scheme } .file-content.code.js-syntax-highlight
.line-numbers .line-numbers
- if blob.data.present? - if blob.data.present?
- blob.data.lines.each_index do |index| - blob.data.lines.each_index do |index|
- offset = defined?(first_line_number) ? first_line_number : 1 - offset = defined?(first_line_number) ? first_line_number : 1
- i = index + offset - i = index + offset
-# We're not using `link_to` because it is too slow once we get to thousands of lines. -# We're not using `link_to` because it is too slow once we get to thousands of lines.
%a{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i} %a.diff-line-num{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}
%i.fa.fa-link %i.fa.fa-link
= i = i
.blob-content{data: {blob_id: blob.id}} .blob-content{data: {blob_id: blob.id}}
:preserve = highlight(blob.name, blob.data)
#{highlight(blob.name, blob.data)}
...@@ -10,6 +10,9 @@ ...@@ -10,6 +10,9 @@
.value .value
- if issuable.assignee - if issuable.assignee
%strong= link_to_member(@project, issuable.assignee, size: 24) %strong= link_to_member(@project, issuable.assignee, size: 24)
- if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee)
%a.pull-right.cannot-be-merged{href: '#', data: {toggle: 'tooltip'}, title: 'Not allowed to merge'}
= icon('exclamation-triangle')
- else - else
.light None .light None
......
...@@ -7,8 +7,11 @@ if defined?(Unicorn) ...@@ -7,8 +7,11 @@ if defined?(Unicorn)
# Unicorn self-process killer # Unicorn self-process killer
require 'unicorn/worker_killer' require 'unicorn/worker_killer'
min = (ENV['GITLAB_UNICORN_MEMORY_MIN'] || 300 * 1 << 20).to_i
max = (ENV['GITLAB_UNICORN_MEMORY_MAX'] || 350 * 1 << 20).to_i
# Max memory size (RSS) per worker # Max memory size (RSS) per worker
use Unicorn::WorkerKiller::Oom, (200 * (1 << 20)), (250 * (1 << 20)) use Unicorn::WorkerKiller::Oom, min, max
end end
end end
......
...@@ -49,6 +49,7 @@ if Gitlab::Metrics.enabled? ...@@ -49,6 +49,7 @@ if Gitlab::Metrics.enabled?
config.instrument_instance_methods(Gitlab::Shell) config.instrument_instance_methods(Gitlab::Shell)
config.instrument_methods(Gitlab::Git) config.instrument_methods(Gitlab::Git)
config.instrument_instance_methods(Gitlab::Git::Repository)
Gitlab::Git.constants.each do |name| Gitlab::Git.constants.each do |name|
const = Gitlab::Git.const_get(name) const = Gitlab::Git.const_get(name)
......
# Be sure to restart your server when you modify this file.
require 'gitlab/current_settings'
include Gitlab::CurrentSettings
if Rails.env.production?
# allow it to fail: it may do so when create_from_defaults is executed before migrations are actually done
begin
sentry_enabled = current_application_settings.sentry_enabled
rescue
sentry_enabled = false
end
if sentry_enabled
Raven.configure do |config|
config.dsn = current_application_settings.sentry_dsn
end
end
end
...@@ -8,3 +8,7 @@ en: ...@@ -8,3 +8,7 @@ en:
wrong_size: "is the wrong size (should be %{file_size})" wrong_size: "is the wrong size (should be %{file_size})"
size_too_small: "is too small (should be at least %{file_size})" size_too_small: "is too small (should be at least %{file_size})"
size_too_big: "is too big (should be at most %{file_size})" size_too_big: "is too big (should be at most %{file_size})"
views:
pagination:
previous: "Prev"
next: "Next"
class AddSentryToApplicationSettings < ActiveRecord::Migration
def change
change_table :application_settings do |t|
t.boolean :sentry_enabled, default: false
t.string :sentry_dsn
end
end
end
class AddIpBlockingSettingsToApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :ip_blocking_enabled, :boolean, default: false
add_column :application_settings, :dnsbl_servers_list, :text
end
end
class AddServicesCategory < ActiveRecord::Migration
def up
add_column :services, :category, :string, default: 'common', null: false
category = quote_column_name('category')
type = quote_column_name('type')
execute <<-EOF
UPDATE services
SET #{category} = 'issue_tracker'
WHERE #{type} IN (
'CustomIssueTrackerService',
'GitlabIssueTrackerService',
'IssueTrackerService',
'JiraService',
'RedmineService'
);
EOF
execute <<-EOF
UPDATE services
SET #{category} = 'ci'
WHERE #{type} IN (
'BambooService',
'BuildkiteService',
'CiService',
'DroneCiService',
'GitlabCiService',
'TeamcityService'
);
EOF
add_index :services, :category
end
def down
remove_column :services, :category
end
end
class AddServicesDefault < ActiveRecord::Migration
def up
add_column :services, :default, :boolean, default: false
default = quote_column_name('default')
type = quote_column_name('type')
execute <<-EOF
UPDATE services
SET #{default} = true
WHERE #{type} = 'GitlabIssueTrackerService'
EOF
add_index :services, :default
end
def down
remove_column :services, :default
end
end
class AddLdapEmailToUsers < ActiveRecord::Migration
def up
add_column :users, :ldap_email, :boolean, default: false, null: false
if Gitlab::Database.mysql?
execute %{
UPDATE users, identities
SET users.ldap_email = TRUE
WHERE identities.user_id = users.id
AND users.email LIKE 'temp-email-for-oauth%'
AND identities.provider LIKE 'ldap%'
AND identities.extern_uid IS NOT NULL
}
else
execute %{
UPDATE users
SET ldap_email = TRUE
FROM identities
WHERE identities.user_id = users.id
AND users.email LIKE 'temp-email-for-oauth%'
AND identities.provider LIKE 'ldap%'
AND identities.extern_uid IS NOT NULL
}
end
end
def down
remove_column :users, :ldap_email
end
end
class AddBaseCommitShaToMergeRequestDiffs < ActiveRecord::Migration
def change
add_column :merge_request_diffs, :base_commit_sha, :string
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160113111034) do ActiveRecord::Schema.define(version: 20160120172143) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -62,6 +62,10 @@ ActiveRecord::Schema.define(version: 20160113111034) do ...@@ -62,6 +62,10 @@ ActiveRecord::Schema.define(version: 20160113111034) do
t.string "recaptcha_private_key" t.string "recaptcha_private_key"
t.integer "metrics_port", default: 8089 t.integer "metrics_port", default: 8089
t.integer "metrics_sample_interval", default: 15 t.integer "metrics_sample_interval", default: 15
t.boolean "sentry_enabled", default: false
t.string "sentry_dsn"
t.boolean "ip_blocking_enabled", default: false
t.text "dnsbl_servers_list"
end end
create_table "audit_events", force: :cascade do |t| create_table "audit_events", force: :cascade do |t|
...@@ -490,6 +494,7 @@ ActiveRecord::Schema.define(version: 20160113111034) do ...@@ -490,6 +494,7 @@ ActiveRecord::Schema.define(version: 20160113111034) do
t.integer "merge_request_id", null: false t.integer "merge_request_id", null: false
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.string "base_commit_sha"
end end
add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree
...@@ -725,20 +730,24 @@ ActiveRecord::Schema.define(version: 20160113111034) do ...@@ -725,20 +730,24 @@ ActiveRecord::Schema.define(version: 20160113111034) do
t.string "type" t.string "type"
t.string "title" t.string "title"
t.integer "project_id" t.integer "project_id"
t.datetime "created_at" t.datetime "created_at", null: false
t.datetime "updated_at" t.datetime "updated_at", null: false
t.boolean "active", default: false, null: false t.boolean "active", null: false
t.text "properties" t.text "properties"
t.boolean "template", default: false t.boolean "template", default: false
t.boolean "push_events", default: true t.boolean "push_events", default: true
t.boolean "issues_events", default: true t.boolean "issues_events", default: true
t.boolean "merge_requests_events", default: true t.boolean "merge_requests_events", default: true
t.boolean "tag_push_events", default: true t.boolean "tag_push_events", default: true
t.boolean "note_events", default: true, null: false t.boolean "note_events", default: true, null: false
t.boolean "build_events", default: false, null: false t.boolean "build_events", default: false, null: false
t.string "category", default: "common", null: false
t.boolean "default", default: false
end end
add_index "services", ["category"], name: "index_services_on_category", using: :btree
add_index "services", ["created_at", "id"], name: "index_services_on_created_at_and_id", using: :btree add_index "services", ["created_at", "id"], name: "index_services_on_created_at_and_id", using: :btree
add_index "services", ["default"], name: "index_services_on_default", using: :btree
add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree
add_index "services", ["template"], name: "index_services_on_template", using: :btree add_index "services", ["template"], name: "index_services_on_template", using: :btree
...@@ -850,6 +859,7 @@ ActiveRecord::Schema.define(version: 20160113111034) do ...@@ -850,6 +859,7 @@ ActiveRecord::Schema.define(version: 20160113111034) do
t.boolean "hide_project_limit", default: false t.boolean "hide_project_limit", default: false
t.string "unlock_token" t.string "unlock_token"
t.datetime "otp_grace_period_started_at" t.datetime "otp_grace_period_started_at"
t.boolean "ldap_email", default: false, null: false
end end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
- [User permissions](ci/permissions/README.md) - [User permissions](ci/permissions/README.md)
- [API](ci/api/README.md) - [API](ci/api/README.md)
- [Triggering builds through the API](ci/triggers/README.md) - [Triggering builds through the API](ci/triggers/README.md)
- [Build artifacts](ci/build_artifacts/README.md)
### CI Languages ### CI Languages
...@@ -53,6 +54,7 @@ ...@@ -53,6 +54,7 @@
- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough. - [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough.
- [Install](install/README.md) Requirements, directory structures and installation from source. - [Install](install/README.md) Requirements, directory structures and installation from source.
- [Restart GitLab](administration/restart_gitlab.md) Learn how to restart GitLab and its components
- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter. - [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter.
- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages. - [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars. - [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
...@@ -67,6 +69,8 @@ ...@@ -67,6 +69,8 @@
- [Reply by email](incoming_email/README.md) Allow users to comment on issues and merge requests by replying to notification emails. - [Reply by email](incoming_email/README.md) Allow users to comment on issues and merge requests by replying to notification emails.
- [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE. - [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE.
- [Git LFS configuration](workflow/lfs/lfs_administration.md) - [Git LFS configuration](workflow/lfs/lfs_administration.md)
- [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast.
- [GitLab Performance Monitoring](monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics
## Contributor documentation ## Contributor documentation
......
...@@ -17,6 +17,8 @@ DATABASE_URL | url | For example: postgresql://localhost/blog_development?pool=5 ...@@ -17,6 +17,8 @@ DATABASE_URL | url | For example: postgresql://localhost/blog_development?pool=5
GITLAB_EMAIL_FROM | email | Email address used in the "From" field in mails sent by GitLab GITLAB_EMAIL_FROM | email | Email address used in the "From" field in mails sent by GitLab
GITLAB_EMAIL_DISPLAY_NAME | string | Name used in the "From" field in mails sent by GitLab GITLAB_EMAIL_DISPLAY_NAME | string | Name used in the "From" field in mails sent by GitLab
GITLAB_EMAIL_REPLY_TO | email | Email address used in the "Reply-To" field in mails sent by GitLab GITLAB_EMAIL_REPLY_TO | email | Email address used in the "Reply-To" field in mails sent by GitLab
GITLAB_UNICORN_MEMORY_MIN | integer | The minimum memory threshold (in bytes) for the Unicorn worker killer
GITLAB_UNICORN_MEMORY_MAX | integer | The maximum memory threshold (in bytes) for the Unicorn worker killer
## Complete database variables ## Complete database variables
......
# Housekeeping
_**Note:** This feature was [introduced][ce-2371] in GitLab 8.4_
---
The housekeeping function runs [`git gc`][man] on the current project Git
repository.
`git gc` runs a number of housekeeping tasks, such as compressing file
revisions (to reduce disk space and increase performance) and removing
unreachable objects which may have been created from prior invocations of
`git add`.
You can find this option under your **[Project] > Settings**.
---
![Housekeeping settings](img/housekeeping_settings.png)
[ce-2371]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2371 "Housekeeping merge request"
[man]: https://www.kernel.org/pub/software/scm/git/docs/git-gc.html "git gc man page"
# How to restart GitLab
Depending on how you installed GitLab, there are different methods to restart
its service(s).
If you want the TL;DR versions, jump to:
- [Omnibus GitLab restart](#omnibus-gitlab-restart)
- [Omnibus GitLab reconfigure](#omnibus-gitlab-reconfigure)
- [Source installation restart](#installations-from-source)
## Omnibus installations
If you have used the [Omnibus packages][omnibus-dl] to install GitLab, then
you should already have `gitlab-ctl` in your `PATH`.
`gitlab-ctl` interacts with the Omnibus packages and can be used to restart the
GitLab Rails application (Unicorn) as well as the other components, like:
- GitLab Workhorse
- Sidekiq
- PostgreSQL (if you are using the bundled one)
- NGINX (if you are using the bundled one)
- Redis (if you are using the bundled one)
- [Mailroom][]
- Logrotate
### Omnibus GitLab restart
There may be times in the documentation where you will be asked to _restart_
GitLab. In that case, you need to run the following command:
```bash
sudo gitlab-ctl restart
```
The output should be similar to this:
```
ok: run: gitlab-workhorse: (pid 11291) 1s
ok: run: logrotate: (pid 11299) 0s
ok: run: mailroom: (pid 11306) 0s
ok: run: nginx: (pid 11309) 0s
ok: run: postgresql: (pid 11316) 1s
ok: run: redis: (pid 11325) 0s
ok: run: sidekiq: (pid 11331) 1s
ok: run: unicorn: (pid 11338) 0s
```
To restart a component separately, you can append its service name to the
`restart` command. For example, to restart **only** NGINX you would run:
```bash
sudo gitlab-ctl restart nginx
```
To check the status of GitLab services, run:
```bash
sudo gitlab-ctl status
```
Notice that all services say `ok: run`.
Sometimes, components time out during the restart and sometimes they get stuck.
In that case, you can use `gitlab-ctl kill <service>` to send the `SIGKILL`
signal to the service, for example `sidekiq`. After that, a restart should
perform fine.
As a last resort, you can try to
[reconfigure GitLab](#omnibus-gitlab-reconfigure) instead.
### Omnibus GitLab reconfigure
There may be times in the documentation where you will be asked to _reconfigure_
GitLab. Remember that this method applies only for the Omnibus packages.
Reconfigure Omnibus GitLab with:
```bash
sudo gitlab-ctl reconfigure
```
Reconfiguring GitLab should occur in the event that something in its
configuration (`/etc/gitlab/gitlab.rb`) has changed.
When you run this command, [Chef], the underlying configuration management
application that powers Omnibus GitLab, will make sure that all directories,
permissions, services, etc., are in place and in the same shape that they were
initially shipped.
It will also restart GitLab components where needed, if any of their
configuration files have changed.
If you manually edit any files in `/var/opt/gitlab` that are managed by Chef,
running reconfigure will revert the changes AND restart the services that
depend on those files.
## Installations from source
If you have followed the official installation guide to [install GitLab from
source][install], run the following command to restart GitLab:
```
sudo service gitlab restart
```
The output should be similar to this:
```
Shutting down GitLab Unicorn
Shutting down GitLab Sidekiq
Shutting down GitLab Workhorse
Shutting down GitLab MailRoom
...
GitLab is not running.
Starting GitLab Unicorn
Starting GitLab Sidekiq
Starting GitLab Workhorse
Starting GitLab MailRoom
...
The GitLab Unicorn web server with pid 28059 is running.
The GitLab Sidekiq job dispatcher with pid 28176 is running.
The GitLab Workhorse with pid 28122 is running.
The GitLab MailRoom email processor with pid 28114 is running.
GitLab and all its components are up and running.
```
This should restart Unicorn, Sidekiq, GitLab Workhorse and [Mailroom][]
(if enabled). The init service file that does all the magic can be found on
your server in `/etc/init.d/gitlab`.
---
If you are using other init systems, like systemd, you can check the
[GitLab Recipes][gl-recipes] repository for some unofficial services. These are
**not** officially supported so use them at your own risk.
[omnibus-dl]: https://about.gitlab.com/downloads/ "Download the Omnibus packages"
[install]: ../install/installation.md "Documentation to install GitLab from source"
[mailroom]: ../incoming_email/README.md "Used for replying by email in GitLab issues and merge requests"
[chef]: https://www.chef.io/chef/ "Chef official website"
[src-service]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/init.d/gitlab "GitLab init service file"
[gl-recipes]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/init "GitLab Recipes repository"
...@@ -33,6 +33,7 @@ GET /groups/:id/projects ...@@ -33,6 +33,7 @@ GET /groups/:id/projects
Parameters: Parameters:
- `archived` (optional) - if passed, limit by archived status - `archived` (optional) - if passed, limit by archived status
- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private`
- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` - `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
- `search` (optional) - Return list of authorized projects according to a search criteria - `search` (optional) - Return list of authorized projects according to a search criteria
......
...@@ -29,6 +29,7 @@ GET /projects ...@@ -29,6 +29,7 @@ GET /projects
Parameters: Parameters:
- `archived` (optional) - if passed, limit by archived status - `archived` (optional) - if passed, limit by archived status
- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private`
- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` - `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
- `search` (optional) - Return list of authorized projects according to a search criteria - `search` (optional) - Return list of authorized projects according to a search criteria
...@@ -152,6 +153,7 @@ GET /projects/owned ...@@ -152,6 +153,7 @@ GET /projects/owned
Parameters: Parameters:
- `archived` (optional) - if passed, limit by archived status - `archived` (optional) - if passed, limit by archived status
- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private`
- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` - `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
- `search` (optional) - Return list of authorized projects according to a search criteria - `search` (optional) - Return list of authorized projects according to a search criteria
...@@ -167,6 +169,7 @@ GET /projects/starred ...@@ -167,6 +169,7 @@ GET /projects/starred
Parameters: Parameters:
- `archived` (optional) - if passed, limit by archived status - `archived` (optional) - if passed, limit by archived status
- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private`
- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` - `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
- `search` (optional) - Return list of authorized projects according to a search criteria - `search` (optional) - Return list of authorized projects according to a search criteria
...@@ -182,6 +185,7 @@ GET /projects/all ...@@ -182,6 +185,7 @@ GET /projects/all
Parameters: Parameters:
- `archived` (optional) - if passed, limit by archived status - `archived` (optional) - if passed, limit by archived status
- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private`
- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` - `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
- `search` (optional) - Return list of authorized projects according to a search criteria - `search` (optional) - Return list of authorized projects according to a search criteria
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
* [Using Variables](variables/README.md) * [Using Variables](variables/README.md)
* [Using SSH keys](ssh_keys/README.md) * [Using SSH keys](ssh_keys/README.md)
* [Triggering builds through the API](triggers/README.md) * [Triggering builds through the API](triggers/README.md)
* [Build artifacts](build_artifacts/README.md)
### Languages ### Languages
......
# Introduction to build artifacts
Artifacts is a list of files and directories which are attached to a build
after it completes successfully.
Since GitLab 8.2 and [GitLab Runner] 0.7.0, build artifacts that are created by
GitLab Runner are uploaded to GitLab and are downloadable as a single archive
(`tar.gz`) using the GitLab UI.
Starting from GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format
changed to `ZIP`, and it is now possible to browse its contents, with the added
ability of downloading the files separately.
## Enabling build artifacts
If you are searching for ways to use the artifacts feature, jump to
[Defining artifacts in `.gitlab-ci.yml`](#defining-artifacts-in-gitlab-ciyml).
The artifacts feature is enabled by default in all GitLab installations.
If by any chance you want to disable the artifacts feature on your GitLab
instance, follow the steps below.
---
**In Omnibus installations:**
1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
```ruby
gitlab_rails['artifacts_enabled'] = false
```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
---
**In installations from source:**
1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
```yaml
artifacts:
enabled: false
```
1. Save the file and [restart GitLab][] for the changes to take effect.
## Defining artifacts in `.gitlab-ci.yml`
A simple example of using the artifacts definition in `.gitlab-ci.yml` would be
the following:
```yaml
pdf:
script: xelatex mycv.tex
artifacts:
paths:
- mycv.pdf
```
A job named `pdf` calls the `xelatex` command in order to build a pdf file from
the latex source file `mycv.tex`. We then define the `artifacts` paths which in
turn are defined with the `paths` keyword. All paths to files and directories
are relative to the repository that was cloned during the build.
For more examples on artifacts, follow the
[separate artifacts yaml documentation](../yaml/README.md#artifacts).
## Storing build artifacts
After a successful build, GitLab Runner uploads an archive containing the build
artifacts to GitLab.
To change the location where the artifacts are stored, follow the steps below.
---
**In Omnibus installations:**
_The artifacts are stored by default in
`/var/opt/gitlab/gitlab-rails/shared/artifacts`._
1. To change the storage path for example to `/mnt/storage/artifacts`, edit
`/etc/gitlab/gitlab.rb` and add the following line:
```ruby
gitlab_rails['artifacts_path'] = "/mnt/storage/artifacts"
```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
---
**In installations from source:**
_The artifacts are stored by default in
`/home/git/gitlab/shared/artifacts`._
1. To change the storage path for example to `/mnt/storage/artifacts`, edit
`/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
```yaml
artifacts:
enabled: true
path: /mnt/storage/artifacts
```
1. Save the file and [restart GitLab][] for the changes to take effect.
## Browsing build artifacts
When GitLab receives an artifacts archive, an archive metadata file is also
generated. This metadata file describes all the entries that are located in the
artifacts archive itself. The metadata file is in a binary format, with
additional GZIP compression.
GitLab does not extract the artifacts archive in order to save space, memory
and disk I/O. It instead inspects the metadata file which contains all the
relevant information. This is especially important when there is a lot of
artifacts, or an archive is a very large file.
---
After a successful build, if you visit the build's specific page, you can see
that there are two buttons.
One is for downloading the artifacts archive and the other for browsing its
contents.
![Build artifacts browser button](img/build_artifacts_browser_button.png)
---
The archive browser shows the name and the actual file size of each file in the
archive. If your artifacts contained directories, then you are also able to
browse inside them.
Below you can see an image of three different file formats, as well as two
directories.
![Build artifacts browser](img/build_artifacts_browser.png)
---
## Downloading build artifacts
If you need to download the whole archive, there are buttons in various places
inside GitLab that make that possible.
1. While on the builds page, you can see the download icon for each build's
artifacts archive in the right corner
1. While inside a specific build, you are presented with a download button
along with the one that browses the archive
1. And finally, when browsing and archive you can see the download button at
the top right corner
---
Note that GitLab does not extract the entire artifacts archive to send just a
single file to the user.
When clicking on a specific file, [GitLab Workhorse] extracts it from the
archive and the download begins.
This implementation saves space, memory and disk I/O.
[gitlab runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner "GitLab Runner repository"
[reconfigure gitlab]: ../../administration/restart_gitlab.md "How to restart GitLab documentation"
[restart gitlab]: ../../administration/restart_gitlab.md "How to restart GitLab documentation"
[gitlab workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse "GitLab Workhorse repository"
...@@ -12,7 +12,7 @@ configuration from the developer. To overcome this we will be using the ...@@ -12,7 +12,7 @@ configuration from the developer. To overcome this we will be using the
official [PHP docker image][php-hub] that can be found in Docker Hub. official [PHP docker image][php-hub] that can be found in Docker Hub.
This will allow us to test PHP projects against different versions of PHP. This will allow us to test PHP projects against different versions of PHP.
However, not everything is plug 'n' play, you still need to onfigure some However, not everything is plug 'n' play, you still need to configure some
things manually. things manually.
As with every build, you need to create a valid `.gitlab-ci.yml` describing the As with every build, you need to create a valid `.gitlab-ci.yml` describing the
......
...@@ -56,7 +56,7 @@ export CI_SERVER_VERSION="" ...@@ -56,7 +56,7 @@ export CI_SERVER_VERSION=""
``` ```
### YAML-defined variables ### YAML-defined variables
**This feature requires GitLab Runner 0.5.0 or higher** **This feature requires GitLab Runner 0.5.0 or higher and GitLab CI 7.14 or higher **
GitLab CI allows you to add to `.gitlab-ci.yml` variables that are set in build environment. GitLab CI allows you to add to `.gitlab-ci.yml` variables that are set in build environment.
The variables are stored in repository and are meant to store non-sensitive project configuration, ie. RAILS_ENV or DATABASE_URL. The variables are stored in repository and are meant to store non-sensitive project configuration, ie. RAILS_ENV or DATABASE_URL.
......
...@@ -135,10 +135,9 @@ thus allowing to fine tune them. ...@@ -135,10 +135,9 @@ thus allowing to fine tune them.
### cache ### cache
`cache` is used to specify a list of files and directories which should be `cache` is used to specify a list of files and directories which should be
cached between builds. Caches are stored according to the branch/ref and the cached between builds.
job name. They are not currently shared between different job names or between
branches/refs, which means that caching will benefit you if you push subsequent **By default the caching is enabled per-job and per-branch.**
commits to an existing feature branch.
If `cache` is defined outside the scope of the jobs, it means it is set If `cache` is defined outside the scope of the jobs, it means it is set
globally and all jobs will use its definition. globally and all jobs will use its definition.
...@@ -152,6 +151,59 @@ cache: ...@@ -152,6 +151,59 @@ cache:
- binaries/ - binaries/
``` ```
#### cache:key
_**Note:** Introduced in GitLab Runner v1.0.0._
The `key` directive allows you to define the affinity of caching
between jobs, allowing to have a single cache for all jobs,
cache per-job, cache per-branch or any other way you deem proper.
This allows you to fine tune caching, allowing you to cache data between different jobs or even different branches.
The `cache:key` variable can use any of the [predefined variables](../variables/README.md):
Example configurations:
To enable per-job caching:
```yaml
cache:
key: "$CI_BUILD_NAME"
untracked: true
```
To enable per-branch caching:
```yaml
cache:
key: "$CI_BUILD_REF_NAME"
untracked: true
```
To enable per-job and per-branch caching:
```yaml
cache:
key: "$CI_BUILD_NAME/$CI_BUILD_REF_NAME"
untracked: true
```
To enable per-branch and per-stage caching:
```yaml
cache:
key: "$CI_BUILD_STAGE/$CI_BUILD_REF_NAME"
untracked: true
```
If you use **Windows Batch** to run your shell scripts you need to replace the `$` with `%`:
```yaml
cache:
key: "%CI_BUILD_STAGE%/%CI_BUILD_REF_NAME%"
untracked: true
```
## Jobs ## Jobs
`.gitlab-ci.yml` allows you to specify an unlimited number of jobs. Each job `.gitlab-ci.yml` allows you to specify an unlimited number of jobs. Each job
......
...@@ -103,6 +103,23 @@ Inside the document: ...@@ -103,6 +103,23 @@ Inside the document:
`_**Note:** This feature was introduced in GitLab EE 8.3_`. Otherwise, leave `_**Note:** This feature was introduced in GitLab EE 8.3_`. Otherwise, leave
this mention out this mention out
## References
- **GitLab Restart:**
There are many cases that a restart/reconfigure of GitLab is required. To
avoid duplication, link to the special document that can be found in
[`doc/administration/restart_gitlab.md`][doc-restart]. Usually the text will
read like:
```
Save the file and [reconfigure GitLab](../administration/restart_gitlab.md)
for the changes to take effect.
```
If the document you are editing resides in a place other than the GitLab CE/EE
`doc/` directory, instead of the relative link, use the full path:
`http://doc.gitlab.com/ce/administration/restart_gitlab.html`.
Replace `reconfigure` with `restart` where appropriate.
## API ## API
Here is a list of must-have items. Use them in the exact order that appears Here is a list of must-have items. Use them in the exact order that appears
...@@ -229,3 +246,4 @@ curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -d "restricted_signup_domai ...@@ -229,3 +246,4 @@ curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -d "restricted_signup_domai
[cURL]: http://curl.haxx.se/ "cURL website" [cURL]: http://curl.haxx.se/ "cURL website"
[single spaces]: http://www.slate.com/articles/technology/technology/2011/01/space_invaders.html [single spaces]: http://www.slate.com/articles/technology/technology/2011/01/space_invaders.html
[gfm]: http://doc.gitlab.com/ce/markdown/markdown.html#newlines "GitLab flavored markdown documentation" [gfm]: http://doc.gitlab.com/ce/markdown/markdown.html#newlines "GitLab flavored markdown documentation"
[doc-restart]: ../administration/restart_gitlab.md "GitLab restart documentation"
...@@ -107,9 +107,16 @@ Then select 'Internet Site' and press enter to confirm the hostname. ...@@ -107,9 +107,16 @@ Then select 'Internet Site' and press enter to confirm the hostname.
## 2. Ruby ## 2. Ruby
The use of Ruby version managers such as [RVM](https://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system Ruby. _**Note:** The current supported Ruby versions are 2.1.x. Ruby 2.2 and 2.3 are
currently not supported._
Remove the old Ruby 1.8 if present The use of Ruby version managers such as [RVM], [rbenv] or [chruby] with GitLab
in production, frequently leads to hard to diagnose problems. For example,
GitLab Shell is called from OpenSSH, and having a version manager can prevent
pushing and pulling over SSH. Version managers are not supported and we strongly
advise everyone to follow the instructions below to use a system Ruby.
Remove the old Ruby 1.8 if present:
sudo apt-get remove ruby1.8 sudo apt-get remove ruby1.8
...@@ -555,3 +562,7 @@ this is likely due to an outdated Nginx or Apache configuration, or a missing or ...@@ -555,3 +562,7 @@ this is likely due to an outdated Nginx or Apache configuration, or a missing or
misconfigured gitlab-workhorse instance. Double-check that you've misconfigured gitlab-workhorse instance. Double-check that you've
[installed Go](#3-go), [installed gitlab-workhorse](#install-gitlab-workhorse), [installed Go](#3-go), [installed gitlab-workhorse](#install-gitlab-workhorse),
and correctly [configured Nginx](#site-configuration). and correctly [configured Nginx](#site-configuration).
[RVM]: https://rvm.io/ "RVM Homepage"
[rbenv]: https://github.com/sstephenson/rbenv "rbenv on GitHub"
[chruby]: https://github.com/postmodern/chruby "chruby on GitHub"
...@@ -32,15 +32,18 @@ Please consider using a virtual machine to run GitLab. ...@@ -32,15 +32,18 @@ Please consider using a virtual machine to run GitLab.
## Ruby versions ## Ruby versions
GitLab requires Ruby (MRI) 2.1 GitLab requires Ruby (MRI) 2.1.x and currently does not work with versions 2.2
and 2.3.
You will have to use the standard MRI implementation of Ruby. You will have to use the standard MRI implementation of Ruby.
We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab needs several Gems that have native extensions. We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab
needs several Gems that have native extensions.
## Hardware requirements ## Hardware requirements
### Storage ### Storage
The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least as much free space as all your repos combined take up. The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least as much free space as all your repos combined take up.
If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them. If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them.
...@@ -109,4 +112,4 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o ...@@ -109,4 +112,4 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o
- Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/)) - Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/))
- Safari 7+ (known problem: required fields in html5 do not work) - Safari 7+ (known problem: required fields in html5 do not work)
- Opera (Latest released version) - Opera (Latest released version)
- Internet Explorer (IE) 10+ but please make sure that you have the `Compatibility View` mode disabled. - Internet Explorer (IE) 10+ but please make sure that you have the `Compatibility View` mode disabled.
\ No newline at end of file
# GitLab Configuration
GitLab Performance Monitoring is disabled by default. To enable it and change any of its
settings, navigate to the Admin area in **Settings > Metrics**
(`/admin/application_settings`).
The minimum required settings you need to set are the InfluxDB host and port.
Make sure _Enable InfluxDB Metrics_ is checked and hit **Save** to save the
changes.
---
![GitLab Performance Monitoring Admin Settings](img/metrics_gitlab_configuration_settings.png)
---
Finally, a restart of all GitLab processes is required for the changes to take
effect:
```bash
# For Omnibus installations
sudo gitlab-ctl restart
# For installations from source
sudo service gitlab restart
```
## Pending Migrations
When any migrations are pending, the metrics are disabled until the migrations
have been performed.
---
Read more on:
- [Introduction to GitLab Performance Monitoring](introduction.md)
- [InfluxDB Configuration](influxdb_configuration.md)
- [InfluxDB Schema](influxdb_schema.md)
# InfluxDB Configuration
The default settings provided by [InfluxDB] are not sufficient for a high traffic
GitLab environment. The settings discussed in this document are based on the
settings GitLab uses for GitLab.com, depending on your own needs you may need to
further adjust them.
If you are intending to run InfluxDB on the same server as GitLab, make sure
you have plenty of RAM since InfluxDB can use quite a bit depending on traffic.
Unless you are going with a budget setup, it's advised to run it separately.
## Requirements
- InfluxDB 0.9.5 or newer
- A fairly modern version of Linux
- At least 4GB of RAM
- At least 10GB of storage for InfluxDB data
Note that the RAM and storage requirements can differ greatly depending on the
amount of data received/stored. To limit the amount of stored data users can
look into [InfluxDB Retention Policies][influxdb-retention].
## Installation
Installing InfluxDB is out of the scope of this document. Please refer to the
[InfluxDB documentation].
## InfluxDB Server Settings
Since InfluxDB has many settings that users may wish to customize themselves
(e.g. what port to run InfluxDB on), we'll only cover the essentials.
The configuration file in question is usually located at
`/etc/influxdb/influxdb.conf`. Whenever you make a change in this file,
InfluxDB needs to be restarted.
### Storage Engine
InfluxDB comes with different storage engines and as of InfluxDB 0.9.5 a new
storage engine is available, called [TSM Tree]. All users **must** use the new
`tsm1` storage engine as this [will be the default engine][tsm1-commit] in
upcoming InfluxDB releases.
Make sure you have the following in your configuration file:
```
[data]
dir = "/var/lib/influxdb/data"
engine = "tsm1"
```
### Admin Panel
Production environments should have the InfluxDB admin panel **disabled**. This
feature can be disabled by adding the following to your InfluxDB configuration
file:
```
[admin]
enabled = false
```
### HTTP
HTTP is required when using the [InfluxDB CLI] or other tools such as Grafana,
thus it should be enabled. When enabling make sure to _also_ enable
authentication:
```
[http]
enabled = true
auth-enabled = true
```
_**Note:** Before you enable authentication, you might want to [create an
admin user](#create-a-new-admin-user)._
### UDP
GitLab writes data to InfluxDB via UDP and thus this must be enabled. Enabling
UDP can be done using the following settings:
```
[[udp]]
enabled = true
bind-address = ":8089"
database = "gitlab"
batch-size = 1000
batch-pending = 5
batch-timeout = "1s"
read-buffer = 209715200
```
This does the following:
1. Enable UDP and bind it to port 8089 for all addresses.
2. Store any data received in the "gitlab" database.
3. Define a batch of points to be 1000 points in size and allow a maximum of
5 batches _or_ flush them automatically after 1 second.
4. Define a UDP read buffer size of 200 MB.
One of the most important settings here is the UDP read buffer size as if this
value is set too low, packets will be dropped. You must also make sure the OS
buffer size is set to the same value, the default value is almost never enough.
To set the OS buffer size to 200 MB, on Linux you can run the following command:
```bash
sysctl -w net.core.rmem_max=209715200
```
To make this permanent, add the following to `/etc/sysctl.conf` and restart the
server:
```bash
net.core.rmem_max=209715200
```
It is **very important** to make sure the buffer sizes are large enough to
handle all data sent to InfluxDB as otherwise you _will_ lose data. The above
buffer sizes are based on the traffic for GitLab.com. Depending on the amount of
traffic, users may be able to use a smaller buffer size, but we highly recommend
using _at least_ 100 MB.
When enabling UDP, users should take care to not expose the port to the public,
as doing so will allow anybody to write data into your InfluxDB database (as
[InfluxDB's UDP protocol][udp] doesn't support authentication). We recommend either
whitelisting the allowed IP addresses/ranges, or setting up a VLAN and only
allowing traffic from members of said VLAN.
## Create a new admin user
If you want to [enable authentication](#http), you might want to [create an
admin user][influx-admin]:
```
influx -execute "CREATE USER jeff WITH PASSWORD '1234' WITH ALL PRIVILEGES"
```
## Create the `gitlab` database
Once you get InfluxDB up and running, you need to create a database for GitLab.
Make sure you have changed the [storage engine](#storage-engine) to `tsm1`
before creating a database.
_**Note:** If you [created an admin user](#create-a-new-admin-user) and enabled
[HTTP authentication](#http), remember to append the username (`-username <username>`)
and password (`-password <password>`) you set earlier to the commands below._
Run the following command to create a database named `gitlab`:
```bash
influx -execute 'CREATE DATABASE gitlab'
```
The name **must** be `gitlab`, do not use any other name.
Next, make sure that the database was successfully created:
```bash
influx -execute 'SHOW DATABASES'
```
The output should be similar to:
```
name: databases
---------------
name
_internal
gitlab
```
That's it! Now your GitLab instance should send data to InfluxDB.
---
Read more on:
- [Introduction to GitLab Performance Monitoring](introduction.md)
- [GitLab Configuration](gitlab_configuration.md)
- [InfluxDB Schema](influxdb_schema.md)
[influxdb-retention]: https://docs.influxdata.com/influxdb/v0.9/query_language/database_management/#retention-policy-management
[influxdb documentation]: https://docs.influxdata.com/influxdb/v0.9/
[influxdb cli]: https://docs.influxdata.com/influxdb/v0.9/tools/shell/
[udp]: https://docs.influxdata.com/influxdb/v0.9/write_protocols/udp/
[influxdb]: https://influxdata.com/time-series-platform/influxdb/
[tsm tree]: https://influxdata.com/blog/new-storage-engine-time-structured-merge-tree/
[tsm1-commit]: https://github.com/influxdata/influxdb/commit/15d723dc77651bac83e09e2b1c94be480966cb0d
[influx-admin]: https://docs.influxdata.com/influxdb/v0.9/administration/authentication_and_authorization/#create-a-new-admin-user
# InfluxDB Schema
The following measurements are currently stored in InfluxDB:
- `PROCESS_file_descriptors`
- `PROCESS_gc_statistics`
- `PROCESS_memory_usage`
- `PROCESS_method_calls`
- `PROCESS_object_counts`
- `PROCESS_transactions`
- `PROCESS_views`
Here, `PROCESS` is replaced with either `rails` or `sidekiq` depending on the
process type. In all series, any form of duration is stored in milliseconds.
## PROCESS_file_descriptors
This measurement contains the number of open file descriptors over time. The
value field `value` contains the number of descriptors.
## PROCESS_gc_statistics
This measurement contains Ruby garbage collection statistics such as the amount
of minor/major GC runs (relative to the last sampling interval), the time spent
in garbage collection cycles, and all fields/values returned by `GC.stat`.
## PROCESS_memory_usage
This measurement contains the process' memory usage (in bytes) over time. The
value field `value` contains the number of bytes.
## PROCESS_method_calls
This measurement contains the methods called during a transaction along with
their duration, and a name of the transaction action that invoked the method (if
available). The method call duration is stored in the value field `duration`,
while the method name is stored in the tag `method`. The tag `action` contains
the full name of the transaction action. Both the `method` and `action` fields
are in the following format:
```
ClassName#method_name
```
For example, a method called by the `show` method in the `UsersController` class
would have `action` set to `UsersController#show`.
## PROCESS_object_counts
This measurement is used to store retained Ruby objects (per class) and the
amount of retained objects. The number of objects is stored in the `count` value
field while the class name is stored in the `type` tag.
## PROCESS_transactions
This measurement is used to store basic transaction details such as the time it
took to complete a transaction, how much time was spent in SQL queries, etc. The
following value fields are available:
| Value | Description |
| ----- | ----------- |
| `duration` | The total duration of the transaction |
| `allocated_memory` | The amount of bytes allocated while the transaction was running. This value is only reliable when using single-threaded application servers |
| `method_duration` | The total time spent in method calls |
| `sql_duration` | The total time spent in SQL queries |
| `view_duration` | The total time spent in views |
## PROCESS_views
This measurement is used to store view rendering timings for a transaction. The
following value fields are available:
| Value | Description |
| ----- | ----------- |
| `duration` | The rendering time of the view |
| `view` | The path of the view, relative to the application's root directory |
The `action` tag contains the action name of the transaction that rendered the
view.
---
Read more on:
- [Introduction to GitLab Performance Monitoring](introduction.md)
- [GitLab Configuration](gitlab_configuration.md)
- [InfluxDB Configuration](influxdb_configuration.md)
# GitLab Performance Monitoring
GitLab comes with its own application performance measuring system as of GitLab
8.4, simply called "GitLab Performance Monitoring". GitLab Performance Monitoring is available in both the
Community and Enterprise editions.
Apart from this introduction, you are advised to read through the following
documents in order to understand and properly configure GitLab Performance Monitoring:
- [GitLab Configuration](gitlab_configuration.md)
- [InfluxDB Configuration](influxdb_configuration.md)
- [InfluxDB Schema](influxdb_schema.md)
## Introduction to GitLab Performance Monitoring
GitLab Performance Monitoring makes it possible to measure a wide variety of statistics
including (but not limited to):
- The time it took to complete a transaction (a web request or Sidekiq job).
- The time spent in running SQL queries and rendering HAML views.
- The time spent executing (instrumented) Ruby methods.
- Ruby object allocations, and retained objects in particular.
- System statistics such as the process' memory usage and open file descriptors.
- Ruby garbage collection statistics.
Metrics data is written to [InfluxDB][influxdb] over [UDP][influxdb-udp]. Stored
data can be visualized using [Grafana][grafana] or any other application that
supports reading data from InfluxDB. Alternatively data can be queried using the
InfluxDB CLI.
## Metric Types
Two types of metrics are collected:
1. Transaction specific metrics.
1. Sampled metrics, collected at a certain interval in a separate thread.
### Transaction Metrics
Transaction metrics are metrics that can be associated with a single
transaction. This includes statistics such as the transaction duration, timings
of any executed SQL queries, time spent rendering HAML views, etc. These metrics
are collected for every Rack request and Sidekiq job processed.
### Sampled Metrics
Sampled metrics are metrics that can't be associated with a single transaction.
Examples include garbage collection statistics and retained Ruby objects. These
metrics are collected at a regular interval. This interval is made up out of two
parts:
1. A user defined interval.
1. A randomly generated offset added on top of the interval, the same offset
can't be used twice in a row.
The actual interval can be anywhere between a half of the defined interval and a
half above the interval. For example, for a user defined interval of 15 seconds
the actual interval can be anywhere between 7.5 and 22.5. The interval is
re-generated for every sampling run instead of being generated once and re-used
for the duration of the process' lifetime.
[influxdb]: https://influxdata.com/time-series-platform/influxdb/
[influxdb-udp]: https://docs.influxdata.com/influxdb/v0.9/write_protocols/udp/
[grafana]: http://grafana.org/
...@@ -48,7 +48,7 @@ which should already be on your system from GitLab 8.1. ...@@ -48,7 +48,7 @@ which should already be on your system from GitLab 8.1.
```bash ```bash
cd /home/git/gitlab-workhorse cd /home/git/gitlab-workhorse
sudo -u git -H git fetch --all sudo -u git -H git fetch --all
sudo -u git -H git checkout 0.6.0 sudo -u git -H git checkout 0.6.1
sudo -u git -H make sudo -u git -H make
``` ```
...@@ -104,10 +104,7 @@ via [/etc/default/gitlab]. ...@@ -104,10 +104,7 @@ via [/etc/default/gitlab].
#### Init script #### Init script
We updated the init script for GitLab in order to pass new We updated the init script for GitLab in order to set a specific PATH for gitlab-workhorse.
configuration options to gitlab-workhorse. We let gitlab-workhorse
connect to the Rails application via a Unix domain socket and we tell
it where the 'public' directory of GitLab is.
``` ```
cd /home/git/gitlab cd /home/git/gitlab
......
...@@ -56,7 +56,7 @@ X-Gitlab-Event: Push Hook ...@@ -56,7 +56,7 @@ X-Gitlab-Event: Push Hook
"author": { "author": {
"name": "Jordi Mallach", "name": "Jordi Mallach",
"email": "jordi@softcatala.org" "email": "jordi@softcatala.org"
} },
"added": ["CHANGELOG"], "added": ["CHANGELOG"],
"modified": ["app/controller/application.rb"], "modified": ["app/controller/application.rb"],
"removed": [] "removed": []
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
- [GitLab Flow](gitlab_flow.md) - [GitLab Flow](gitlab_flow.md)
- [Groups](groups.md) - [Groups](groups.md)
- [Keyboard shortcuts](shortcuts.md) - [Keyboard shortcuts](shortcuts.md)
- [File finder](file_finder.md)
- [Labels](labels.md) - [Labels](labels.md)
- [Notification emails](notifications.md) - [Notification emails](notifications.md)
- [Project Features](project_features.md) - [Project Features](project_features.md)
......
# File finder
_**Note:** This feature was [introduced][gh-9889] in GitLab 8.4._
---
The file finder feature allows you to quickly shortcut your way when you are
searching for a file in a repository using the GitLab UI.
You can find the **Find File** button when in the **Files** section of a
project.
![Find file button](img/file_finder_find_button.png)
---
For those who prefer to keep their fingers on the keyboard, there is a
[shortcut button](shortcuts.md) as well, which you can invoke from _anywhere_
in a project.
Press `t` to launch the File search function when in **Issues**,
**Merge requests**, **Milestones**, even the project's settings.
Start typing what you are searching for and watch the magic happen. With the
up/down arrows, you go up and down the results, with `Esc` you close the search
and go back to **Files**.
## How it works
The File finder feature is powered by the [Fuzzy filter] library.
It implements a fuzzy search with highlight, and tries to provide intuitive
results by recognizing patterns that people use while searching.
For example, consider the [GitLab CE repository][ce] and that we want to open
the `app/controllers/admin/deploy_keys_controller.rb` file.
Using fuzzy search, we start by typing letters that get us closer to the file.
**Protip:** To narrow down your search, include `/` in your search terms.
![Find file button](img/file_finder_find_file.png)
[gh-9889]: https://github.com/gitlabhq/gitlabhq/pull/9889 "File finder pull request"
[fuzzy filter]: https://github.com/jeancroy/fuzzaldrin-plus "fuzzaldrin-plus on GitHub"
[ce]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master "GitLab CE repository"
# Protected branches # Protected branches
Permission in GitLab are fundamentally defined around the idea of having read or write permission to the repository and branches. Permissions in GitLab are fundamentally defined around the idea of having read or write permission to the repository and branches.
To prevent people from messing with history or pushing code without review, we've created protected branches. To prevent people from messing with history or pushing code without review, we've created protected branches.
......
...@@ -7,21 +7,21 @@ Feature: Project Builds Artifacts ...@@ -7,21 +7,21 @@ Feature: Project Builds Artifacts
Scenario: I download build artifacts Scenario: I download build artifacts
Given recent build has artifacts available Given recent build has artifacts available
When I visit recent build summary page When I visit recent build details page
And I click artifacts download button And I click artifacts download button
Then download of build artifacts archive starts Then download of build artifacts archive starts
Scenario: I browse build artifacts Scenario: I browse build artifacts
Given recent build has artifacts available Given recent build has artifacts available
And recent build has artifacts metadata available And recent build has artifacts metadata available
When I visit recent build summary page When I visit recent build details page
And I click artifacts browse button And I click artifacts browse button
Then I should see content of artifacts archive Then I should see content of artifacts archive
Scenario: I browse subdirectory of build artifacts Scenario: I browse subdirectory of build artifacts
Given recent build has artifacts available Given recent build has artifacts available
And recent build has artifacts metadata available And recent build has artifacts metadata available
When I visit recent build summary page When I visit recent build details page
And I click artifacts browse button And I click artifacts browse button
And I click link to subdirectory within build artifacts And I click link to subdirectory within build artifacts
Then I should see content of subdirectory within artifacts archive Then I should see content of subdirectory within artifacts archive
...@@ -30,7 +30,7 @@ Feature: Project Builds Artifacts ...@@ -30,7 +30,7 @@ Feature: Project Builds Artifacts
Given recent build has artifacts available Given recent build has artifacts available
And recent build has artifacts metadata available And recent build has artifacts metadata available
And recent build artifacts contain directory with UTF-8 characters And recent build artifacts contain directory with UTF-8 characters
When I visit recent build summary page When I visit recent build details page
And I click artifacts browse button And I click artifacts browse button
And I navigate to directory with UTF-8 characters in name And I navigate to directory with UTF-8 characters in name
Then I should see content of directory with UTF-8 characters in name Then I should see content of directory with UTF-8 characters in name
...@@ -39,7 +39,7 @@ Feature: Project Builds Artifacts ...@@ -39,7 +39,7 @@ Feature: Project Builds Artifacts
Given recent build has artifacts available Given recent build has artifacts available
And recent build has artifacts metadata available And recent build has artifacts metadata available
And recent build artifacts contain directory with invalid UTF-8 characters And recent build artifacts contain directory with invalid UTF-8 characters
When I visit recent build summary page When I visit recent build details page
And I click artifacts browse button And I click artifacts browse button
And I navigate to parent directory of directory with invalid name And I navigate to parent directory of directory with invalid name
Then I should not see directory with invalid name on the list Then I should not see directory with invalid name on the list
...@@ -47,7 +47,7 @@ Feature: Project Builds Artifacts ...@@ -47,7 +47,7 @@ Feature: Project Builds Artifacts
Scenario: I download a single file from build artifacts Scenario: I download a single file from build artifacts
Given recent build has artifacts available Given recent build has artifacts available
And recent build has artifacts metadata available And recent build has artifacts metadata available
When I visit recent build summary page When I visit recent build details page
And I click artifacts browse button And I click artifacts browse button
And I click a link to file within build artifacts And I click a link to file within build artifacts
Then download of a file extracted from build artifacts should start Then download of a file extracted from build artifacts should start
...@@ -5,7 +5,11 @@ Feature: Project Builds Summary ...@@ -5,7 +5,11 @@ Feature: Project Builds Summary
And project has CI enabled And project has CI enabled
And project has a recent build And project has a recent build
Scenario: I browse build summary page Scenario: I browse build details page
When I visit recent build summary page When I visit recent build details page
Then I see summary for build Then I see details of a build
And I see build trace And I see build trace
Scenario: I browse project builds page
When I visit project builds page
Then I see button to CI Lint
...@@ -51,6 +51,14 @@ Feature: Project Issues ...@@ -51,6 +51,14 @@ Feature: Project Issues
Then I should see comment "XML attached" Then I should see comment "XML attached"
And I should see an error alert section within the comment form And I should see an error alert section within the comment form
@javascript
Scenario: Visiting Issues after leaving a comment
Given I visit issue page "Release 0.4"
And I leave a comment like "XML attached"
And I visit project "Shop" issues page
And I sort the list by "Last updated"
Then I should see "Release 0.4" at the top
@javascript @javascript
Scenario: I search issue Scenario: I search issue
Given I fill in issue search with "Re" Given I fill in issue search with "Re"
......
...@@ -75,6 +75,25 @@ Feature: Project Merge Requests ...@@ -75,6 +75,25 @@ Feature: Project Merge Requests
And I leave a comment like "XML attached" And I leave a comment like "XML attached"
Then I should see comment "XML attached" Then I should see comment "XML attached"
@javascript
Scenario: Visiting Merge Requests after leaving a comment
Given project "Shop" have "Bug NS-05" open merge request with diffs inside
And I visit merge request page "Bug NS-04"
And I leave a comment like "XML attached"
And I visit project "Shop" merge requests page
And I sort the list by "Last updated"
Then I should see "Bug NS-04" at the top
@javascript
Scenario: Visiting Merge Requests after commenting on diffs
Given project "Shop" have "Bug NS-05" open merge request with diffs inside
And I visit merge request page "Bug NS-05"
And I click on the Changes tab
And I leave a comment like "Line is wrong" on diff
And I visit project "Shop" merge requests page
And I sort the list by "Last updated"
Then I should see "Bug NS-05" at the top
@javascript @javascript
Scenario: I comment on a merge request diff Scenario: I comment on a merge request diff
Given project "Shop" have "Bug NS-05" open merge request with diffs inside Given project "Shop" have "Bug NS-05" open merge request with diffs inside
......
...@@ -4,11 +4,18 @@ class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps ...@@ -4,11 +4,18 @@ class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps
include SharedBuilds include SharedBuilds
include RepoHelpers include RepoHelpers
step 'I see summary for build' do step 'I see details of a build' do
expect(page).to have_content "Build ##{@build.id}" expect(page).to have_content "Build ##{@build.id}"
end end
step 'I see build trace' do step 'I see build trace' do
expect(page).to have_css '#build-trace' expect(page).to have_css '#build-trace'
end end
step 'I see button to CI Lint' do
page.within('.controls') do
ci_lint_tool_link = page.find_link('CI Lint')
expect(ci_lint_tool_link[:href]).to eq ci_lint_path
end
end
end end
...@@ -293,6 +293,11 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps ...@@ -293,6 +293,11 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
expect(page).to have_content('Yay!') expect(page).to have_content('Yay!')
end end
end end
step 'I should see "Release 0.4" at the top' do
expect(page.find('ul.content-list.issues-list li.issue:first-child')).to have_content("Release 0.4")
end
def filter_issue(text) def filter_issue(text)
fill_in 'issue_search', with: text fill_in 'issue_search', with: text
end end
......
...@@ -41,7 +41,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -41,7 +41,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end end
step 'I should not see "master" branch' do step 'I should not see "master" branch' do
expect(page).not_to have_content "master" expect(find('.merge-request-info')).not_to have_content "master"
end end
step 'I should see "other_branch" branch' do step 'I should see "other_branch" branch' do
...@@ -415,6 +415,14 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -415,6 +415,14 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end end
end end
step 'I should see "Bug NS-05" at the top' do
expect(page.find('ul.content-list.mr-list li.merge-request:first-child')).to have_content("Bug NS-05")
end
step 'I should see "Bug NS-04" at the top' do
expect(page.find('ul.content-list.mr-list li.merge-request:first-child')).to have_content("Bug NS-04")
end
def merge_request def merge_request
@merge_request ||= MergeRequest.find_by!(title: "Bug NS-05") @merge_request ||= MergeRequest.find_by!(title: "Bug NS-05")
end end
......
...@@ -10,10 +10,14 @@ module SharedBuilds ...@@ -10,10 +10,14 @@ module SharedBuilds
@build = create :ci_build, commit: ci_commit @build = create :ci_build, commit: ci_commit
end end
step 'I visit recent build summary page' do step 'I visit recent build details page' do
visit namespace_project_build_path(@project.namespace, @project, @build) visit namespace_project_build_path(@project.namespace, @project, @build)
end end
step 'I visit project builds page' do
visit namespace_project_builds_path(@project.namespace, @project)
end
step 'recent build has artifacts available' do step 'recent build has artifacts available' do
artifacts = Rails.root + 'spec/fixtures/ci_build_artifacts.zip' artifacts = Rails.root + 'spec/fixtures/ci_build_artifacts.zip'
archive = fixture_file_upload(artifacts, 'application/zip') archive = fixture_file_upload(artifacts, 'application/zip')
......
...@@ -144,4 +144,11 @@ module SharedNote ...@@ -144,4 +144,11 @@ module SharedNote
expect(page).to have_content("+1 Awesome!") expect(page).to have_content("+1 Awesome!")
end end
end end
step 'I sort the list by "Last updated"' do
find('button.dropdown-toggle.btn').click
page.within('ul.dropdown-menu.dropdown-menu-align-right li') do
click_link "Last updated"
end
end
end end
...@@ -264,6 +264,10 @@ module API ...@@ -264,6 +264,10 @@ module API
projects = projects.search(params[:search]) projects = projects.search(params[:search])
end end
if params[:visibility].present?
projects = projects.search_by_visibility(params[:visibility])
end
projects.reorder(project_order_by => project_sort) projects.reorder(project_order_by => project_sort)
end end
......
...@@ -133,6 +133,7 @@ module Banzai ...@@ -133,6 +133,7 @@ module Banzai
next unless link && text next unless link && text
link = CGI.unescape(link) link = CGI.unescape(link)
next unless link.force_encoding('UTF-8').valid_encoding?
# Ignore ending punctionation like periods or commas # Ignore ending punctionation like periods or commas
next unless link == text && text =~ /\A#{pattern}/ next unless link == text && text =~ /\A#{pattern}/
...@@ -170,6 +171,7 @@ module Banzai ...@@ -170,6 +171,7 @@ module Banzai
next unless link && text next unless link && text
link = CGI.unescape(link) link = CGI.unescape(link)
next unless link.force_encoding('UTF-8').valid_encoding?
next unless link && link =~ /\A#{pattern}\z/ next unless link && link =~ /\A#{pattern}\z/
html = yield link, text html = yield link, text
......
...@@ -115,6 +115,10 @@ module Ci ...@@ -115,6 +115,10 @@ module Ci
end end
if @cache if @cache
if @cache[:key] && !validate_string(@cache[:key])
raise ValidationError, "cache:key parameter should be a string"
end
if @cache[:untracked] && !validate_boolean(@cache[:untracked]) if @cache[:untracked] && !validate_boolean(@cache[:untracked])
raise ValidationError, "cache:untracked parameter should be an boolean" raise ValidationError, "cache:untracked parameter should be an boolean"
end end
...@@ -198,6 +202,10 @@ module Ci ...@@ -198,6 +202,10 @@ module Ci
end end
def validate_job_cache!(name, job) def validate_job_cache!(name, job)
if job[:cache][:key] && !validate_string(job[:cache][:key])
raise ValidationError, "#{name} job: cache:key parameter should be a string"
end
if job[:cache][:untracked] && !validate_boolean(job[:cache][:untracked]) if job[:cache][:untracked] && !validate_boolean(job[:cache][:untracked])
raise ValidationError, "#{name} job: cache:untracked parameter should be an boolean" raise ValidationError, "#{name} job: cache:untracked parameter should be an boolean"
end end
......
require 'resolv'
class DNSXLCheck
class Resolver
def self.search(query)
begin
Resolv.getaddress(query)
true
rescue Resolv::ResolvError
false
end
end
end
IP_REGEXP = /\A(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\z/
DEFAULT_THRESHOLD = 0.33
def self.create_from_list(list)
dnsxl_check = DNSXLCheck.new
list.each do |entry|
dnsxl_check.add_list(entry.domain, entry.weight)
end
dnsxl_check
end
def test(ip)
if use_threshold?
test_with_threshold(ip)
else
test_strict(ip)
end
end
def test_with_threshold(ip)
return false if lists.empty?
search(ip)
final_score >= threshold
end
def test_strict(ip)
return false if lists.empty?
search(ip)
@score > 0
end
def use_threshold=(value)
@use_threshold = value == true
end
def use_threshold?
@use_threshold &&= true
end
def threshold=(threshold)
raise ArgumentError, "'threshold' value must be grather than 0 and less than or equal to 1" unless threshold > 0 && threshold <= 1
@threshold = threshold
end
def threshold
@threshold ||= DEFAULT_THRESHOLD
end
def add_list(domain, weight)
@lists ||= []
@lists << { domain: domain, weight: weight }
end
def lists
@lists ||= []
end
private
def search(ip)
raise ArgumentError, "'ip' value must be in #{IP_REGEXP} format" unless ip.match(IP_REGEXP)
@score = 0
reversed = reverse_ip(ip)
search_in_rbls(reversed)
end
def reverse_ip(ip)
ip.split('.').reverse.join('.')
end
def search_in_rbls(reversed_ip)
lists.each do |rbl|
query = "#{reversed_ip}.#{rbl[:domain]}"
@score += rbl[:weight] if Resolver.search(query)
end
end
def final_score
weights = lists.map{ |rbl| rbl[:weight] }.reduce(:+).to_i
return 0 if weights == 0
(@score.to_f / weights.to_f).round(2)
end
end
...@@ -13,8 +13,8 @@ module Gitlab ...@@ -13,8 +13,8 @@ module Gitlab
attr_reader :file, :path, :full_version attr_reader :file, :path, :full_version
def initialize(file, path) def initialize(file, path, **opts)
@file, @path = file, path @file, @path, @opts = file, path, opts
@full_version = read_version @full_version = read_version
end end
...@@ -52,7 +52,9 @@ module Gitlab ...@@ -52,7 +52,9 @@ module Gitlab
def match_entries(gz) def match_entries(gz)
entries = {} entries = {}
match_pattern = %r{^#{Regexp.escape(@path)}[^/]*/?$}
child_pattern = '[^/]*/?$' unless @opts[:recursive]
match_pattern = /^#{Regexp.escape(@path)}#{child_pattern}/
until gz.eof? do until gz.eof? do
begin begin
......
...@@ -95,6 +95,13 @@ module Gitlab ...@@ -95,6 +95,13 @@ module Gitlab
children.empty? children.empty?
end end
def total_size
descendant_pattern = %r{^#{Regexp.escape(@path)}}
entries.sum do |path, entry|
(entry[:size] if path =~ descendant_pattern).to_i
end
end
def to_s def to_s
@path @path
end end
......
module Gitlab module Gitlab
module Diff module Diff
class File class File
attr_reader :diff attr_reader :diff, :diff_refs
delegate :new_file, :deleted_file, :renamed_file, delegate :new_file, :deleted_file, :renamed_file,
:old_path, :new_path, to: :diff, prefix: false :old_path, :new_path, to: :diff, prefix: false
def initialize(diff) def initialize(diff, diff_refs)
@diff = diff @diff = diff
@diff_refs = diff_refs
end
def old_ref
diff_refs[0] if diff_refs
end
def new_ref
diff_refs[1] if diff_refs
end end
# Array of Gitlab::DIff::Line objects # Array of Gitlab::DIff::Line objects
...@@ -15,6 +24,14 @@ module Gitlab ...@@ -15,6 +24,14 @@ module Gitlab
@lines ||= parser.parse(raw_diff.lines) @lines ||= parser.parse(raw_diff.lines)
end end
def highlighted_diff_lines
Gitlab::Diff::Highlight.new(self).highlight
end
def parallel_diff_lines
Gitlab::Diff::ParallelDiff.new(self).parallelize
end
def mode_changed? def mode_changed?
!!(diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode) !!(diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode)
end end
......
module Gitlab
module Diff
class Highlight
attr_reader :diff_file, :diff_lines, :raw_lines
delegate :old_path, :new_path, :old_ref, :new_ref, to: :diff_file, prefix: :diff
def initialize(diff_lines)
if diff_lines.is_a?(Gitlab::Diff::File)
@diff_file = diff_lines
@diff_lines = @diff_file.diff_lines
else
@diff_lines = diff_lines
end
@raw_lines = @diff_lines.map(&:text)
end
def highlight
@diff_lines.map.with_index do |diff_line, i|
diff_line = diff_line.dup
# ignore highlighting for "match" lines
next diff_line if diff_line.type == 'match' || diff_line.type == 'nonewline'
rich_line = highlight_line(diff_line, i)
if line_inline_diffs = inline_diffs[i]
rich_line = InlineDiffMarker.new(diff_line.text, rich_line).mark(line_inline_diffs)
end
diff_line.text = rich_line.html_safe
diff_line
end
end
private
def highlight_line(diff_line, index)
return html_escape(diff_line.text) unless diff_file && diff_file.diff_refs
line_prefix = diff_line.text.match(/\A(.)/) ? $1 : ' '
case diff_line.type
when 'new', nil
rich_line = new_lines[diff_line.new_pos - 1]
when 'old'
rich_line = old_lines[diff_line.old_pos - 1]
end
# Only update text if line is found. This will prevent
# issues with submodules given the line only exists in diff content.
rich_line ? line_prefix + rich_line : html_escape(diff_line.text)
end
def inline_diffs
@inline_diffs ||= InlineDiff.new(@raw_lines).inline_diffs
end
def old_lines
return unless diff_file
@old_lines ||= Gitlab::Highlight.highlight_lines(*processing_args(:old))
end
def new_lines
return unless diff_file
@new_lines ||= Gitlab::Highlight.highlight_lines(*processing_args(:new))
end
def processing_args(version)
ref = send("diff_#{version}_ref")
path = send("diff_#{version}_path")
[ref.project.repository, ref.id, path]
end
def html_escape(str)
replacements = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
str.gsub(/[&"'><]/, replacements)
end
end
end
end
module Gitlab
module Diff
class InlineDiff
attr_accessor :lines
def initialize(lines)
@lines = lines
end
def inline_diffs
inline_diffs = []
local_edit_indexes.each do |index|
old_index = index
new_index = index + 1
old_line = @lines[old_index]
new_line = @lines[new_index]
# Skip inline diff if empty line was replaced with content
next if old_line[1..-1] == ""
# Add one, because this is based on the prefixless version
lcp = longest_common_prefix(old_line[1..-1], new_line[1..-1]) + 1
lcs = longest_common_suffix(old_line[lcp..-1], new_line[lcp..-1])
old_diff_range = lcp..(old_line.length - lcs - 1)
new_diff_range = lcp..(new_line.length - lcs - 1)
inline_diffs[old_index] = [old_diff_range] if old_diff_range.begin <= old_diff_range.end
inline_diffs[new_index] = [new_diff_range] if new_diff_range.begin <= new_diff_range.end
end
inline_diffs
end
private
# Find runs of single line edits
def local_edit_indexes
line_prefixes = @lines.map { |line| line.match(/\A([+-])/) ? $1 : ' ' }
joined_line_prefixes = " #{line_prefixes.join} "
offset = 0
local_edit_indexes = []
while index = joined_line_prefixes.index(" -+ ", offset)
local_edit_indexes << index
offset = index + 1
end
local_edit_indexes
end
def longest_common_prefix(a, b)
max_length = [a.length, b.length].max
length = 0
(0..max_length - 1).each do |pos|
old_char = a[pos]
new_char = b[pos]
break if old_char != new_char
length += 1
end
length
end
def longest_common_suffix(a, b)
longest_common_prefix(a.reverse, b.reverse)
end
end
end
end
module Gitlab
module Diff
class InlineDiffMarker
attr_accessor :raw_line, :rich_line
def initialize(raw_line, rich_line = raw_line)
@raw_line = raw_line
@rich_line = rich_line
end
def mark(line_inline_diffs)
marker_ranges = []
line_inline_diffs.each do |inline_diff_range|
# Map the inline-diff range based on the raw line to character positions in the rich line
inline_diff_positions = position_mapping[inline_diff_range].flatten
# Turn the array of character positions into ranges
marker_ranges.concat(collapse_ranges(inline_diff_positions))
end
offset = 0
# Mark each range
marker_ranges.each do |range|
offset = insert_around_range(rich_line, range, "<span class='idiff'>", "</span>", offset)
end
rich_line
end
private
# Mapping of character positions in the raw line, to the rich (highlighted) line
def position_mapping
@position_mapping ||= begin
mapping = []
rich_pos = 0
(0..raw_line.length).each do |raw_pos|
rich_char = rich_line[rich_pos]
# The raw and rich lines are the same except for HTML tags,
# so skip over any `<...>` segment
while rich_char == '<'
until rich_char == '>'
rich_pos += 1
rich_char = rich_line[rich_pos]
end
rich_pos += 1
rich_char = rich_line[rich_pos]
end
# multi-char HTML entities in the rich line correspond to a single character in the raw line
if rich_char == '&'
multichar_mapping = [rich_pos]
until rich_char == ';'
rich_pos += 1
multichar_mapping << rich_pos
rich_char = rich_line[rich_pos]
end
mapping[raw_pos] = multichar_mapping
else
mapping[raw_pos] = rich_pos
end
rich_pos += 1
end
mapping
end
end
# Takes an array of integers, and returns an array of ranges covering the same integers
def collapse_ranges(positions)
return [] if positions.empty?
ranges = []
start = prev = positions[0]
range = start..prev
positions[1..-1].each do |pos|
if pos == prev + 1
range = start..pos
prev = pos
else
ranges << range
start = prev = pos
range = start..prev
end
end
ranges << range
ranges
end
# Inserts tags around the characters identified by the given range
def insert_around_range(text, range, before, after, offset = 0)
# Just to be sure
return offset if offset + range.end + 1 > text.length
text.insert(offset + range.begin, before)
offset += before.length
text.insert(offset + range.end + 1, after)
offset += after.length
offset
end
end
end
end
module Gitlab module Gitlab
module Diff module Diff
class Line class Line
attr_reader :type, :text, :index, :old_pos, :new_pos attr_reader :type, :index, :old_pos, :new_pos
attr_accessor :text
def initialize(text, type, index, old_pos, new_pos) def initialize(text, type, index, old_pos, new_pos)
@text, @type, @index = text, type, index @text, @type, @index = text, type, index
......
module Gitlab
module Diff
class ParallelDiff
attr_accessor :diff_file
def initialize(diff_file)
@diff_file = diff_file
end
def parallelize
lines = []
skip_next = false
highlighted_diff_lines = diff_file.highlighted_diff_lines
highlighted_diff_lines.each do |line|
full_line = line.text
type = line.type
line_code = generate_line_code(diff_file.file_path, line)
line_new = line.new_pos
line_old = line.old_pos
next_line = diff_file.next_line(line.index)
if next_line
next_line = highlighted_diff_lines[next_line.index]
next_line_code = generate_line_code(diff_file.file_path, next_line)
next_type = next_line.type
next_line = next_line.text
end
case type
when 'match', nil
# line in the right panel is the same as in the left one
lines << {
left: {
type: type,
number: line_old,
text: full_line,
line_code: line_code,
},
right: {
type: type,
number: line_new,
text: full_line,
line_code: line_code
}
}
when 'old'
case next_type
when 'new'
# Left side has text removed, right side has text added
lines << {
left: {
type: type,
number: line_old,
text: full_line,
line_code: line_code,
},
right: {
type: next_type,
number: line_new,
text: next_line,
line_code: next_line_code
}
}
skip_next = true
when 'old', 'nonewline', nil
# Left side has text removed, right side doesn't have any change
# No next line code, no new line number, no new line text
lines << {
left: {
type: type,
number: line_old,
text: full_line,
line_code: line_code,
},
right: {
type: next_type,
number: nil,
text: "",
line_code: nil
}
}
end
when 'new'
if skip_next
# Change has been already included in previous line so no need to do it again
skip_next = false
next
else
# Change is only on the right side, left side has no change
lines << {
left: {
type: nil,
number: nil,
text: "",
line_code: line_code,
},
right: {
type: type,
number: line_new,
text: full_line,
line_code: line_code
}
}
end
end
end
lines
end
private
def generate_line_code(file_path, line)
Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos)
end
end
end
end
...@@ -11,13 +11,10 @@ module Gitlab ...@@ -11,13 +11,10 @@ module Gitlab
line_new = 1 line_new = 1
type = nil type = nil
lines_arr = ::Gitlab::InlineDiff.processing lines @lines.each do |line|
lines_arr.each do |line|
next if filename?(line) next if filename?(line)
full_line = html_escape(line.gsub(/\n/, '')) full_line = line.gsub(/\n/, '')
full_line = ::Gitlab::InlineDiff.replace_markers full_line
if line.match(/^@@ -/) if line.match(/^@@ -/)
type = "match" type = "match"
...@@ -29,6 +26,10 @@ module Gitlab ...@@ -29,6 +26,10 @@ module Gitlab
lines_obj << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new) lines_obj << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new)
line_obj_index += 1 line_obj_index += 1
next next
elsif line[0] == '\\'
type = 'nonewline'
lines_obj << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new)
line_obj_index += 1
else else
type = identification_type(line) type = identification_type(line)
lines_obj << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new) lines_obj << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new)
...@@ -36,10 +37,13 @@ module Gitlab ...@@ -36,10 +37,13 @@ module Gitlab
end end
if line[0] == "+" case line[0]
when "+"
line_new += 1 line_new += 1
elsif line[0] == "-" when "-"
line_old += 1 line_old += 1
when "\\"
# No increment
else else
line_new += 1 line_new += 1
line_old += 1 line_old += 1
...@@ -62,19 +66,15 @@ module Gitlab ...@@ -62,19 +66,15 @@ module Gitlab
end end
def identification_type(line) def identification_type(line)
if line[0] == "+" case line[0]
when "+"
"new" "new"
elsif line[0] == "-" when "-"
"old" "old"
else else
nil nil
end end
end end
def html_escape(str)
replacements = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
str.gsub(/[&"'><]/, replacements)
end
end end
end end
end end
...@@ -9,6 +9,7 @@ module Gitlab ...@@ -9,6 +9,7 @@ module Gitlab
delegate :namespace, :name_with_namespace, to: :project, prefix: :project delegate :namespace, :name_with_namespace, to: :project, prefix: :project
delegate :name, to: :author, prefix: :author delegate :name, to: :author, prefix: :author
delegate :username, to: :author, prefix: :author
def initialize(notify, project_id, recipient, opts = {}) def initialize(notify, project_id, recipient, opts = {})
raise ArgumentError, 'Missing options: author_id, ref, action' unless raise ArgumentError, 'Missing options: author_id, ref, action' unless
......
...@@ -82,8 +82,12 @@ module Gitlab ...@@ -82,8 +82,12 @@ module Gitlab
end end
true true
rescue Gitlab::Shell::Error rescue Gitlab::Shell::Error => e
false if e.message =~ /repository not exported/
true
else
false
end
end end
end end
end end
......
...@@ -18,7 +18,7 @@ module Gitlab ...@@ -18,7 +18,7 @@ module Gitlab
end end
def cross_project? def cross_project?
source_repo.fork == true source_repo.id != target_repo.id
end end
def number def number
...@@ -73,6 +73,10 @@ module Gitlab ...@@ -73,6 +73,10 @@ module Gitlab
project project
end end
def target_repo
raw_data.base.repo
end
def target_branch def target_branch
target_project.repository.find_branch(raw_data.base.ref) target_project.repository.find_branch(raw_data.base.ref)
end end
......
module Gitlab
class Highlight
def self.highlight(blob_name, blob_content, nowrap: true)
new(blob_name, blob_content, nowrap: nowrap).highlight(blob_content, continue: false)
end
def self.highlight_lines(repository, ref, file_name)
blob = repository.blob_at(ref, file_name)
return [] unless blob
highlight(file_name, blob.data).lines.map!(&:html_safe)
end
def initialize(blob_name, blob_content, nowrap: true)
@formatter = rouge_formatter(nowrap: nowrap)
@lexer = Rouge::Lexer.guess(filename: blob_name, source: blob_content).new rescue Rouge::Lexers::PlainText
end
def highlight(text, continue: true)
@formatter.format(@lexer.lex(text, continue: continue)).html_safe
rescue
@formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe
end
private
def rouge_formatter(options = {})
options = options.reverse_merge(
nowrap: true,
cssclass: 'code highlight',
lineanchors: true,
lineanchorsid: 'LC'
)
Rouge::Formatters::HTMLGitlab.new(options)
end
end
end
module Gitlab
class InlineDiff
class << self
START = "#!idiff-start!#"
FINISH = "#!idiff-finish!#"
def processing(diff_arr)
indexes = _indexes_of_changed_lines diff_arr
indexes.each do |index|
first_line = diff_arr[index+1]
second_line = diff_arr[index+2]
# Skip inline diff if empty line was replaced with content
next if first_line == "-\n"
first_token = find_first_token(first_line, second_line)
apply_first_token(diff_arr, index, first_token)
last_token = find_last_token(first_line, second_line, first_token)
apply_last_token(diff_arr, index, last_token)
end
diff_arr
end
def apply_first_token(diff_arr, index, first_token)
start = first_token + START
if first_token.empty?
# In case if we remove string of spaces in commit
diff_arr[index+1].sub!("-", "-" => "-#{START}")
diff_arr[index+2].sub!("+", "+" => "+#{START}")
else
diff_arr[index+1].sub!(first_token, first_token => start)
diff_arr[index+2].sub!(first_token, first_token => start)
end
end
def apply_last_token(diff_arr, index, last_token)
# This is tricky: escape backslashes so that `sub` doesn't interpret them
# as backreferences. Regexp.escape does NOT do the right thing.
replace_token = FINISH + last_token.gsub(/\\/, '\&\&')
diff_arr[index+1].sub!(/#{Regexp.escape(last_token)}$/, replace_token)
diff_arr[index+2].sub!(/#{Regexp.escape(last_token)}$/, replace_token)
end
def find_first_token(first_line, second_line)
max_length = [first_line.size, second_line.size].max
first_the_same_symbols = 0
(0..max_length + 1).each do |i|
first_the_same_symbols = i - 1
if first_line[i] != second_line[i] && i > 0
break
end
end
first_line[0..first_the_same_symbols][1..-1]
end
def find_last_token(first_line, second_line, first_token)
max_length = [first_line.size, second_line.size].max
last_the_same_symbols = 0
(1..max_length + 1).each do |i|
last_the_same_symbols = -i
shortest_line = second_line.size > first_line.size ? first_line : second_line
if (first_line[-i] != second_line[-i]) || "#{first_token}#{START}".size == shortest_line[1..-i].size
break
end
end
last_the_same_symbols += 1
first_line[last_the_same_symbols..-1]
end
def _indexes_of_changed_lines(diff_arr)
chain_of_first_symbols = ""
diff_arr.each_with_index do |line, i|
chain_of_first_symbols += line[0]
end
chain_of_first_symbols.gsub!(/[^\-\+]/, "#")
offset = 0
indexes = []
while index = chain_of_first_symbols.index("#-+#", offset)
indexes << index
offset = index + 1
end
indexes
end
def replace_markers(line)
line.gsub!(START, "<span class='idiff'>")
line.gsub!(FINISH, "</span>")
line
end
end
end
end
module Gitlab
class IpCheck
def initialize(ip)
@ip = ip
application_settings = ApplicationSetting.current
@ip_blocking_enabled = application_settings.ip_blocking_enabled
@dnsbl_servers_list = application_settings.dnsbl_servers_list
end
def spam?
@ip_blocking_enabled && blacklisted?
end
private
def blacklisted?
on_dns_blacklist?
end
def on_dns_blacklist?
dnsbl_check = DNSXLCheck.new
prepare_dnsbl_list(dnsbl_check)
dnsbl_check.test(@ip)
end
def prepare_dnsbl_list(dnsbl_check)
@dnsbl_servers_list.split(',').map(&:strip).reject(&:empty?).each do |domain|
dnsbl_check.add_list(domain, 1)
end
end
end
end
...@@ -30,28 +30,31 @@ module Gitlab ...@@ -30,28 +30,31 @@ module Gitlab
end end
def find_by_uid_and_provider def find_by_uid_and_provider
self.class.find_by_uid_and_provider( self.class.find_by_uid_and_provider(auth_hash.uid, auth_hash.provider)
auth_hash.uid, auth_hash.provider)
end end
def find_by_email def find_by_email
::User.find_by(email: auth_hash.email.downcase) ::User.find_by(email: auth_hash.email.downcase) if auth_hash.has_email?
end end
def update_user_attributes def update_user_attributes
return unless persisted? if persisted?
if auth_hash.has_email?
gl_user.skip_reconfirmation!
gl_user.email = auth_hash.email
end
gl_user.skip_reconfirmation! # find_or_initialize_by doesn't update `gl_user.identities`, and isn't autosaved.
gl_user.email = auth_hash.email identity = gl_user.identities.find { |identity| identity.provider == auth_hash.provider }
identity ||= gl_user.identities.build(provider: auth_hash.provider)
# find_or_initialize_by doesn't update `gl_user.identities`, and isn't autosaved. # For a new identity set extern_uid to the LDAP DN
identity = gl_user.identities.find { |identity| identity.provider == auth_hash.provider } # For an existing identity with matching email but changed DN, update the DN.
identity ||= gl_user.identities.build(provider: auth_hash.provider) # For an existing identity with no change in DN, this line changes nothing.
identity.extern_uid = auth_hash.uid
end
# For a new user set extern_uid to the LDAP DN gl_user.ldap_email = auth_hash.has_email?
# For an existing user with matching email but changed DN, update the DN.
# For an existing user with no change in DN, this line changes nothing.
identity.extern_uid = auth_hash.uid
gl_user gl_user
end end
......
...@@ -32,6 +32,10 @@ module Gitlab ...@@ -32,6 +32,10 @@ module Gitlab
@password ||= Gitlab::Utils.force_utf8(Devise.friendly_token[0, 8].downcase) @password ||= Gitlab::Utils.force_utf8(Devise.friendly_token[0, 8].downcase)
end end
def has_email?
get_info(:email).present?
end
private private
def info def info
...@@ -46,8 +50,8 @@ module Gitlab ...@@ -46,8 +50,8 @@ module Gitlab
def username_and_email def username_and_email
@username_and_email ||= begin @username_and_email ||= begin
username = get_info(:username) || get_info(:nickname) username = get_info(:username).presence || get_info(:nickname).presence
email = get_info(:email) email = get_info(:email).presence
username ||= generate_username(email) if email username ||= generate_username(email) if email
email ||= generate_temporarily_email(username) if username email ||= generate_temporarily_email(username) if username
......
...@@ -111,7 +111,7 @@ module Gitlab ...@@ -111,7 +111,7 @@ module Gitlab
def block_after_signup? def block_after_signup?
if creating_linked_ldap_user? if creating_linked_ldap_user?
ldap_config.block_auto_created_users ldap_config.block_auto_created_users
else else
Gitlab.config.omniauth.block_auto_created_users Gitlab.config.omniauth.block_auto_created_users
end end
end end
...@@ -135,16 +135,16 @@ module Gitlab ...@@ -135,16 +135,16 @@ module Gitlab
def user_attributes def user_attributes
# Give preference to LDAP for sensitive information when creating a linked account # Give preference to LDAP for sensitive information when creating a linked account
if creating_linked_ldap_user? if creating_linked_ldap_user?
username = ldap_person.username username = ldap_person.username.presence
email = ldap_person.email.first email = ldap_person.email.first.presence
else
username = auth_hash.username
email = auth_hash.email
end end
username ||= auth_hash.username
email ||= auth_hash.email
name = auth_hash.name name = auth_hash.name
name = ::Namespace.clean_path(username) if name.strip.empty? name = ::Namespace.clean_path(username) if name.strip.empty?
{ {
name: name, name: name,
username: ::Namespace.clean_path(username), username: ::Namespace.clean_path(username),
......
...@@ -38,6 +38,7 @@ web_server_pid_path="$pid_path/unicorn.pid" ...@@ -38,6 +38,7 @@ web_server_pid_path="$pid_path/unicorn.pid"
sidekiq_pid_path="$pid_path/sidekiq.pid" sidekiq_pid_path="$pid_path/sidekiq.pid"
mail_room_enabled=false mail_room_enabled=false
mail_room_pid_path="$pid_path/mail_room.pid" mail_room_pid_path="$pid_path/mail_room.pid"
gitlab_workhorse_dir=$(cd $app_root/../gitlab-workhorse && pwd)
gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid" gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid"
gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080 -authSocket $rails_socket -documentRoot $app_root/public" gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080 -authSocket $rails_socket -documentRoot $app_root/public"
gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log" gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log"
...@@ -233,10 +234,12 @@ start_gitlab() { ...@@ -233,10 +234,12 @@ start_gitlab() {
if [ "$gitlab_workhorse_status" = "0" ]; then if [ "$gitlab_workhorse_status" = "0" ]; then
echo "The gitlab-workhorse is already running with pid $spid, not restarting" echo "The gitlab-workhorse is already running with pid $spid, not restarting"
else else
# No need to remove a socket, gitlab-workhorse does this itself # No need to remove a socket, gitlab-workhorse does this itself.
# Because gitlab-workhorse has multiple executables we need to fix
# the PATH.
$app_root/bin/daemon_with_pidfile $gitlab_workhorse_pid_path \ $app_root/bin/daemon_with_pidfile $gitlab_workhorse_pid_path \
$app_root/../gitlab-workhorse/gitlab-workhorse \ /usr/bin/env PATH=$gitlab_workhorse_dir:$PATH \
$gitlab_workhorse_options \ gitlab-workhorse $gitlab_workhorse_options \
>> $gitlab_workhorse_log 2>&1 & >> $gitlab_workhorse_log 2>&1 &
fi fi
......
...@@ -30,6 +30,9 @@ web_server_pid_path="$pid_path/unicorn.pid" ...@@ -30,6 +30,9 @@ web_server_pid_path="$pid_path/unicorn.pid"
# The default is "$pid_path/sidekiq.pid" # The default is "$pid_path/sidekiq.pid"
sidekiq_pid_path="$pid_path/sidekiq.pid" sidekiq_pid_path="$pid_path/sidekiq.pid"
# The directory where the gitlab-workhorse binaries are. Usually
# /home/git/gitlab-workhorse .
gitlab_workhorse_dir=$(cd $app_root/../gitlab-workhorse && pwd)
gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid" gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid"
# The -listenXxx settings determine where gitlab-workhorse # The -listenXxx settings determine where gitlab-workhorse
# listens for connections from NGINX. To listen on localhost:8181, write # listens for connections from NGINX. To listen on localhost:8181, write
......
---
- :left:
:type: match
:number: 6
:text: "@@ -6,12 +6,18 @@ module Popen"
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
:right:
:type: match
:number: 6
:text: "@@ -6,12 +6,18 @@ module Popen"
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
- :left:
:type:
:number: 6
:text: |2
<span id="LC6" class="line"></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
:right:
:type:
:number: 6
:text: |2
<span id="LC6" class="line"></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
- :left:
:type:
:number: 7
:text: |2
<span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7
:right:
:type:
:number: 7
:text: |2
<span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7
- :left:
:type:
:number: 8
:text: |2
<span id="LC8" class="line"> <span class="k">unless</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8
:right:
:type:
:number: 8
:text: |2
<span id="LC8" class="line"> <span class="k">unless</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8
- :left:
:type: old
:number: 9
:text: |
-<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9
:right:
:type: new
:number: 9
:text: |
+<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff'> </span><span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9
- :left:
:type:
:number: 10
:text: |2
<span id="LC10" class="line"> <span class="k">end</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10
:right:
:type:
:number: 10
:text: |2
<span id="LC10" class="line"> <span class="k">end</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10
- :left:
:type:
:number: 11
:text: |2
<span id="LC11" class="line"></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11
:right:
:type:
:number: 11
:text: |2
<span id="LC11" class="line"></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11
- :left:
:type:
:number: 12
:text: |2
<span id="LC12" class="line"> <span class="n">path</span> <span class="o">||=</span> <span class="no">Dir</span><span class="p">.</span><span class="nf">pwd</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12
:right:
:type:
:number: 12
:text: |2
<span id="LC12" class="line"> <span class="n">path</span> <span class="o">||=</span> <span class="no">Dir</span><span class="p">.</span><span class="nf">pwd</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12
- :left:
:type: old
:number: 13
:text: |
-<span id="LC13" class="line"> <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&quot;PWD&quot;</span> <span class="o">=&gt;</span> <span class="n">path</span> <span class="p">}</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_13_13
:right:
:type: old
:number:
:text: ''
:line_code:
- :left:
:type: old
:number: 14
:text: |
-<span id="LC14" class="line"> <span class="n">options</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">chdir: </span><span class="n">path</span> <span class="p">}</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_13
:right:
:type: new
:number: 13
:text: |
+<span id="LC13" class="line"></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_13
- :left:
:type:
:number:
:text: ''
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14
:right:
:type: new
:number: 14
:text: |
+<span id="LC14" class="line"> <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14
- :left:
:type:
:number:
:text: ''
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15
:right:
:type: new
:number: 15
:text: |
+<span id="LC15" class="line"> <span class="s2">&quot;PWD&quot;</span> <span class="o">=&gt;</span> <span class="n">path</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15
- :left:
:type:
:number:
:text: ''
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16
:right:
:type: new
:number: 16
:text: |
+<span id="LC16" class="line"> <span class="p">}</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16
- :left:
:type:
:number:
:text: ''
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17
:right:
:type: new
:number: 17
:text: |
+<span id="LC17" class="line"></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17
- :left:
:type:
:number:
:text: ''
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18
:right:
:type: new
:number: 18
:text: |
+<span id="LC18" class="line"> <span class="n">options</span> <span class="o">=</span> <span class="p">{</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18
- :left:
:type:
:number:
:text: ''
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19
:right:
:type: new
:number: 19
:text: |
+<span id="LC19" class="line"> <span class="ss">chdir: </span><span class="n">path</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19
- :left:
:type:
:number:
:text: ''
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20
:right:
:type: new
:number: 20
:text: |
+<span id="LC20" class="line"> <span class="p">}</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20
- :left:
:type:
:number: 15
:text: |2
<span id="LC21" class="line"></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21
:right:
:type:
:number: 21
:text: |2
<span id="LC21" class="line"></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21
- :left:
:type:
:number: 16
:text: |2
<span id="LC22" class="line"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22
:right:
:type:
:number: 22
:text: |2
<span id="LC22" class="line"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22
- :left:
:type:
:number: 17
:text: |2
<span id="LC23" class="line"> <span class="no">FileUtils</span><span class="p">.</span><span class="nf">mkdir_p</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23
:right:
:type:
:number: 23
:text: |2
<span id="LC23" class="line"> <span class="no">FileUtils</span><span class="p">.</span><span class="nf">mkdir_p</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23
- :left:
:type: match
:number: 19
:text: "@@ -19,6 +25,7 @@ module Popen"
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25
:right:
:type: match
:number: 25
:text: "@@ -19,6 +25,7 @@ module Popen"
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25
- :left:
:type:
:number: 19
:text: |2
<span id="LC25" class="line"></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25
:right:
:type:
:number: 25
:text: |2
<span id="LC25" class="line"></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25
- :left:
:type:
:number: 20
:text: |2
<span id="LC26" class="line"> <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26
:right:
:type:
:number: 26
:text: |2
<span id="LC26" class="line"> <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26
- :left:
:type:
:number: 21
:text: |2
<span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27
:right:
:type:
:number: 27
:text: |2
<span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27
- :left:
:type:
:number:
:text: ''
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28
:right:
:type: new
:number: 28
:text: |
+<span id="LC28" class="line"></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28
- :left:
:type:
:number: 22
:text: |2
<span id="LC29" class="line"> <span class="no">Open3</span><span class="p">.</span><span class="nf">popen3</span><span class="p">(</span><span class="n">vars</span><span class="p">,</span> <span class="o">*</span><span class="n">cmd</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">stdin</span><span class="p">,</span> <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span><span class="p">,</span> <span class="n">wait_thr</span><span class="o">|</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29
:right:
:type:
:number: 29
:text: |2
<span id="LC29" class="line"> <span class="no">Open3</span><span class="p">.</span><span class="nf">popen3</span><span class="p">(</span><span class="n">vars</span><span class="p">,</span> <span class="o">*</span><span class="n">cmd</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">stdin</span><span class="p">,</span> <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span><span class="p">,</span> <span class="n">wait_thr</span><span class="o">|</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29
- :left:
:type:
:number: 23
:text: |2
<span id="LC30" class="line"> <span class="vi">@cmd_output</span> <span class="o">&lt;&lt;</span> <span class="n">stdout</span><span class="p">.</span><span class="nf">read</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30
:right:
:type:
:number: 30
:text: |2
<span id="LC30" class="line"> <span class="vi">@cmd_output</span> <span class="o">&lt;&lt;</span> <span class="n">stdout</span><span class="p">.</span><span class="nf">read</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30
- :left:
:type:
:number: 24
:text: |2
<span id="LC31" class="line"> <span class="vi">@cmd_output</span> <span class="o">&lt;&lt;</span> <span class="n">stderr</span><span class="p">.</span><span class="nf">read</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31
:right:
:type:
:number: 31
:text: |2
<span id="LC31" class="line"> <span class="vi">@cmd_output</span> <span class="o">&lt;&lt;</span> <span class="n">stderr</span><span class="p">.</span><span class="nf">read</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31
require 'spec_helper' require 'spec_helper'
describe BlobHelper do describe BlobHelper do
describe 'highlight' do let(:blob_name) { 'test.lisp' }
let(:blob_name) { 'test.lisp' } let(:no_context_content) { ":type \"assem\"))" }
let(:no_context_content) { ":type \"assem\"))" } let(:blob_content) { "(make-pathname :defaults name\n#{no_context_content}" }
let(:blob_content) { "(make-pathname :defaults name\n#{no_context_content}" } let(:split_content) { blob_content.split("\n") }
let(:split_content) { blob_content.split("\n") } let(:multiline_content) do
let(:multiline_content) do %q(
%q( def test(input):
def test(input): """This is line 1 of a multi-line comment.
"""This is line 1 of a multi-line comment. This is line 2.
This is line 2. """
""" )
) end
end
describe '#highlight' do
it 'should return plaintext for unknown lexer context' do it 'should return plaintext for unknown lexer context' do
result = highlight(blob_name, no_context_content, nowrap: true, continue: false) result = helper.highlight(blob_name, no_context_content, nowrap: true)
expect(result).to eq('<span id="LC1" class="line">:type &quot;assem&quot;))</span>') expect(result).to eq('<span id="LC1" class="line">:type &quot;assem&quot;))</span>')
end end
...@@ -24,28 +24,17 @@ describe BlobHelper do ...@@ -24,28 +24,17 @@ describe BlobHelper do
expected = %Q[<span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span> expected = %Q[<span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>
<span id="LC2" class="line"><span class="ss">:type</span> <span class="s">&quot;assem&quot;</span><span class="p">))</span></span>] <span id="LC2" class="line"><span class="ss">:type</span> <span class="s">&quot;assem&quot;</span><span class="p">))</span></span>]
expect(highlight(blob_name, blob_content, nowrap: true, continue: false)).to eq(expected) expect(helper.highlight(blob_name, blob_content, nowrap: true)).to eq(expected)
end
it 'should highlight continued blocks' do
# Both lines have LC1 as ID since formatter doesn't support continue at the moment
expected = [
'<span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>',
'<span id="LC1" class="line"><span class="ss">:type</span> <span class="s">&quot;assem&quot;</span><span class="p">))</span></span>'
]
result = split_content.map{ |content| highlight(blob_name, content, nowrap: true, continue: true) }
expect(result).to eq(expected)
end end
it 'should highlight multi-line comments' do it 'should highlight multi-line comments' do
result = highlight(blob_name, multiline_content, nowrap: true, continue: false) result = helper.highlight(blob_name, multiline_content, nowrap: true)
html = Nokogiri::HTML(result) html = Nokogiri::HTML(result)
lines = html.search('.s') lines = html.search('.s')
expect(lines.count).to eq(3) expect(lines.count).to eq(3)
expect(lines[0].text).to eq('"""This is line 1 of a multi-line comment.') expect(lines[0].text).to eq('"""This is line 1 of a multi-line comment.')
expect(lines[1].text).to eq(' This is line 2.') expect(lines[1].text).to eq(' This is line 2.')
expect(lines[2].text).to eq(' """') expect(lines[2].text).to eq(' """')
end end
context 'diff highlighting' do context 'diff highlighting' do
...@@ -59,9 +48,23 @@ describe BlobHelper do ...@@ -59,9 +48,23 @@ describe BlobHelper do
end end
it 'should highlight each line properly' do it 'should highlight each line properly' do
result = highlight(blob_name, blob_content, nowrap: true, continue: false) result = helper.highlight(blob_name, blob_content, nowrap: true)
expect(result).to eq(expected) expect(result).to eq(expected)
end end
end end
end end
describe "#highlighter" do
it 'should highlight continued blocks' do
# Both lines have LC1 as ID since formatter doesn't support continue at the moment
expected = [
'<span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>',
'<span id="LC1" class="line"><span class="ss">:type</span> <span class="s">&quot;assem&quot;</span><span class="p">))</span></span>'
]
highlighter = helper.highlighter(blob_name, blob_content, nowrap: true)
result = split_content.map{ |content| highlighter.highlight(content) }
expect(result).to eq(expected)
end
end
end end
...@@ -4,10 +4,12 @@ describe DiffHelper do ...@@ -4,10 +4,12 @@ describe DiffHelper do
include RepoHelpers include RepoHelpers
let(:project) { create(:project) } let(:project) { create(:project) }
let(:repository) { project.repository }
let(:commit) { project.commit(sample_commit.id) } let(:commit) { project.commit(sample_commit.id) }
let(:diffs) { commit.diffs } let(:diffs) { commit.diffs }
let(:diff) { diffs.first } let(:diff) { diffs.first }
let(:diff_file) { Gitlab::Diff::File.new(diff) } let(:diff_refs) { [commit.parent, commit] }
let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs) }
describe 'diff_hard_limit_enabled?' do describe 'diff_hard_limit_enabled?' do
it 'should return true if param is provided' do it 'should return true if param is provided' do
...@@ -44,55 +46,41 @@ describe DiffHelper do ...@@ -44,55 +46,41 @@ describe DiffHelper do
describe 'safe_diff_files' do describe 'safe_diff_files' do
it 'should return all files from a commit that is smaller than safe limits' do it 'should return all files from a commit that is smaller than safe limits' do
expect(safe_diff_files(diffs).length).to eq(2) expect(safe_diff_files(diffs, diff_refs).length).to eq(2)
end end
it 'should return only the first file if the diff line count in the 2nd file takes the total beyond safe limits' do it 'should return only the first file if the diff line count in the 2nd file takes the total beyond safe limits' do
allow(diffs[1].diff).to receive(:lines).and_return([""] * 4999) #simulate 4999 lines allow(diffs[1].diff).to receive(:lines).and_return([""] * 4999) #simulate 4999 lines
expect(safe_diff_files(diffs).length).to eq(1) expect(safe_diff_files(diffs, diff_refs).length).to eq(1)
end end
it 'should return all files from a commit that is beyond safe limit for numbers of lines if force diff is true' do it 'should return all files from a commit that is beyond safe limit for numbers of lines if force diff is true' do
allow(controller).to receive(:params) { { force_show_diff: true } } allow(controller).to receive(:params) { { force_show_diff: true } }
allow(diffs[1].diff).to receive(:lines).and_return([""] * 4999) #simulate 4999 lines allow(diffs[1].diff).to receive(:lines).and_return([""] * 4999) #simulate 4999 lines
expect(safe_diff_files(diffs).length).to eq(2) expect(safe_diff_files(diffs, diff_refs).length).to eq(2)
end end
it 'should return only the first file if the diff line count in the 2nd file takes the total beyond hard limits' do it 'should return only the first file if the diff line count in the 2nd file takes the total beyond hard limits' do
allow(controller).to receive(:params) { { force_show_diff: true } } allow(controller).to receive(:params) { { force_show_diff: true } }
allow(diffs[1].diff).to receive(:lines).and_return([""] * 49999) #simulate 49999 lines allow(diffs[1].diff).to receive(:lines).and_return([""] * 49999) #simulate 49999 lines
expect(safe_diff_files(diffs).length).to eq(1) expect(safe_diff_files(diffs, diff_refs).length).to eq(1)
end end
it 'should return only a safe number of file diffs if a commit touches more files than the safe limits' do it 'should return only a safe number of file diffs if a commit touches more files than the safe limits' do
large_diffs = diffs * 100 #simulate 200 diffs large_diffs = diffs * 100 #simulate 200 diffs
expect(safe_diff_files(large_diffs).length).to eq(100) expect(safe_diff_files(large_diffs, diff_refs).length).to eq(100)
end end
it 'should return all file diffs if a commit touches more files than the safe limits but force diff is true' do it 'should return all file diffs if a commit touches more files than the safe limits but force diff is true' do
allow(controller).to receive(:params) { { force_show_diff: true } } allow(controller).to receive(:params) { { force_show_diff: true } }
large_diffs = diffs * 100 #simulate 200 diffs large_diffs = diffs * 100 #simulate 200 diffs
expect(safe_diff_files(large_diffs).length).to eq(200) expect(safe_diff_files(large_diffs, diff_refs).length).to eq(200)
end end
it 'should return a limited file diffs if a commit touches more files than the hard limits and force diff is true' do it 'should return a limited file diffs if a commit touches more files than the hard limits and force diff is true' do
allow(controller).to receive(:params) { { force_show_diff: true } } allow(controller).to receive(:params) { { force_show_diff: true } }
very_large_diffs = diffs * 1000 #simulate 2000 diffs very_large_diffs = diffs * 1000 #simulate 2000 diffs
expect(safe_diff_files(very_large_diffs).length).to eq(1000) expect(safe_diff_files(very_large_diffs, diff_refs).length).to eq(1000)
end
end
describe 'parallel_diff' do
it 'should return an array of arrays containing the parsed diff' do
expect(parallel_diff(diff_file, 0)).
to match_array(parallel_diff_result_array)
end
end
describe 'generate_line_code' do
it 'should generate correct line code' do
expect(generate_line_code(diff_file.file_path, diff_file.diff_lines.first)).
to eq('2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6')
end end
end end
...@@ -126,39 +114,11 @@ describe DiffHelper do ...@@ -126,39 +114,11 @@ describe DiffHelper do
expect(diff_line_content(diff_file.diff_lines.first.text)). expect(diff_line_content(diff_file.diff_lines.first.text)).
to eq('@@ -6,12 +6,18 @@ module Popen') to eq('@@ -6,12 +6,18 @@ module Popen')
expect(diff_line_content(diff_file.diff_lines.first.type)).to eq('match') expect(diff_line_content(diff_file.diff_lines.first.type)).to eq('match')
expect(diff_line_content(diff_file.diff_lines.first.new_pos)).to eq(6) expect(diff_file.diff_lines.first.new_pos).to eq(6)
end end
end
def parallel_diff_result_array it 'should return safe HTML' do
[ expect(diff_line_content(diff_file.diff_lines.first.text)).to be_html_safe
["match", 6, "@@ -6,12 +6,18 @@ module Popen", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6", "match", 6, "@@ -6,12 +6,18 @@ module Popen", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6"], end
[nil, 6, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6", nil, 6, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6"], [nil, 7, " def popen(cmd, path=nil)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7", nil, 7, " def popen(cmd, path=nil)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"],
[nil, 8, " unless cmd.is_a?(Array)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8", nil, 8, " unless cmd.is_a?(Array)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"],
["old", 9, "- raise <span class='idiff'></span>&quot;System commands must be given as an array of strings&quot;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9", "new", 9, "+ raise <span class='idiff'>RuntimeError, </span>&quot;System commands must be given as an array of strings&quot;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"],
[nil, 10, " end", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10", nil, 10, " end", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"],
[nil, 11, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11", nil, 11, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11"],
[nil, 12, " path ||= Dir.pwd", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12", nil, 12, " path ||= Dir.pwd", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12"],
["old", 13, "- vars = { &quot;PWD&quot; =&gt; path }", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_13_13", "old", nil, "&nbsp;", nil],
["old", 14, "- options = { chdir: path }", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_13", "new", 13, "+", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_13"],
[nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14", "new", 14, "+ vars = {", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14"],
[nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15", "new", 15, "+ &quot;PWD&quot; =&gt; path", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"],
[nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16", "new", 16, "+ }", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16"],
[nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17", "new", 17, "+", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17"],
[nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18", "new", 18, "+ options = {", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18"],
[nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19", "new", 19, "+ chdir: path", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19"],
[nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20", "new", 20, "+ }", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20"],
[nil, 15, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21", nil, 21, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21"],
[nil, 16, " unless File.directory?(path)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22", nil, 22, " unless File.directory?(path)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22"],
[nil, 17, " FileUtils.mkdir_p(path)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23", nil, 23, " FileUtils.mkdir_p(path)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23"],
["match", 19, "@@ -19,6 +25,7 @@ module Popen", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25", "match", 25, "@@ -19,6 +25,7 @@ module Popen", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25"],
[nil, 19, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25", nil, 25, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25"],
[nil, 20, " @cmd_output = &quot;&quot;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26", nil, 26, " @cmd_output = &quot;&quot;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26"],
[nil, 21, " @cmd_status = 0", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27", nil, 27, " @cmd_status = 0", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27"],
[nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28", "new", 28, "+", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28"],
[nil, 22, " Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29", nil, 29, " Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29"],
[nil, 23, " @cmd_output &lt;&lt; stdout.read", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30", nil, 30, " @cmd_output &lt;&lt; stdout.read", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30"],
[nil, 24, " @cmd_output &lt;&lt; stderr.read", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31", nil, 31, " @cmd_output &lt;&lt; stderr.read", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31"]
]
end end
end end
...@@ -336,7 +336,7 @@ module Ci ...@@ -336,7 +336,7 @@ module Ci
describe "Caches" do describe "Caches" do
it "returns cache when defined globally" do it "returns cache when defined globally" do
config = YAML.dump({ config = YAML.dump({
cache: { paths: ["logs/", "binaries/"], untracked: true }, cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'key' },
rspec: { rspec: {
script: "rspec" script: "rspec"
} }
...@@ -348,13 +348,14 @@ module Ci ...@@ -348,13 +348,14 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
paths: ["logs/", "binaries/"], paths: ["logs/", "binaries/"],
untracked: true, untracked: true,
key: 'key',
) )
end end
it "returns cache when defined in a job" do it "returns cache when defined in a job" do
config = YAML.dump({ config = YAML.dump({
rspec: { rspec: {
cache: { paths: ["logs/", "binaries/"], untracked: true }, cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'key' },
script: "rspec" script: "rspec"
} }
}) })
...@@ -365,15 +366,16 @@ module Ci ...@@ -365,15 +366,16 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
paths: ["logs/", "binaries/"], paths: ["logs/", "binaries/"],
untracked: true, untracked: true,
key: 'key',
) )
end end
it "overwrite cache when defined for a job and globally" do it "overwrite cache when defined for a job and globally" do
config = YAML.dump({ config = YAML.dump({
cache: { paths: ["logs/", "binaries/"], untracked: true }, cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'global' },
rspec: { rspec: {
script: "rspec", script: "rspec",
cache: { paths: ["test/"], untracked: false }, cache: { paths: ["test/"], untracked: false, key: 'local' },
} }
}) })
...@@ -383,6 +385,7 @@ module Ci ...@@ -383,6 +385,7 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
paths: ["test/"], paths: ["test/"],
untracked: false, untracked: false,
key: 'local',
) )
end end
end end
...@@ -615,6 +618,20 @@ module Ci ...@@ -615,6 +618,20 @@ module Ci
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:paths parameter should be an array of strings") end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:paths parameter should be an array of strings")
end end
it "returns errors if cache:key is not a string" do
config = YAML.dump({ cache: { key: 1 }, rspec: { script: "test" } })
expect do
GitlabCiYamlProcessor.new(config)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:key parameter should be a string")
end
it "returns errors if job cache:key is not an a string" do
config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { key: 1 } } })
expect do
GitlabCiYamlProcessor.new(config)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:key parameter should be a string")
end
it "returns errors if job cache:untracked is not an array of strings" do it "returns errors if job cache:untracked is not an array of strings" do
config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { untracked: "string" } } }) config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { untracked: "string" } } })
expect do expect do
......
require 'spec_helper'
require 'ostruct'
describe 'DNSXLCheck', lib: true, no_db: true do
let(:spam_ip) { '127.0.0.2' }
let(:no_spam_ip) { '127.0.0.3' }
let(:invalid_ip) { 'a.b.c.d' }
let!(:dnsxl_check) { DNSXLCheck.create_from_list([OpenStruct.new({ domain: 'test', weight: 1 })]) }
before(:context) do
class DNSXLCheck::Resolver
class << self
alias_method :old_search, :search
def search(query)
return false if query.match(/always\.failing\.domain\z/)
return true if query.match(/\A2\.0\.0\.127\./)
return false if query.match(/\A3\.0\.0\.127\./)
end
end
end
end
describe '#test' do
before do
dnsxl_check.threshold = 0.75
dnsxl_check.add_list('always.failing.domain', 1)
end
context 'when threshold is used' do
before { dnsxl_check.use_threshold= true }
it { expect(dnsxl_check.test(spam_ip)).to be_falsey }
end
context 'when threshold is not used' do
before { dnsxl_check.use_threshold= false }
it { expect(dnsxl_check.test(spam_ip)).to be_truthy }
end
end
describe '#test_with_threshold' do
it { expect{ dnsxl_check.test_with_threshold(invalid_ip) }.to raise_error(ArgumentError) }
it { expect(dnsxl_check.test_with_threshold(spam_ip)).to be_truthy }
it { expect(dnsxl_check.test_with_threshold(no_spam_ip)).to be_falsey }
end
describe '#test_strict' do
before do
dnsxl_check.threshold = 1
dnsxl_check.add_list('always.failing.domain', 1)
end
it { expect{ dnsxl_check.test_strict(invalid_ip) }.to raise_error(ArgumentError) }
it { expect(dnsxl_check.test_with_threshold(spam_ip)).to be_falsey }
it { expect(dnsxl_check.test_with_threshold(no_spam_ip)).to be_falsey }
it { expect(dnsxl_check.test_strict(spam_ip)).to be_truthy }
it { expect(dnsxl_check.test_strict(no_spam_ip)).to be_falsey }
end
describe '#threshold=' do
it { expect{ dnsxl_check.threshold = 0 }.to raise_error(ArgumentError) }
it { expect{ dnsxl_check.threshold = 1.1 }.to raise_error(ArgumentError) }
it { expect{ dnsxl_check.threshold = 0.5 }.not_to raise_error }
end
end
...@@ -4,13 +4,13 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do ...@@ -4,13 +4,13 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
let(:entries) do let(:entries) do
{ 'path/' => {}, { 'path/' => {},
'path/dir_1/' => {}, 'path/dir_1/' => {},
'path/dir_1/file_1' => {}, 'path/dir_1/file_1' => { size: 10 },
'path/dir_1/file_b' => {}, 'path/dir_1/file_b' => { size: 10 },
'path/dir_1/subdir/' => {}, 'path/dir_1/subdir/' => {},
'path/dir_1/subdir/subfile' => {}, 'path/dir_1/subdir/subfile' => { size: 10 },
'path/second_dir' => {}, 'path/second_dir' => {},
'path/second_dir/dir_3/file_2' => {}, 'path/second_dir/dir_3/file_2' => { size: 10 },
'path/second_dir/dir_3/file_3'=> {}, 'path/second_dir/dir_3/file_3'=> { size: 10 },
'another_directory/'=> {}, 'another_directory/'=> {},
'another_file' => {}, 'another_file' => {},
'/file/with/absolute_path' => {} } '/file/with/absolute_path' => {} }
...@@ -112,6 +112,11 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do ...@@ -112,6 +112,11 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
subject { |example| path(example).empty? } subject { |example| path(example).empty? }
it { is_expected.to be false } it { is_expected.to be false }
end end
describe '#total_size' do
subject { |example| path(example).total_size }
it { is_expected.to eq(30) }
end
end end
end end
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Build::Artifacts::Metadata do describe Gitlab::Ci::Build::Artifacts::Metadata do
def metadata(path = '') def metadata(path = '', **opts)
described_class.new(metadata_file_path, path) described_class.new(metadata_file_path, path, **opts)
end end
let(:metadata_file_path) do let(:metadata_file_path) do
...@@ -51,6 +51,19 @@ describe Gitlab::Ci::Build::Artifacts::Metadata do ...@@ -51,6 +51,19 @@ describe Gitlab::Ci::Build::Artifacts::Metadata do
end end
end end
describe '#find_entries! recursively for other_artifacts_0.1.2/' do
subject { metadata('other_artifacts_0.1.2/', recursive: true).find_entries! }
it 'matches correct paths' do
expect(subject.keys).
to contain_exactly 'other_artifacts_0.1.2/',
'other_artifacts_0.1.2/doc_sample.txt',
'other_artifacts_0.1.2/another-subdirectory/',
'other_artifacts_0.1.2/another-subdirectory/empty_directory/',
'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif'
end
end
describe '#to_entry' do describe '#to_entry' do
subject { metadata('').to_entry } subject { metadata('').to_entry }
it { is_expected.to be_an_instance_of(Gitlab::Ci::Build::Artifacts::Metadata::Entry) } it { is_expected.to be_an_instance_of(Gitlab::Ci::Build::Artifacts::Metadata::Entry) }
......
...@@ -6,7 +6,7 @@ describe Gitlab::Diff::File, lib: true do ...@@ -6,7 +6,7 @@ describe Gitlab::Diff::File, lib: true do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:commit) { project.commit(sample_commit.id) } let(:commit) { project.commit(sample_commit.id) }
let(:diff) { commit.diffs.first } let(:diff) { commit.diffs.first }
let(:diff_file) { Gitlab::Diff::File.new(diff) } let(:diff_file) { Gitlab::Diff::File.new(diff, [commit.parent, commit]) }
describe :diff_lines do describe :diff_lines do
let(:diff_lines) { diff_file.diff_lines } let(:diff_lines) { diff_file.diff_lines }
......
require 'spec_helper'
describe Gitlab::Diff::Highlight, lib: true do
include RepoHelpers
let(:project) { create(:project) }
let(:commit) { project.commit(sample_commit.id) }
let(:diff) { commit.diffs.first }
let(:diff_file) { Gitlab::Diff::File.new(diff, [commit.parent, commit]) }
describe '#highlight' do
let(:diff_lines) { Gitlab::Diff::Highlight.new(diff_file).highlight }
it 'should return Gitlab::Diff::Line elements' do
expect(diff_lines.first).to be_an_instance_of(Gitlab::Diff::Line)
end
it 'should not modify "match" lines' do
expect(diff_lines[0].text).to eq('@@ -6,12 +6,18 @@ module Popen')
expect(diff_lines[22].text).to eq('@@ -19,6 +25,7 @@ module Popen')
end
it 'should highlight unchanged lines' do
code = %Q{ <span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>\n}
expect(diff_lines[2].text).to eq(code)
end
it 'should highlight removed lines' do
code = %Q{-<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>\n}
expect(diff_lines[4].text).to eq(code)
end
it 'should highlight added lines' do
code = %Q{+<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff'> </span><span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>\n}
expect(diff_lines[5].text).to eq(code)
end
end
end
require 'spec_helper'
describe Gitlab::Diff::InlineDiffMarker, lib: true do
describe '#inline_diffs' do
let(:raw) { "abc 'def'" }
let(:rich) { %{<span class="abc">abc</span><span class="space"> </span><span class="def">&#39;def&#39;</span>} }
let(:inline_diffs) { [2..5] }
let(:subject) { Gitlab::Diff::InlineDiffMarker.new(raw, rich).mark(inline_diffs) }
it 'marks the inline diffs' do
expect(subject).to eq(%{<span class="abc">ab<span class='idiff'>c</span></span><span class="space"><span class='idiff'> </span></span><span class="def"><span class='idiff'>&#39;d</span>ef&#39;</span>})
end
end
end
require 'spec_helper'
describe Gitlab::Diff::InlineDiff, lib: true do
describe '#inline_diffs' do
let(:diff) do
<<eos
class Test
- def initialize(test = true)
+ def initialize(test = false)
@test = test
end
end
eos
end
let(:subject) { Gitlab::Diff::InlineDiff.new(diff.lines).inline_diffs }
it 'finds all inline diffs' do
expect(subject[0]).to be_nil
expect(subject[1]).to eq([25..27])
expect(subject[2]).to eq([25..28])
expect(subject[3]).to be_nil
expect(subject[4]).to be_nil
expect(subject[5]).to be_nil
end
end
end
require 'spec_helper'
describe Gitlab::Diff::ParallelDiff, lib: true do
include RepoHelpers
let(:project) { create(:project) }
let(:repository) { project.repository }
let(:commit) { project.commit(sample_commit.id) }
let(:diffs) { commit.diffs }
let(:diff) { diffs.first }
let(:diff_refs) { [commit.parent, commit] }
let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs) }
subject { described_class.new(diff_file) }
let(:parallel_diff_result_array) { YAML.load_file("#{Rails.root}/spec/fixtures/parallel_diff_result.yml") }
describe '#parallelize' do
it 'should return an array of arrays containing the parsed diff' do
expect(subject.parallelize).to match_array(parallel_diff_result_array)
end
end
end
...@@ -86,7 +86,7 @@ eos ...@@ -86,7 +86,7 @@ eos
it { expect(line.type).to eq(nil) } it { expect(line.type).to eq(nil) }
it { expect(line.old_pos).to eq(24) } it { expect(line.old_pos).to eq(24) }
it { expect(line.new_pos).to eq(31) } it { expect(line.new_pos).to eq(31) }
it { expect(line.text).to eq(' @cmd_output &lt;&lt; stderr.read') } it { expect(line.text).to eq(' @cmd_output << stderr.read') }
end end
end end
end end
......
...@@ -2,8 +2,11 @@ require 'spec_helper' ...@@ -2,8 +2,11 @@ require 'spec_helper'
describe Gitlab::GithubImport::PullRequestFormatter, lib: true do describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:source_branch) { OpenStruct.new(ref: 'feature') } let(:repository) { OpenStruct.new(id: 1, fork: false) }
let(:target_branch) { OpenStruct.new(ref: 'master') } let(:source_repo) { repository }
let(:source_branch) { OpenStruct.new(ref: 'feature', repo: source_repo) }
let(:target_repo) { repository }
let(:target_branch) { OpenStruct.new(ref: 'master', repo: target_repo) }
let(:octocat) { OpenStruct.new(id: 123456, login: 'octocat') } let(:octocat) { OpenStruct.new(id: 123456, login: 'octocat') }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') } let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') } let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
...@@ -125,10 +128,8 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -125,10 +128,8 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end end
describe '#cross_project?' do describe '#cross_project?' do
context 'when source repo is not a fork' do context 'when source, and target repositories are the same' do
let(:local_repo) { OpenStruct.new(fork: false) } let(:raw_data) { OpenStruct.new(base_data) }
let(:source_branch) { OpenStruct.new(ref: 'feature', repo: local_repo) }
let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch)) }
it 'returns false' do it 'returns false' do
expect(pull_request.cross_project?).to eq false expect(pull_request.cross_project?).to eq false
...@@ -136,9 +137,17 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do ...@@ -136,9 +137,17 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end end
context 'when source repo is a fork' do context 'when source repo is a fork' do
let(:forked_repo) { OpenStruct.new(fork: true) } let(:source_repo) { OpenStruct.new(id: 2, fork: true) }
let(:source_branch) { OpenStruct.new(ref: 'feature', repo: forked_repo) } let(:raw_data) { OpenStruct.new(base_data) }
let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch)) }
it 'returns true' do
expect(pull_request.cross_project?).to eq true
end
end
context 'when target repo is a fork' do
let(:target_repo) { OpenStruct.new(id: 2, fork: true) }
let(:raw_data) { OpenStruct.new(base_data) }
it 'returns true' do it 'returns true' do
expect(pull_request.cross_project?).to eq true expect(pull_request.cross_project?).to eq true
......
require 'spec_helper'
describe Gitlab::Highlight, lib: true do
include RepoHelpers
let(:project) { create(:project) }
let(:commit) { project.commit(sample_commit.id) }
describe '.highlight_lines' do
let(:lines) do
Gitlab::Highlight.highlight_lines(project.repository, commit.id, 'files/ruby/popen.rb')
end
it 'should properly highlight all the lines' do
expect(lines[4]).to eq(%Q{<span id="LC5" class="line"> <span class="kp">extend</span> <span class="nb">self</span></span>\n})
expect(lines[21]).to eq(%Q{<span id="LC22" class="line"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>\n})
expect(lines[26]).to eq(%Q{<span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>\n})
end
end
end
require 'spec_helper'
describe Gitlab::InlineDiff, lib: true do
describe '#processing' do
let(:diff) do
<<eos
--- a/test.rb
+++ b/test.rb
@@ -1,6 +1,6 @@
class Test
def cleanup_string(input)
return nil if input.nil?
- input.sub(/[\\r\\n].+/,'').sub(/\\\\[rn].+/, '').strip
+ input.to_s.sub(/[\\r\\n].+/,'').sub(/\\\\[rn].+/, '').strip
end
end
eos
end
let(:expected) do
["--- a/test.rb\n",
"+++ b/test.rb\n",
"@@ -1,6 +1,6 @@\n",
" class Test\n",
" def cleanup_string(input)\n",
" return nil if input.nil?\n",
"- input.#!idiff-start!##!idiff-finish!#sub(/[\\r\\n].+/,'').sub(/\\\\[rn].+/, '').strip\n",
"+ input.#!idiff-start!#to_s.#!idiff-finish!#sub(/[\\r\\n].+/,'').sub(/\\\\[rn].+/, '').strip\n",
" end\n",
" end\n"]
end
let(:subject) { Gitlab::InlineDiff.processing(diff.lines) }
it 'should retain backslashes' do
expect(subject).to eq(expected)
end
end
end
...@@ -37,7 +37,7 @@ describe Gitlab::LDAP::User, lib: true do ...@@ -37,7 +37,7 @@ describe Gitlab::LDAP::User, lib: true do
end end
it "dont marks existing ldap user as changed" do it "dont marks existing ldap user as changed" do
create(:omniauth_user, email: 'john@example.com', extern_uid: 'my-uid', provider: 'ldapmain') create(:omniauth_user, email: 'john@example.com', extern_uid: 'my-uid', provider: 'ldapmain', ldap_email: true)
expect(ldap_user.changed?).to be_falsey expect(ldap_user.changed?).to be_falsey
end end
end end
...@@ -110,6 +110,32 @@ describe Gitlab::LDAP::User, lib: true do ...@@ -110,6 +110,32 @@ describe Gitlab::LDAP::User, lib: true do
end end
end end
describe 'updating email' do
context "when LDAP sets an email" do
it "has a real email" do
expect(ldap_user.gl_user.email).to eq(info[:email])
end
it "has ldap_email set to true" do
expect(ldap_user.gl_user.ldap_email?).to be(true)
end
end
context "when LDAP doesn't set an email" do
before do
info.delete(:email)
end
it "has a temp email" do
expect(ldap_user.gl_user.temp_oauth_email?).to be(true)
end
it "has ldap_email set to false" do
expect(ldap_user.gl_user.ldap_email?).to be(false)
end
end
end
describe 'blocking' do describe 'blocking' do
def configure_block(value) def configure_block(value)
allow_any_instance_of(Gitlab::LDAP::Config). allow_any_instance_of(Gitlab::LDAP::Config).
......
...@@ -37,7 +37,8 @@ describe 'Gitlab::NoteDataBuilder', lib: true do ...@@ -37,7 +37,8 @@ describe 'Gitlab::NoteDataBuilder', lib: true do
it 'returns the note and issue-specific data' do it 'returns the note and issue-specific data' do
expect(data).to have_key(:issue) expect(data).to have_key(:issue)
expect(data[:issue]).to eq(issue.hook_attrs) expect(data[:issue].except('updated_at')).to eq(issue.hook_attrs.except('updated_at'))
expect(data[:issue]['updated_at']).to be > issue.hook_attrs['updated_at']
end end
end end
...@@ -47,7 +48,8 @@ describe 'Gitlab::NoteDataBuilder', lib: true do ...@@ -47,7 +48,8 @@ describe 'Gitlab::NoteDataBuilder', lib: true do
it 'returns the note and merge request data' do it 'returns the note and merge request data' do
expect(data).to have_key(:merge_request) expect(data).to have_key(:merge_request)
expect(data[:merge_request]).to eq(merge_request.hook_attrs) expect(data[:merge_request].except('updated_at')).to eq(merge_request.hook_attrs.except('updated_at'))
expect(data[:merge_request]['updated_at']).to be > merge_request.hook_attrs['updated_at']
end end
end end
...@@ -57,7 +59,8 @@ describe 'Gitlab::NoteDataBuilder', lib: true do ...@@ -57,7 +59,8 @@ describe 'Gitlab::NoteDataBuilder', lib: true do
it 'returns the note and merge request diff data' do it 'returns the note and merge request diff data' do
expect(data).to have_key(:merge_request) expect(data).to have_key(:merge_request)
expect(data[:merge_request]).to eq(merge_request.hook_attrs) expect(data[:merge_request].except('updated_at')).to eq(merge_request.hook_attrs.except('updated_at'))
expect(data[:merge_request]['updated_at']).to be > merge_request.hook_attrs['updated_at']
end end
end end
...@@ -67,7 +70,8 @@ describe 'Gitlab::NoteDataBuilder', lib: true do ...@@ -67,7 +70,8 @@ describe 'Gitlab::NoteDataBuilder', lib: true do
it 'returns the note and project snippet data' do it 'returns the note and project snippet data' do
expect(data).to have_key(:snippet) expect(data).to have_key(:snippet)
expect(data[:snippet]).to eq(snippet.hook_attrs) expect(data[:snippet].except('updated_at')).to eq(snippet.hook_attrs.except('updated_at'))
expect(data[:snippet]['updated_at']).to be > snippet.hook_attrs['updated_at']
end end
end end
end end
...@@ -40,14 +40,38 @@ describe Notify do ...@@ -40,14 +40,38 @@ describe Notify do
end end
end end
shared_examples 'an email with X-GitLab headers containing project details' do
it 'has X-GitLab-Project* headers' do
is_expected.to have_header 'X-GitLab-Project', /#{project.name}/
is_expected.to have_header 'X-GitLab-Project-Id', /#{project.id}/
is_expected.to have_header 'X-GitLab-Project-Path', /#{project.path_with_namespace}/
end
end
shared_examples 'an email with X-GitLab headers containing build details' do
it 'has X-GitLab-Build* headers' do
is_expected.to have_header 'X-GitLab-Build-Id', /#{build.id}/
is_expected.to have_header 'X-GitLab-Build-Ref', /#{build.ref}/
end
end
shared_examples 'an email that contains a header with author username' do
it 'has X-GitLab-Author header containing author\'s username' do
is_expected.to have_header 'X-GitLab-Author', user.username
end
end
shared_examples 'an email starting a new thread' do |message_id_prefix| shared_examples 'an email starting a new thread' do |message_id_prefix|
include_examples 'an email with X-GitLab headers containing project details'
it 'has a discussion identifier' do it 'has a discussion identifier' do
is_expected.to have_header 'Message-ID', /<#{message_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/ is_expected.to have_header 'Message-ID', /<#{message_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'X-GitLab-Project', /#{project.name}/
end end
end end
shared_examples 'an answer to an existing thread' do |thread_id_prefix| shared_examples 'an answer to an existing thread' do |thread_id_prefix|
include_examples 'an email with X-GitLab headers containing project details'
it 'has a subject that begins with Re: ' do it 'has a subject that begins with Re: ' do
is_expected.to have_subject /^Re: / is_expected.to have_subject /^Re: /
end end
...@@ -56,7 +80,6 @@ describe Notify do ...@@ -56,7 +80,6 @@ describe Notify do
is_expected.to have_header 'Message-ID', /<(.*)@#{Gitlab.config.gitlab.host}>/ is_expected.to have_header 'Message-ID', /<(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'References', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/ is_expected.to have_header 'References', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/ is_expected.to have_header 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'X-GitLab-Project', /#{project.name}/
end end
end end
...@@ -656,6 +679,8 @@ describe Notify do ...@@ -656,6 +679,8 @@ describe Notify do
it_behaves_like 'it should not have Gmail Actions links' it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link" it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
it 'is sent as the author' do it 'is sent as the author' do
sender = subject.header[:from].addrs[0] sender = subject.header[:from].addrs[0]
...@@ -685,6 +710,8 @@ describe Notify do ...@@ -685,6 +710,8 @@ describe Notify do
it_behaves_like 'it should not have Gmail Actions links' it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link" it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
it 'is sent as the author' do it 'is sent as the author' do
sender = subject.header[:from].addrs[0] sender = subject.header[:from].addrs[0]
...@@ -713,6 +740,8 @@ describe Notify do ...@@ -713,6 +740,8 @@ describe Notify do
it_behaves_like 'it should not have Gmail Actions links' it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link" it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
it 'is sent as the author' do it 'is sent as the author' do
sender = subject.header[:from].addrs[0] sender = subject.header[:from].addrs[0]
...@@ -737,6 +766,8 @@ describe Notify do ...@@ -737,6 +766,8 @@ describe Notify do
it_behaves_like 'it should not have Gmail Actions links' it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link" it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
it 'is sent as the author' do it 'is sent as the author' do
sender = subject.header[:from].addrs[0] sender = subject.header[:from].addrs[0]
...@@ -765,6 +796,8 @@ describe Notify do ...@@ -765,6 +796,8 @@ describe Notify do
it_behaves_like 'it should not have Gmail Actions links' it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link" it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
it 'is sent as the author' do it 'is sent as the author' do
sender = subject.header[:from].addrs[0] sender = subject.header[:from].addrs[0]
...@@ -871,6 +904,8 @@ describe Notify do ...@@ -871,6 +904,8 @@ describe Notify do
it_behaves_like 'it should show Gmail Actions View Commit link' it_behaves_like 'it should show Gmail Actions View Commit link'
it_behaves_like "a user cannot unsubscribe through footer link" it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
it 'is sent as the author' do it 'is sent as the author' do
sender = subject.header[:from].addrs[0] sender = subject.header[:from].addrs[0]
...@@ -904,6 +939,15 @@ describe Notify do ...@@ -904,6 +939,15 @@ describe Notify do
subject { Notify.build_success_email(build.id, 'wow@example.com') } subject { Notify.build_success_email(build.id, 'wow@example.com') }
it_behaves_like 'an email with X-GitLab headers containing build details'
it_behaves_like 'an email with X-GitLab headers containing project details' do
let(:project) { build.project }
end
it 'has header indicating build status' do
is_expected.to have_header 'X-GitLab-Build-Status', 'success'
end
it 'has the correct subject' do it 'has the correct subject' do
should have_subject /Build success for/ should have_subject /Build success for/
end end
...@@ -918,6 +962,15 @@ describe Notify do ...@@ -918,6 +962,15 @@ describe Notify do
subject { Notify.build_fail_email(build.id, 'wow@example.com') } subject { Notify.build_fail_email(build.id, 'wow@example.com') }
it_behaves_like 'an email with X-GitLab headers containing build details'
it_behaves_like 'an email with X-GitLab headers containing project details' do
let(:project) { build.project }
end
it 'has header indicating build status' do
is_expected.to have_header 'X-GitLab-Build-Status', 'failed'
end
it 'has the correct subject' do it 'has the correct subject' do
should have_subject /Build failed for/ should have_subject /Build failed for/
end end
......
...@@ -41,6 +41,8 @@ ...@@ -41,6 +41,8 @@
# recaptcha_site_key :string # recaptcha_site_key :string
# recaptcha_private_key :string # recaptcha_private_key :string
# metrics_port :integer default(8089) # metrics_port :integer default(8089)
# sentry_enabled :boolean default(FALSE)
# sentry_dsn :string
# #
require 'spec_helper' require 'spec_helper'
......
...@@ -362,12 +362,12 @@ describe Ci::Build, models: true do ...@@ -362,12 +362,12 @@ describe Ci::Build, models: true do
subject { build.artifacts_browse_url } subject { build.artifacts_browse_url }
it "should be nil if artifacts browser is unsupported" do it "should be nil if artifacts browser is unsupported" do
allow(build).to receive(:artifacts_browser_supported?).and_return(false) allow(build).to receive(:artifacts_metadata?).and_return(false)
is_expected.to be_nil is_expected.to be_nil
end end
it 'should not be nil if artifacts browser is supported' do it 'should not be nil if artifacts browser is supported' do
allow(build).to receive(:artifacts_browser_supported?).and_return(true) allow(build).to receive(:artifacts_metadata?).and_return(true)
is_expected.to_not be_nil is_expected.to_not be_nil
end end
end end
...@@ -391,8 +391,8 @@ describe Ci::Build, models: true do ...@@ -391,8 +391,8 @@ describe Ci::Build, models: true do
end end
describe :artifacts_browser_supported? do describe :artifacts_metadata? do
subject { build.artifacts_browser_supported? } subject { build.artifacts_metadata? }
context 'artifacts metadata does not exist' do context 'artifacts metadata does not exist' do
it { is_expected.to be_falsy } it { is_expected.to be_falsy }
end end
......
...@@ -90,6 +90,29 @@ describe API::API, api: true do ...@@ -90,6 +90,29 @@ describe API::API, api: true do
end end
end end
context 'and using the visibility filter' do
it 'should filter based on private visibility param' do
get api('/projects', user), { visibility: 'private' }
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::PRIVATE).count)
end
it 'should filter based on internal visibility param' do
get api('/projects', user), { visibility: 'internal' }
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::INTERNAL).count)
end
it 'should filter based on public visibility param' do
get api('/projects', user), { visibility: 'public' }
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::PUBLIC).count)
end
end
context 'and using sorting' do context 'and using sorting' do
before do before do
project2 project2
......
...@@ -6,7 +6,7 @@ describe MergeRequests::MergeWhenBuildSucceedsService do ...@@ -6,7 +6,7 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
let(:mr_merge_if_green_enabled) do let(:mr_merge_if_green_enabled) do
create(:merge_request, merge_when_build_succeeds: true, merge_user: user, create(:merge_request, merge_when_build_succeeds: true, merge_user: user,
source_branch: "source_branch", target_branch: project.default_branch, source_branch: "master", target_branch: 'feature',
source_project: project, target_project: project, state: "opened") source_project: project, target_project: project, state: "opened")
end end
......
...@@ -227,7 +227,7 @@ describe NotificationService, services: true do ...@@ -227,7 +227,7 @@ describe NotificationService, services: true do
end end
describe :reassigned_issue do describe :reassigned_issue do
it 'should email new assignee' do it 'emails new assignee' do
notification.reassigned_issue(issue, @u_disabled) notification.reassigned_issue(issue, @u_disabled)
should_email(issue.assignee) should_email(issue.assignee)
...@@ -238,6 +238,62 @@ describe NotificationService, services: true do ...@@ -238,6 +238,62 @@ describe NotificationService, services: true do
should_not_email(@u_participating) should_not_email(@u_participating)
should_not_email(@u_disabled) should_not_email(@u_disabled)
end end
it 'emails previous assignee even if he has the "on mention" notif level' do
issue.update_attribute(:assignee, @u_mentioned)
issue.update_attributes(assignee: @u_watcher)
notification.reassigned_issue(issue, @u_disabled)
should_email(@u_mentioned)
should_email(@u_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
should_not_email(@unsubscriber)
should_not_email(@u_participating)
should_not_email(@u_disabled)
end
it 'emails new assignee even if he has the "on mention" notif level' do
issue.update_attributes(assignee: @u_mentioned)
notification.reassigned_issue(issue, @u_disabled)
expect(issue.assignee).to be @u_mentioned
should_email(issue.assignee)
should_email(@u_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
should_not_email(@unsubscriber)
should_not_email(@u_participating)
should_not_email(@u_disabled)
end
it 'emails new assignee' do
issue.update_attribute(:assignee, @u_mentioned)
notification.reassigned_issue(issue, @u_disabled)
expect(issue.assignee).to be @u_mentioned
should_email(issue.assignee)
should_email(@u_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
should_not_email(@unsubscriber)
should_not_email(@u_participating)
should_not_email(@u_disabled)
end
it 'does not email new assignee if they are the current user' do
issue.update_attribute(:assignee, @u_mentioned)
notification.reassigned_issue(issue, @u_mentioned)
expect(issue.assignee).to be @u_mentioned
should_email(@u_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
should_not_email(issue.assignee)
should_not_email(@unsubscriber)
should_not_email(@u_participating)
should_not_email(@u_disabled)
end
end end
describe :close_issue do describe :close_issue do
......
...@@ -32,6 +32,7 @@ describe Projects::CreateService, services: true do ...@@ -32,6 +32,7 @@ describe Projects::CreateService, services: true do
it { expect(@project).to be_valid } it { expect(@project).to be_valid }
it { expect(@project.owner).to eq(@user) } it { expect(@project.owner).to eq(@user) }
it { expect(@project.team.masters).to include(@user) }
it { expect(@project.namespace).to eq(@user.namespace) } it { expect(@project.namespace).to eq(@user.namespace) }
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment