Commit b6ea85ec authored by Valery Sizov's avatar Valery Sizov

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into ce_upstream

parents 89a63467 99f08b3f
...@@ -6,14 +6,23 @@ v 8.6.0 (unreleased) ...@@ -6,14 +6,23 @@ v 8.6.0 (unreleased)
- Fix issue when pushing to projects ending in .wiki - Fix issue when pushing to projects ending in .wiki
- Fix avatar stretching by providing a cropping feature (Johann Pardanaud) - Fix avatar stretching by providing a cropping feature (Johann Pardanaud)
- Don't load all of GitLab in mail_room - Don't load all of GitLab in mail_room
- Indicate how much an MR diverged from the target branch (Pierre de La Morinerie)
- Strip leading and trailing spaces in URL validator (evuez) - Strip leading and trailing spaces in URL validator (evuez)
- Return empty array instead of 404 when commit has no statuses in commit status API - Return empty array instead of 404 when commit has no statuses in commit status API
- Add support for cross-project label references
- Update documentation to reflect Guest role not being enforced on internal projects - Update documentation to reflect Guest role not being enforced on internal projects
- Allow search for logged out users - Allow search for logged out users
- Don't show Issues/MRs from archived projects in Groups view - Don't show Issues/MRs from archived projects in Groups view
- Increase the notes polling timeout over time (Roberto Dip)
v 8.5.4
- Do not cache requests for badges (including builds badge)
v 8.5.3 v 8.5.3
- Flush repository caches before renaming projects - Flush repository caches before renaming projects
- Sort starred projects on dashboard based on last activity by default
- Show commit message in JIRA mention comment
- Makes issue page and merge request page usable on mobile browsers.
v 8.5.2 v 8.5.2
- Fix sidebar overlapping content when screen width was below 1200px - Fix sidebar overlapping content when screen width was below 1200px
......
...@@ -363,7 +363,8 @@ description area. Copy-paste it to retain the markdown format. ...@@ -363,7 +363,8 @@ description area. Copy-paste it to retain the markdown format.
to a new table or remove an old table) to aid retrying on failure to a new table or remove an old table) to aid retrying on failure
1. Keeps the GitLab code base clean and well structured 1. Keeps the GitLab code base clean and well structured
1. Contains functionality we think other users will benefit from too 1. Contains functionality we think other users will benefit from too
1. Doesn't add configuration options since they complicate future changes 1. Doesn't add configuration options or settings options since they complicate
making and testing future changes
1. Changes after submitting the merge request should be in separate commits 1. Changes after submitting the merge request should be in separate commits
(no squashing). If necessary, you will be asked to squash when the review is (no squashing). If necessary, you will be asked to squash when the review is
over, before merging. over, before merging.
......
...@@ -54,7 +54,7 @@ gem "browser", '~> 1.0.0' ...@@ -54,7 +54,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", '~> 8.2' gem "gitlab_git", '~> 9.0'
# 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
...@@ -80,7 +80,7 @@ gem "kaminari", "~> 0.16.3" ...@@ -80,7 +80,7 @@ gem "kaminari", "~> 0.16.3"
gem "haml-rails", '~> 0.9.0' gem "haml-rails", '~> 0.9.0'
# Files attachments # Files attachments
gem "carrierwave", '~> 0.9.0' gem "carrierwave", '~> 0.10.0'
# Image editing # Image editing
gem "mini_magick", '~> 4.4.0' gem "mini_magick", '~> 4.4.0'
...@@ -270,10 +270,10 @@ group :development, :test do ...@@ -270,10 +270,10 @@ group :development, :test do
gem 'awesome_print', '~> 1.2.0', require: false gem 'awesome_print', '~> 1.2.0', require: false
gem 'fuubar', '~> 2.0.0' gem 'fuubar', '~> 2.0.0'
gem 'database_cleaner', '~> 1.4.0' gem 'database_cleaner', '~> 1.4.0'
gem 'factory_girl_rails', '~> 4.3.0' gem 'factory_girl_rails', '~> 4.6.0'
gem 'rspec-rails', '~> 3.3.0' gem 'rspec-rails', '~> 3.3.0'
gem 'spinach-rails', '~> 0.2.1' gem 'spinach-rails', '~> 0.2.1'
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826) # Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.7.0' gem 'minitest', '~> 5.7.0'
...@@ -288,7 +288,7 @@ group :development, :test do ...@@ -288,7 +288,7 @@ group :development, :test do
gem 'teaspoon', '~> 1.0.0' gem 'teaspoon', '~> 1.0.0'
gem 'teaspoon-jasmine', '~> 2.2.0' gem 'teaspoon-jasmine', '~> 2.2.0'
gem 'spring', '~> 1.3.6' gem 'spring', '~> 1.6.4'
gem 'spring-commands-rspec', '~> 1.0.4' gem 'spring-commands-rspec', '~> 1.0.4'
gem 'spring-commands-spinach', '~> 1.0.0' gem 'spring-commands-spinach', '~> 1.0.0'
gem 'spring-commands-teaspoon', '~> 0.0.2' gem 'spring-commands-teaspoon', '~> 0.0.2'
......
...@@ -117,10 +117,11 @@ GEM ...@@ -117,10 +117,11 @@ GEM
capybara-screenshot (1.0.11) capybara-screenshot (1.0.11)
capybara (>= 1.0, < 3) capybara (>= 1.0, < 3)
launchy launchy
carrierwave (0.9.0) carrierwave (0.10.0)
activemodel (>= 3.2.0) activemodel (>= 3.2.0)
activesupport (>= 3.2.0) activesupport (>= 3.2.0)
json (>= 1.7) json (>= 1.7)
mime-types (>= 1.16)
cause (0.1) cause (0.1)
charlock_holmes (0.7.3) charlock_holmes (0.7.3)
chunky_png (1.3.5) chunky_png (1.3.5)
...@@ -207,10 +208,10 @@ GEM ...@@ -207,10 +208,10 @@ GEM
excon (0.45.4) excon (0.45.4)
execjs (2.6.0) execjs (2.6.0)
expression_parser (0.9.0) expression_parser (0.9.0)
factory_girl (4.3.0) factory_girl (4.5.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
factory_girl_rails (4.3.0) factory_girl_rails (4.6.0)
factory_girl (~> 4.3.0) factory_girl (~> 4.5.0)
railties (>= 3.0.0) railties (>= 3.0.0)
faraday (0.9.2) faraday (0.9.2)
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
...@@ -379,7 +380,7 @@ GEM ...@@ -379,7 +380,7 @@ GEM
gitlab-license (0.0.4) gitlab-license (0.0.4)
gitlab_emoji (0.3.1) gitlab_emoji (0.3.1)
gemojione (~> 2.2, >= 2.2.1) gemojione (~> 2.2, >= 2.2.1)
gitlab_git (8.2.0) gitlab_git (9.0.0)
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)
...@@ -787,7 +788,7 @@ GEM ...@@ -787,7 +788,7 @@ GEM
capybara (>= 2.0.0) capybara (>= 2.0.0)
railties (>= 3) railties (>= 3)
spinach (>= 0.4) spinach (>= 0.4)
spring (1.3.6) spring (1.6.4)
spring-commands-rspec (1.0.4) spring-commands-rspec (1.0.4)
spring (>= 0.9.1) spring (>= 0.9.1)
spring-commands-spinach (1.0.0) spring-commands-spinach (1.0.0)
...@@ -925,7 +926,7 @@ DEPENDENCIES ...@@ -925,7 +926,7 @@ DEPENDENCIES
cal-heatmap-rails (~> 3.5.0) cal-heatmap-rails (~> 3.5.0)
capybara (~> 2.4.0) capybara (~> 2.4.0)
capybara-screenshot (~> 1.0.0) capybara-screenshot (~> 1.0.0)
carrierwave (~> 0.9.0) carrierwave (~> 0.10.0)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
coffee-rails (~> 4.1.0) coffee-rails (~> 4.1.0)
colorize (~> 0.7.0) colorize (~> 0.7.0)
...@@ -945,7 +946,7 @@ DEPENDENCIES ...@@ -945,7 +946,7 @@ DEPENDENCIES
elasticsearch-rails elasticsearch-rails
email_reply_parser (~> 0.5.8) email_reply_parser (~> 0.5.8)
email_spec (~> 1.6.0) email_spec (~> 1.6.0)
factory_girl_rails (~> 4.3.0) factory_girl_rails (~> 4.6.0)
ffaker (~> 2.0.0) ffaker (~> 2.0.0)
flay flay
flog flog
...@@ -960,7 +961,7 @@ DEPENDENCIES ...@@ -960,7 +961,7 @@ DEPENDENCIES
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 0.0.4) gitlab-license (~> 0.0.4)
gitlab_emoji (~> 0.3.0) gitlab_emoji (~> 0.3.0)
gitlab_git (~> 8.2) gitlab_git (~> 9.0)
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)
...@@ -1049,7 +1050,7 @@ DEPENDENCIES ...@@ -1049,7 +1050,7 @@ DEPENDENCIES
six (~> 0.2.0) six (~> 0.2.0)
slack-notifier (~> 1.2.0) slack-notifier (~> 1.2.0)
spinach-rails (~> 0.2.1) spinach-rails (~> 0.2.1)
spring (~> 1.3.6) spring (~> 1.6.4)
spring-commands-rspec (~> 1.0.4) spring-commands-rspec (~> 1.0.4)
spring-commands-spinach (~> 1.0.0) spring-commands-spinach (~> 1.0.0)
spring-commands-teaspoon (~> 0.0.2) spring-commands-teaspoon (~> 0.0.2)
......
...@@ -224,41 +224,41 @@ $ -> ...@@ -224,41 +224,41 @@ $ ->
.off 'breakpoint:change' .off 'breakpoint:change'
.on 'breakpoint:change', (e, breakpoint) -> .on 'breakpoint:change', (e, breakpoint) ->
if breakpoint is 'sm' or breakpoint is 'xs' if breakpoint is 'sm' or breakpoint is 'xs'
$gutterIcon = $('.gutter-toggle').find('i') $gutterIcon = $('aside .gutter-toggle').find('i')
if $gutterIcon.hasClass('fa-angle-double-right') if $gutterIcon.hasClass('fa-angle-double-right')
$gutterIcon.closest('a').trigger('click') $gutterIcon.closest('a').trigger('click')
$(document) $(document)
.off 'click', 'aside .gutter-toggle' .off 'click', 'aside .gutter-toggle'
.on 'click', 'aside .gutter-toggle', (e) -> .on 'click', 'aside .gutter-toggle', (e, triggered) ->
e.preventDefault() e.preventDefault()
$this = $(this) $this = $(this)
$thisIcon = $this.find 'i' $thisIcon = $this.find 'i'
$allGutterToggleIcons = $('.gutter-toggle i')
if $thisIcon.hasClass('fa-angle-double-right') if $thisIcon.hasClass('fa-angle-double-right')
$thisIcon $allGutterToggleIcons
.removeClass('fa-angle-double-right') .removeClass('fa-angle-double-right')
.addClass('fa-angle-double-left') .addClass('fa-angle-double-left')
$this $('aside.right-sidebar')
.closest('aside')
.removeClass('right-sidebar-expanded') .removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed') .addClass('right-sidebar-collapsed')
$('.page-with-sidebar') $('.page-with-sidebar')
.removeClass('right-sidebar-expanded') .removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed') .addClass('right-sidebar-collapsed')
else else
$thisIcon $allGutterToggleIcons
.removeClass('fa-angle-double-left') .removeClass('fa-angle-double-left')
.addClass('fa-angle-double-right') .addClass('fa-angle-double-right')
$this $('aside.right-sidebar')
.closest('aside')
.removeClass('right-sidebar-collapsed') .removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded') .addClass('right-sidebar-expanded')
$('.page-with-sidebar') $('.page-with-sidebar')
.removeClass('right-sidebar-collapsed') .removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded') .addClass('right-sidebar-expanded')
$.cookie("collapsed_gutter", if not triggered
$('.right-sidebar') $.cookie("collapsed_gutter",
.hasClass('right-sidebar-collapsed'), { path: '/' }) $('.right-sidebar')
.hasClass('right-sidebar-collapsed'), { path: '/' })
bootstrapBreakpoint = undefined; bootstrapBreakpoint = undefined;
checkBootstrapBreakpoints = -> checkBootstrapBreakpoints = ->
......
...@@ -24,10 +24,7 @@ keyCodeIs = (e, keyCode) -> ...@@ -24,10 +24,7 @@ keyCodeIs = (e, keyCode) ->
$(document).on 'keydown.quick_submit', '.js-quick-submit', (e) -> $(document).on 'keydown.quick_submit', '.js-quick-submit', (e) ->
return unless keyCodeIs(e, 13) # Enter return unless keyCodeIs(e, 13) # Enter
if isMac() return unless (e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey) || (e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey)
return unless (e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey)
else
return unless (e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey)
e.preventDefault() e.preventDefault()
......
...@@ -195,6 +195,6 @@ class @MergeRequestTabs ...@@ -195,6 +195,6 @@ class @MergeRequestTabs
setTimeout( -> setTimeout( ->
# Only when sidebar is collapsed # Only when sidebar is collapsed
if $gutterIcon.is('.fa-angle-double-right') if $gutterIcon.is('.fa-angle-double-right')
$gutterIcon.closest('a').trigger('click') $gutterIcon.closest('a').trigger('click',[true])
, 0) , 0)
...@@ -16,11 +16,13 @@ class @Notes ...@@ -16,11 +16,13 @@ class @Notes
@view = view @view = view
@noteable_url = document.URL @noteable_url = document.URL
@notesCountBadge ||= $(".issuable-details").find(".notes-tab .badge") @notesCountBadge ||= $(".issuable-details").find(".notes-tab .badge")
@basePollingInterval = 15000
@maxPollingSteps = 4
@initRefresh()
@setupMainTargetNoteForm()
@cleanBinding() @cleanBinding()
@addBinding() @addBinding()
@setPollingInterval()
@setupMainTargetNoteForm()
@initTaskList() @initTaskList()
addBinding: -> addBinding: ->
...@@ -37,7 +39,7 @@ class @Notes ...@@ -37,7 +39,7 @@ class @Notes
# Reopen and close actions for Issue/MR combined with note form submit # Reopen and close actions for Issue/MR combined with note form submit
$(document).on "click", ".js-comment-button", @updateCloseButton $(document).on "click", ".js-comment-button", @updateCloseButton
$(document).on "keyup", ".js-note-text", @updateTargetButtons $(document).on "keyup input", ".js-note-text", @updateTargetButtons
# remove a note (in general) # remove a note (in general)
$(document).on "click", ".js-note-delete", @removeNote $(document).on "click", ".js-note-delete", @removeNote
...@@ -91,9 +93,11 @@ class @Notes ...@@ -91,9 +93,11 @@ class @Notes
clearInterval(Notes.interval) clearInterval(Notes.interval)
Notes.interval = setInterval => Notes.interval = setInterval =>
@refresh() @refresh()
, 15000 , @pollingInterval
refresh: -> refresh: ->
return if @refreshing is true
refreshing = true
if not document.hidden and document.URL.indexOf(@noteable_url) is 0 if not document.hidden and document.URL.indexOf(@noteable_url) is 0
@getContent() @getContent()
...@@ -105,12 +109,31 @@ class @Notes ...@@ -105,12 +109,31 @@ class @Notes
success: (data) => success: (data) =>
notes = data.notes notes = data.notes
@last_fetched_at = data.last_fetched_at @last_fetched_at = data.last_fetched_at
@setPollingInterval(data.notes.length)
$.each notes, (i, note) => $.each notes, (i, note) =>
if note.discussion_with_diff_html? if note.discussion_with_diff_html?
@renderDiscussionNote(note) @renderDiscussionNote(note)
else else
@renderNote(note) @renderNote(note)
always: =>
@refreshing = false
###
Increase @pollingInterval up to 120 seconds on every function call,
if `shouldReset` has a truthy value, 'null' or 'undefined' the variable
will reset to @basePollingInterval.
Note: this function is used to gradually increase the polling interval
if there aren't new notes coming from the server
###
setPollingInterval: (shouldReset = true) ->
nthInterval = @basePollingInterval * Math.pow(2, @maxPollingSteps - 1)
if shouldReset
@pollingInterval = @basePollingInterval
else if @pollingInterval < nthInterval
@pollingInterval *= 2
@initRefresh()
### ###
Render note in main comments area. Render note in main comments area.
......
...@@ -62,3 +62,12 @@ class @Profile ...@@ -62,3 +62,12 @@ class @Profile
$modalCropImg.attr('src', event.target.result) $modalCropImg.attr('src', event.target.result)
fileData = reader.readAsDataURL(this.files[0]) fileData = reader.readAsDataURL(this.files[0])
$ ->
# Extract the SSH Key title from its comment
$(document).on 'focusout.ssh_key', '#key_key', ->
$title = $('#key_title')
comment = $(@).val().match(/^\S+ \S+ (.+)\n?$/)
if comment && comment.length > 1 && $title.val() == ''
$title.val(comment[1]).change()
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
@import "framework/calendar.scss"; @import "framework/calendar.scss";
@import "framework/callout.scss"; @import "framework/callout.scss";
@import "framework/common.scss"; @import "framework/common.scss";
@import "framework/dropdowns.scss";
@import "framework/files.scss"; @import "framework/files.scss";
@import "framework/filters.scss"; @import "framework/filters.scss";
@import "framework/flash.scss"; @import "framework/flash.scss";
......
...@@ -143,6 +143,19 @@ ...@@ -143,6 +143,19 @@
} }
} }
.btn-transparent {
color: $btn-transparent-color;
background-color: transparent;
border: 0;
&:hover,
&:active,
&:focus {
background-color: transparent;
box-shadow: none;
}
}
.btn-block { .btn-block {
width: 100%; width: 100%;
margin: 0; margin: 0;
......
...@@ -60,25 +60,6 @@ hr { ...@@ -60,25 +60,6 @@ hr {
margin: $gl-padding 0; margin: $gl-padding 0;
} }
.dropdown-menu {
margin: 6px 0 0;
}
.dropdown-menu > li > a {
text-shadow: none;
}
.dropdown-menu-align-right {
left: auto;
right: 0px;
}
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
background: $gl-primary;
color: #FFF;
}
.str-truncated { .str-truncated {
@include str-truncated; @include str-truncated;
} }
......
.caret {
display: inline-block;
width: 0;
height: 0;
margin-left: 2px;
vertical-align: middle;
border-top: $caret-width-base dashed $dropdown-caret-color;
border-right: $caret-width-base solid transparent;
border-left: $caret-width-base solid transparent;
}
.dropdown {
position: relative;
}
.open {
.dropdown-menu {
display: block;
}
}
.dropdown-menu {
display: none;
position: absolute;
top: 100%;
left: 0;
z-index: 9999;
width: 240px;
margin-top: 2px;
margin-bottom: 0;
padding: 10px 10px;
font-size: 14px;
font-weight: normal;
background-color: $dropdown-bg;
border: 1px solid $dropdown-border-color;
border-radius: $border-radius-base;
box-shadow: 0 2px 4px $dropdown-shadow-color;
li {
text-align: left;
list-style: none;
}
.divider {
width: 100%;
height: 1px;
margin-top: 8px;
margin-bottom: 8px;
background-color: $dropdown-divider-color;
}
a {
display: block;
position: relative;
padding-left: 10px;
padding-right: 10px;
color: $dropdown-link-color;
line-height: 34px;
text-overflow: ellipsis;
border-radius: 2px;
white-space: nowrap;
overflow: hidden;
&:hover {
background-color: $dropdown-link-hover-bg;
text-decoration: none;
}
}
}
.dropdown-menu-align-right {
left: auto;
right: 0;
}
.dropdown-menu-selectable {
a {
padding-left: 25px;
&.is-active {
&::before {
content: "\f00c";
position: absolute;
left: 4px;
top: 8px;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
}
}
}
.dropdown-header {
padding-left: 10px;
padding-right: 10px;
color: $dropdown-header-color;
font-size: 13px;
line-height: 22px;
}
...@@ -5,11 +5,20 @@ ...@@ -5,11 +5,20 @@
*/ */
.status-box { .status-box {
/* Extra small devices (phones, less than 768px) */
/* No media query since this is the default in Bootstrap */
padding: 5px 11px;
margin-top: 4px;
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) {
padding: 0 $gl-btn-padding;
margin-top: 5px;
}
@include border-radius(3px); @include border-radius(3px);
display: block; display: block;
float: left; float: left;
padding: 0 $gl-btn-padding;
margin-top: 5px;
margin-right: 10px; margin-right: 10px;
color: #FFF; color: #FFF;
font-size: $gl-font-size; font-size: $gl-font-size;
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
* *
*/ */
.well-list { .well-list {
position: relative;
margin: 0; margin: 0;
padding: 0; padding: 0;
list-style: none; list-style: none;
......
...@@ -27,7 +27,14 @@ ...@@ -27,7 +27,14 @@
} }
&.right-sidebar-expanded { &.right-sidebar-expanded {
padding-right: $gutter_width; /* Extra small devices (phones, less than 768px) */
/* No media query since this is the default in Bootstrap */
padding-right: 0;
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) {
padding-right: $gutter_width;
}
} }
} }
...@@ -199,7 +206,12 @@ ...@@ -199,7 +206,12 @@
padding-left: $sidebar_width; padding-left: $sidebar_width;
&.right-sidebar-collapsed { &.right-sidebar-collapsed {
padding-right: $sidebar_collapsed_width; /* Extra small devices (phones, less than 768px) */
padding-right: 0;
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) {
padding-right: $sidebar_collapsed_width;
}
} }
.sidebar-wrapper { .sidebar-wrapper {
...@@ -225,7 +237,12 @@ ...@@ -225,7 +237,12 @@
padding-left: $sidebar_collapsed_width; padding-left: $sidebar_collapsed_width;
&.right-sidebar-collapsed { &.right-sidebar-collapsed {
padding-right: $sidebar_collapsed_width; /* Extra small devices (phones, less than 768px) */
padding-right: 0;
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) {
padding-right: $sidebar_collapsed_width;
}
} }
.sidebar-wrapper { .sidebar-wrapper {
...@@ -292,7 +309,13 @@ ...@@ -292,7 +309,13 @@
} }
.page-sidebar-collapsed { .page-sidebar-collapsed {
/* Extra small devices (phones, less than 768px) */
@include collapsed-sidebar; @include collapsed-sidebar;
padding-right: 0;
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) {
@include collapsed-sidebar;
}
} }
.page-sidebar-expanded { .page-sidebar-expanded {
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
// Components // Components
@import "bootstrap/component-animations"; @import "bootstrap/component-animations";
@import "bootstrap/dropdowns"; // @import "bootstrap/dropdowns";
@import "bootstrap/button-groups"; @import "bootstrap/button-groups";
@import "bootstrap/input-groups"; @import "bootstrap/input-groups";
@import "bootstrap/navs"; @import "bootstrap/navs";
......
...@@ -36,6 +36,11 @@ $list-title-color: #333333; ...@@ -36,6 +36,11 @@ $list-title-color: #333333;
$list-text-color: #555555; $list-text-color: #555555;
$profile-settings-link-color: $md-link-color; $profile-settings-link-color: $md-link-color;
$btn-transparent-color: #8F8F8F;
$ssh-key-icon-color: #8F8F8F;
$ssh-key-icon-size: 18px;
/* /*
* Color schema * Color schema
*/ */
...@@ -93,6 +98,9 @@ $border-red-light: #E52C5A; ...@@ -93,6 +98,9 @@ $border-red-light: #E52C5A;
$border-red-normal: #D22852; $border-red-normal: #D22852;
$border-red-dark: #CA264F; $border-red-dark: #CA264F;
$help-well-bg: #FAFAFA;
$help-well-border: #E5E5E5;
/* header */ /* header */
$light-grey-header: #faf9f9; $light-grey-header: #faf9f9;
...@@ -118,3 +126,15 @@ $deleted: #f77; ...@@ -118,3 +126,15 @@ $deleted: #f77;
*/ */
$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace; $monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
$regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif; $regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif;
/*
* Dropdowns
*/
$dropdown-bg: #fff;
$dropdown-link-color: #555;
$dropdown-link-hover-bg: rgba(#000, .04);
$dropdown-border-color: rgba(#000, .1);
$dropdown-shadow-color: rgba(#000, .1);
$dropdown-divider-color: rgba(#000, .1);
$dropdown-header-color: #959494;
$dropdown-caret-color: #54565B;
...@@ -18,7 +18,8 @@ ...@@ -18,7 +18,8 @@
} }
.issue-meta { .issue-meta {
margin-left: 65px display: inline-block;
line-height: 20px;
} }
} }
......
...@@ -151,7 +151,6 @@ ...@@ -151,7 +151,6 @@
} }
} }
.right-sidebar { .right-sidebar {
position: fixed; position: fixed;
top: 58px; top: 58px;
...@@ -184,6 +183,13 @@ ...@@ -184,6 +183,13 @@
} }
&.right-sidebar-collapsed { &.right-sidebar-collapsed {
/* Extra small devices (phones, less than 768px) */
display: none;
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) {
display: block
}
width: $sidebar_collapsed_width; width: $sidebar_collapsed_width;
padding-top: 0; padding-top: 0;
...@@ -247,6 +253,10 @@ ...@@ -247,6 +253,10 @@
} }
} }
.btn-default.gutter-toggle {
margin-top: 4px;
}
.detail-page-description { .detail-page-description {
small { small {
color: $gray-darkest; color: $gray-darkest;
......
...@@ -99,18 +99,17 @@ form.edit-issue { ...@@ -99,18 +99,17 @@ form.edit-issue {
.btn { .btn {
width: 100%; width: 100%;
margin-top: -1px;
&:first-child:not(:last-child) { &:first-child:not(:last-child) {
border-radius: 4px 4px 0 0;
} }
&:not(:first-child):not(:last-child) { &:not(:first-child):not(:last-child) {
border-radius: 0; margin-top: 10px;
} }
&:last-child:not(:first-child) { &:last-child:not(:first-child) {
border-radius: 0 0 4px 4px; margin-top: 10px;
} }
} }
} }
...@@ -134,3 +133,11 @@ form.edit-issue { ...@@ -134,3 +133,11 @@ form.edit-issue {
color: $secondary-text; color: $secondary-text;
margin-left: 52px; margin-left: 52px;
} }
.editor-details {
display: block;
@media (min-width: $screen-sm-min) {
display: inline-block;
}
}
\ No newline at end of file
...@@ -5,12 +5,6 @@ ...@@ -5,12 +5,6 @@
} }
} }
.profile-settings-sidebar {
a {
color: $profile-settings-link-color;
}
}
.avatar-image { .avatar-image {
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
float: left; float: left;
...@@ -24,6 +18,11 @@ ...@@ -24,6 +18,11 @@
display: inline-block; display: inline-block;
} }
.account-btn-link,
.profile-settings-sidebar a {
color: $profile-settings-link-color;
}
.oauth-buttons { .oauth-buttons {
.btn-group { .btn-group {
margin-right: 10px; margin-right: 10px;
...@@ -55,6 +54,18 @@ ...@@ -55,6 +54,18 @@
} }
} }
.account-well {
padding: 10px 10px;
background-color: $help-well-bg;
border: 1px solid $help-well-border;
border-radius: $border-radius-base;
ul {
padding-left: 20px;
margin-bottom: 0;
}
}
.calendar-hint { .calendar-hint {
margin-top: -12px; margin-top: -12px;
float: right; float: right;
...@@ -134,3 +145,33 @@ ...@@ -134,3 +145,33 @@
width: auto; width: auto;
} }
} }
.key-list-item {
.key-list-item-info {
@media (min-width: $screen-sm-min) {
float: left;
}
}
.description {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
}
.key-icon {
color: $ssh-key-icon-color;
font-size: $ssh-key-icon-size;
line-height: 42px;
}
.key-created-at {
line-height: 42px;
}
.profile-settings-content {
a {
color: $profile-settings-link-color;
}
}
...@@ -49,10 +49,6 @@ ...@@ -49,10 +49,6 @@
} }
} }
.project-home-dropdown {
margin: 13px 0px 0;
}
.notifications-btn { .notifications-btn {
margin-top: -28px; margin-top: -28px;
...@@ -185,29 +181,6 @@ ...@@ -185,29 +181,6 @@
} }
} }
.dropdown-menu {
@include box-shadow(rgba(76, 86, 103, 0.247059) 0px 0px 1px 0px, rgba(31, 37, 50, 0.317647) 0px 2px 18px 0px);
@include border-radius ($border-radius-default);
border: none;
padding: 10px 0;
font-size: 14px;
font-weight: 100;
li a {
color: #5f697a;
line-height: 30px;
&:hover {
background-color: #3084bb !important;
}
}
i {
margin-right: 8px;
}
}
.project-visibility-level-holder { .project-visibility-level-holder {
.radio { .radio {
margin-bottom: 10px; margin-bottom: 10px;
......
...@@ -31,7 +31,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -31,7 +31,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
end end
def starred def starred
@projects = current_user.starred_projects @projects = current_user.starred_projects.sorted_by_activity
@projects = @projects.includes(:namespace, :forked_from_project, :tags) @projects = @projects.includes(:namespace, :forked_from_project, :tags)
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
......
...@@ -23,6 +23,14 @@ class PasswordsController < Devise::PasswordsController ...@@ -23,6 +23,14 @@ class PasswordsController < Devise::PasswordsController
end end
end end
def update
super do |resource|
if resource.valid? && resource.require_password?
resource.update_attribute(:password_automatically_set, false)
end
end
end
protected protected
def resource_from_email def resource_from_email
......
...@@ -3,23 +3,21 @@ class Profiles::KeysController < Profiles::ApplicationController ...@@ -3,23 +3,21 @@ class Profiles::KeysController < Profiles::ApplicationController
def index def index
@keys = current_user.keys @keys = current_user.keys
@key = Key.new
end end
def show def show
@key = current_user.keys.find(params[:id]) @key = current_user.keys.find(params[:id])
end end
def new
@key = current_user.keys.new
end
def create def create
@key = current_user.keys.new(key_params) @key = current_user.keys.new(key_params)
if @key.save if @key.save
redirect_to profile_key_path(@key) redirect_to profile_key_path(@key)
else else
render 'new' @keys = current_user.keys.select(&:persisted?)
render :index
end end
end end
......
class Projects::BadgesController < Projects::ApplicationController class Projects::BadgesController < Projects::ApplicationController
before_action :set_no_cache
def build def build
respond_to do |format| respond_to do |format|
format.html { render_404 } format.html { render_404 }
...@@ -8,4 +10,15 @@ class Projects::BadgesController < Projects::ApplicationController ...@@ -8,4 +10,15 @@ class Projects::BadgesController < Projects::ApplicationController
end end
end end
end end
private
def set_no_cache
expires_now
# Add some deprecated headers for older agents
#
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = 'Fri, 01 Jan 1990 00:00:00 GMT'
end
end end
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
# Not to be confused with CommitsController, plural. # Not to be confused with CommitsController, plural.
class Projects::CommitController < Projects::ApplicationController class Projects::CommitController < Projects::ApplicationController
include CreatesCommit include CreatesCommit
include DiffHelper
# Authorize # Authorize
before_action :require_non_empty_project before_action :require_non_empty_project
...@@ -100,12 +101,10 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -100,12 +101,10 @@ class Projects::CommitController < Projects::ApplicationController
def define_show_vars def define_show_vars
return git_not_found! unless commit return git_not_found! unless commit
if params[:w].to_i == 1 opts = diff_options
@diffs = commit.diffs({ ignore_whitespace_change: true }) opts[:ignore_whitespace_change] = true if params[:format] == 'diff'
else
@diffs = commit.diffs
end
@diffs = commit.diffs(opts)
@diff_refs = [commit.parent || commit, commit] @diff_refs = [commit.parent || commit, commit]
@notes_count = commit.notes.count @notes_count = commit.notes.count
......
require 'addressable/uri' require 'addressable/uri'
class Projects::CompareController < Projects::ApplicationController class Projects::CompareController < Projects::ApplicationController
include DiffHelper
# Authorize # Authorize
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :authorize_download_code! before_action :authorize_download_code!
...@@ -11,16 +13,14 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -11,16 +13,14 @@ class Projects::CompareController < Projects::ApplicationController
end end
def show def show
diff_options = { ignore_whitespace_change: true } if params[:w] == '1' compare = CompareService.new.
compare_result = CompareService.new.
execute(@project, @head_ref, @project, @base_ref, diff_options) execute(@project, @head_ref, @project, @base_ref, diff_options)
if compare_result if compare
@commits = Commit.decorate(compare_result.commits, @project) @commits = Commit.decorate(compare.commits, @project)
@diffs = compare_result.diffs
@commit = @project.commit(@head_ref) @commit = @project.commit(@head_ref)
@base_commit = @project.merge_base_commit(@base_ref, @head_ref) @base_commit = @project.merge_base_commit(@base_ref, @head_ref)
@diffs = compare.diffs(diff_options)
@diff_refs = [@base_commit, @commit] @diff_refs = [@base_commit, @commit]
@line_notes = [] @line_notes = []
end end
......
class Projects::MergeRequestsController < Projects::ApplicationController class Projects::MergeRequestsController < Projects::ApplicationController
include DiffHelper
before_action :module_enabled before_action :module_enabled
before_action :merge_request, only: [ before_action :merge_request, only: [
:edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check, :edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check,
...@@ -111,7 +113,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -111,7 +113,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@commits = @merge_request.compare_commits.reverse @commits = @merge_request.compare_commits.reverse
@commit = @merge_request.last_commit @commit = @merge_request.last_commit
@base_commit = @merge_request.diff_base_commit @base_commit = @merge_request.diff_base_commit
@diffs = @merge_request.compare_diffs @diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare
@ci_commit = @merge_request.ci_commit @ci_commit = @merge_request.ci_commit
@statuses = @ci_commit.statuses if @ci_commit @statuses = @ci_commit.statuses if @ci_commit
......
...@@ -4,6 +4,7 @@ class SessionsController < Devise::SessionsController ...@@ -4,6 +4,7 @@ class SessionsController < Devise::SessionsController
skip_before_action :check_2fa_requirement, only: [:destroy] skip_before_action :check_2fa_requirement, only: [:destroy]
prepend_before_action :check_initial_setup, only: [:new]
prepend_before_action :authenticate_with_two_factor, only: [:create] prepend_before_action :authenticate_with_two_factor, only: [:create]
prepend_before_action :store_redirect_path, only: [:new] prepend_before_action :store_redirect_path, only: [:new]
before_action :gitlab_geo_login, only: [:new] before_action :gitlab_geo_login, only: [:new]
...@@ -34,6 +35,22 @@ class SessionsController < Devise::SessionsController ...@@ -34,6 +35,22 @@ class SessionsController < Devise::SessionsController
private private
# Handle an "initial setup" state, where there's only one user, it's an admin,
# and they require a password change.
def check_initial_setup
return unless User.count == 1
user = User.admins.last
return unless user && user.require_password?
token = user.generate_reset_token
user.save
redirect_to edit_user_password_path(reset_password_token: token),
notice: "Please create a password for your new account."
end
def user_params def user_params
params.require(:user).permit(:login, :password, :remember_me, :otp_attempt) params.require(:user).permit(:login, :password, :remember_me, :otp_attempt)
end end
......
...@@ -67,12 +67,8 @@ class UsersController < ApplicationController ...@@ -67,12 +67,8 @@ class UsersController < ApplicationController
end end
def calendar_activities def calendar_activities
@calendar_date = Date.parse(params[:date]) rescue nil @calendar_date = Date.parse(params[:date]) rescue Date.today
@events = [] @events = contributions_calendar.events_by_date(@calendar_date)
if @calendar_date
@events = contributions_calendar.events_by_date(@calendar_date)
end
render 'calendar_activities', layout: false render 'calendar_activities', layout: false
end end
......
...@@ -12,40 +12,20 @@ module DiffHelper ...@@ -12,40 +12,20 @@ module DiffHelper
params[:view] == 'parallel' ? 'parallel' : 'inline' params[:view] == 'parallel' ? 'parallel' : 'inline'
end end
def allowed_diff_size def diff_hard_limit_enabled?
if diff_hard_limit_enabled? params[:force_show_diff].present?
Commit::DIFF_HARD_LIMIT_FILES
else
Commit::DIFF_SAFE_FILES
end
end end
def allowed_diff_lines def diff_options
options = { ignore_whitespace_change: params[:w] == '1' }
if diff_hard_limit_enabled? if diff_hard_limit_enabled?
Commit::DIFF_HARD_LIMIT_LINES options.merge!(Commit.max_diff_options)
else
Commit::DIFF_SAFE_LINES
end end
options
end end
def safe_diff_files(diffs, diff_refs) def safe_diff_files(diffs, diff_refs)
lines = 0 diffs.decorate! { |diff| Gitlab::Diff::File.new(diff, diff_refs) }
safe_files = []
diffs.first(allowed_diff_size).each do |diff|
lines += diff.diff.lines.count
break if lines > allowed_diff_lines
safe_files << Gitlab::Diff::File.new(diff, diff_refs)
end
safe_files
end
def diff_hard_limit_enabled?
# Enabling hard limit allows user to see more diff information
if params[:force_show_diff].present?
true
else
false
end
end end
def generate_line_code(file_path, line) def generate_line_code(file_path, line)
......
...@@ -50,6 +50,8 @@ module GitlabMarkdownHelper ...@@ -50,6 +50,8 @@ module GitlabMarkdownHelper
context[:project] ||= @project context[:project] ||= @project
text = Banzai.pre_process(text, context)
html = Banzai.render(text, context) html = Banzai.render(text, context)
context.merge!( context.merge!(
......
...@@ -50,19 +50,25 @@ module LabelsHelper ...@@ -50,19 +50,25 @@ module LabelsHelper
@project.labels.pluck(:title) @project.labels.pluck(:title)
end end
def render_colored_label(label) def render_colored_label(label, label_suffix = '')
label_color = label.color || Label::DEFAULT_COLOR label_color = label.color || Label::DEFAULT_COLOR
text_color = text_color_for_bg(label_color) text_color = text_color_for_bg(label_color)
# Intentionally not using content_tag here so that this method can be called # Intentionally not using content_tag here so that this method can be called
# by LabelReferenceFilter # by LabelReferenceFilter
span = %(<span class="label color-label") + span = %(<span class="label color-label") +
%( style="background-color: #{label_color}; color: #{text_color}">) + %(style="background-color: #{label_color}; color: #{text_color}">) +
escape_once(label.name) + '</span>' %(#{escape_once(label.name)}#{label_suffix}</span>)
span.html_safe span.html_safe
end end
def render_colored_cross_project_label(label)
label_suffix = label.project.name_with_namespace
label_suffix = " <i>in #{escape_once(label_suffix)}</i>"
render_colored_label(label, label_suffix)
end
def suggested_colors def suggested_colors
[ [
'#0033CC', '#0033CC',
...@@ -119,5 +125,6 @@ module LabelsHelper ...@@ -119,5 +125,6 @@ module LabelsHelper
end end
# Required for Banzai::Filter::LabelReferenceFilter # Required for Banzai::Filter::LabelReferenceFilter
module_function :render_colored_label, :text_color_for_bg, :escape_once module_function :render_colored_label, :render_colored_cross_project_label,
:text_color_for_bg, :escape_once
end end
...@@ -38,12 +38,16 @@ module ProjectsHelper ...@@ -38,12 +38,16 @@ module ProjectsHelper
author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar] author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar]
# Build name span tag # Build name span tag
author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name] if opts[:by_username]
author_html << content_tag(:span, sanitize("@#{author.username}"), class: opts[:author_class]) if opts[:name]
else
author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name]
end
author_html = author_html.html_safe author_html = author_html.html_safe
if opts[:name] if opts[:name]
link_to(author_html, user_path(author), class: "author_link").html_safe link_to(author_html, user_path(author), class: "author_link #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe
else else
title = opts[:title].sub(":name", sanitize(author.name)) title = opts[:title].sub(":name", sanitize(author.name))
link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe
......
...@@ -202,6 +202,7 @@ class Ability ...@@ -202,6 +202,7 @@ class Ability
def project_dev_rules def project_dev_rules
@project_dev_rules ||= project_report_rules + [ @project_dev_rules ||= project_report_rules + [
:admin_merge_request, :admin_merge_request,
:update_merge_request,
:create_commit_status, :create_commit_status,
:update_commit_status, :update_commit_status,
:create_build, :create_build,
...@@ -227,7 +228,6 @@ class Ability ...@@ -227,7 +228,6 @@ class Ability
:read_pages, :read_pages,
:push_code_to_protected_branches, :push_code_to_protected_branches,
:update_project_snippet, :update_project_snippet,
:update_merge_request,
:update_pages, :update_pages,
:admin_milestone, :admin_milestone,
:admin_project_snippet, :admin_project_snippet,
......
...@@ -12,12 +12,7 @@ class Commit ...@@ -12,12 +12,7 @@ class Commit
attr_accessor :project attr_accessor :project
# Safe amount of changes (files and lines) in one commit to render DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines]
# Used to prevent 500 error on huge commits by suppressing diff
#
# User can force display of diff above this size
DIFF_SAFE_FILES = 100 unless defined?(DIFF_SAFE_FILES)
DIFF_SAFE_LINES = 5000 unless defined?(DIFF_SAFE_LINES)
# Commits above this size will not be rendered in HTML # Commits above this size will not be rendered in HTML
DIFF_HARD_LIMIT_FILES = 1000 unless defined?(DIFF_HARD_LIMIT_FILES) DIFF_HARD_LIMIT_FILES = 1000 unless defined?(DIFF_HARD_LIMIT_FILES)
...@@ -36,13 +31,20 @@ class Commit ...@@ -36,13 +31,20 @@ class Commit
# Calculate number of lines to render for diffs # Calculate number of lines to render for diffs
def diff_line_count(diffs) def diff_line_count(diffs)
diffs.reduce(0) { |sum, d| sum + d.diff.lines.count } diffs.reduce(0) { |sum, d| sum + Gitlab::Git::Util.count_lines(d.diff) }
end end
# Truncate sha to 8 characters # Truncate sha to 8 characters
def truncate_sha(sha) def truncate_sha(sha)
sha[0..7] sha[0..7]
end end
def max_diff_options
{
max_files: DIFF_HARD_LIMIT_FILES,
max_lines: DIFF_HARD_LIMIT_LINES,
}
end
end end
attr_accessor :raw attr_accessor :raw
......
class DiffLine
attr_accessor :type, :content, :num, :code
end
...@@ -48,10 +48,15 @@ class Label < ActiveRecord::Base ...@@ -48,10 +48,15 @@ class Label < ActiveRecord::Base
'~' '~'
end end
##
# Pattern used to extract label references from text # Pattern used to extract label references from text
#
# This pattern supports cross-project references.
#
def self.reference_pattern def self.reference_pattern
%r{ %r{
#{reference_prefix} (#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}
(?: (?:
(?<label_id>\d+) | # Integer-based label ID, or (?<label_id>\d+) | # Integer-based label ID, or
(?<label_name> (?<label_name>
...@@ -62,24 +67,31 @@ class Label < ActiveRecord::Base ...@@ -62,24 +67,31 @@ class Label < ActiveRecord::Base
}x }x
end end
def self.link_reference_pattern
nil
end
##
# Returns the String necessary to reference this Label in Markdown # Returns the String necessary to reference this Label in Markdown
# #
# format - Symbol format to use (default: :id, optional: :name) # format - Symbol format to use (default: :id, optional: :name)
# #
# Note that its argument differs from other objects implementing Referable. If
# a non-Symbol argument is given (such as a Project), it will default to :id.
#
# Examples: # Examples:
# #
# Label.first.to_reference # => "~1" # Label.first.to_reference # => "~1"
# Label.first.to_reference(:name) # => "~\"bug\"" # Label.first.to_reference(format: :name) # => "~\"bug\""
# Label.first.to_reference(project) # => "gitlab-org/gitlab-ce~1"
# #
# Returns a String # Returns a String
def to_reference(format = :id) #
if format == :name && !name.include?('"') def to_reference(from_project = nil, format: :id)
%(#{self.class.reference_prefix}"#{name}") format_reference = label_format_reference(format)
reference = "#{self.class.reference_prefix}#{format_reference}"
if cross_project_reference?(from_project)
project.to_reference + reference
else else
"#{self.class.reference_prefix}#{id}" reference
end end
end end
...@@ -98,4 +110,16 @@ class Label < ActiveRecord::Base ...@@ -98,4 +110,16 @@ class Label < ActiveRecord::Base
def template? def template?
template template
end end
private
def label_format_reference(format = :id)
raise StandardError, 'Unknown format' unless [:id, :name].include?(format)
if format == :name && !name.include?('"')
%("#{name}")
else
id
end
end
end end
...@@ -51,7 +51,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -51,7 +51,7 @@ class MergeRequest < ActiveRecord::Base
after_create :create_merge_request_diff after_create :create_merge_request_diff
after_update :update_merge_request_diff after_update :update_merge_request_diff
delegate :commits, :diffs, :diffs_no_whitespace, to: :merge_request_diff, prefix: nil delegate :commits, :diffs, :real_size, to: :merge_request_diff, prefix: nil
# When this attribute is true some MR validation is ignored # When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests # It allows us to close or modify broken merge requests
...@@ -59,8 +59,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -59,8 +59,7 @@ class MergeRequest < ActiveRecord::Base
# Temporary fields to store compare vars # Temporary fields to store compare vars
# when creating new merge request # when creating new merge request
attr_accessor :can_be_created, :compare_failed, attr_accessor :can_be_created, :compare_commits, :compare
:compare_commits, :compare_diffs
state_machine :state, initial: :opened do state_machine :state, initial: :opened do
event :close do event :close do
...@@ -186,6 +185,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -186,6 +185,10 @@ 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_size
merge_request_diff.size
end
def diff_base_commit def diff_base_commit
if merge_request_diff if merge_request_diff
merge_request_diff.base_commit merge_request_diff.base_commit
...@@ -543,6 +546,16 @@ class MergeRequest < ActiveRecord::Base ...@@ -543,6 +546,16 @@ class MergeRequest < ActiveRecord::Base
end end
end end
def state_icon_name
if merged?
"check"
elsif closed?
"times"
else
"circle-o"
end
end
def target_sha def target_sha
@target_sha ||= target_project.repository.commit(target_branch).sha @target_sha ||= target_project.repository.commit(target_branch).sha
end end
...@@ -600,6 +613,29 @@ class MergeRequest < ActiveRecord::Base ...@@ -600,6 +613,29 @@ class MergeRequest < ActiveRecord::Base
File.exist?(rebase_dir_path) File.exist?(rebase_dir_path)
end end
def diverged_commits_count
cache = Rails.cache.read(:"merge_request_#{id}_diverged_commits")
if cache.blank? || cache[:source_sha] != source_sha || cache[:target_sha] != target_sha
cache = {
source_sha: source_sha,
target_sha: target_sha,
diverged_commits_count: compute_diverged_commits_count
}
Rails.cache.write(:"merge_request_#{id}_diverged_commits", cache)
end
cache[:diverged_commits_count]
end
def compute_diverged_commits_count
Gitlab::Git::Commit.between(target_project.repository.raw_repository, source_sha, target_sha).size
end
def diverged_from_target_branch?
diverged_commits_count > 0
end
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
......
...@@ -19,14 +19,15 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -19,14 +19,15 @@ class MergeRequestDiff < ActiveRecord::Base
# Prevent store of diff if commits amount more then 500 # Prevent store of diff if commits amount more then 500
COMMITS_SAFE_SIZE = 500 COMMITS_SAFE_SIZE = 500
attr_reader :commits, :diffs, :diffs_no_whitespace
belongs_to :merge_request belongs_to :merge_request
delegate :target_branch, :source_branch, to: :merge_request, prefix: nil delegate :target_branch, :source_branch, to: :merge_request, prefix: nil
state_machine :state, initial: :empty do state_machine :state, initial: :empty do
state :collected state :collected
state :overflow
# Deprecated states: these are no longer used but these values may still occur
# in the database.
state :timeout state :timeout
state :overflow_commits_safe_size state :overflow_commits_safe_size
state :overflow_diff_files_limit state :overflow_diff_files_limit
...@@ -43,19 +44,23 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -43,19 +44,23 @@ class MergeRequestDiff < ActiveRecord::Base
reload_diffs reload_diffs
end end
def diffs def size
@diffs ||= (load_diffs(st_diffs) || []) real_size.presence || diffs.size
end end
def diffs_no_whitespace def diffs(options={})
compare_result = Gitlab::CompareResult.new( if options[:ignore_whitespace_change]
Gitlab::Git::Compare.new( @diffs_no_whitespace ||= begin
self.repository.raw_repository, compare = Gitlab::Git::Compare.new(
self.target_branch, self.repository.raw_repository,
self.source_sha, self.target_branch,
), { ignore_whitespace_change: true } self.source_sha,
) )
@diffs_no_whitespace ||= load_diffs(dump_commits(compare_result.diffs)) compare.diffs(options)
end
else
@diffs ||= load_diffs(st_diffs, options)
end
end end
def commits def commits
...@@ -94,16 +99,18 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -94,16 +99,18 @@ class MergeRequestDiff < ActiveRecord::Base
end end
end end
def load_diffs(raw) def load_diffs(raw, options)
if raw.respond_to?(:map) if raw.respond_to?(:each)
raw.map { |hash| Gitlab::Git::Diff.new(hash) } Gitlab::Git::DiffCollection.new(raw, options)
else
Gitlab::Git::DiffCollection.new([])
end end
end end
# Collect array of Git::Commit objects # Collect array of Git::Commit objects
# between target and source branches # between target and source branches
def unmerged_commits def unmerged_commits
commits = compare_result.commits commits = compare.commits
if commits.present? if commits.present?
commits = Commit.decorate(commits, merge_request.source_project). commits = Commit.decorate(commits, merge_request.source_project).
...@@ -133,27 +140,21 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -133,27 +140,21 @@ class MergeRequestDiff < ActiveRecord::Base
if commits.size.zero? if commits.size.zero?
self.state = :empty self.state = :empty
elsif commits.size > COMMITS_SAFE_SIZE
self.state = :overflow_commits_safe_size
else else
new_diffs = unmerged_diffs diff_collection = unmerged_diffs
end
if new_diffs.any? if diff_collection.overflow?
if new_diffs.size > Commit::DIFF_HARD_LIMIT_FILES # Set our state to 'overflow' to make the #empty? and #collected?
self.state = :overflow_diff_files_limit # methods (generated by StateMachine) return false.
new_diffs = new_diffs.first(Commit::DIFF_HARD_LIMIT_LINES) self.state = :overflow
end end
if new_diffs.sum { |diff| diff.diff.lines.count } > Commit::DIFF_HARD_LIMIT_LINES self.real_size = diff_collection.real_size
self.state = :overflow_diff_lines_limit
new_diffs = new_diffs.first(Commit::DIFF_HARD_LIMIT_LINES)
end
end
if new_diffs.present? if diff_collection.any?
new_diffs = dump_commits(new_diffs) new_diffs = dump_diffs(diff_collection)
self.state = :collected self.state = :collected
end
end end
self.st_diffs = new_diffs self.st_diffs = new_diffs
...@@ -166,10 +167,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -166,10 +167,7 @@ class MergeRequestDiff < ActiveRecord::Base
# Collect array of Git::Diff objects # Collect array of Git::Diff objects
# between target and source branches # between target and source branches
def unmerged_diffs def unmerged_diffs
compare_result.diffs || [] compare.diffs(Commit.max_diff_options)
rescue Gitlab::Git::Diff::TimeoutError
self.state = :timeout
[]
end end
def repository def repository
...@@ -181,18 +179,16 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -181,18 +179,16 @@ class MergeRequestDiff < ActiveRecord::Base
source_commit.try(:sha) source_commit.try(:sha)
end end
def compare_result def compare
@compare_result ||= @compare ||=
begin begin
# Update ref for merge request # Update ref for merge request
merge_request.fetch_ref merge_request.fetch_ref
Gitlab::CompareResult.new( Gitlab::Git::Compare.new(
Gitlab::Git::Compare.new( self.repository.raw_repository,
self.repository.raw_repository, self.target_branch,
self.target_branch, self.source_sha
self.source_sha
)
) )
end end
end end
......
...@@ -40,6 +40,7 @@ class Note < ActiveRecord::Base ...@@ -40,6 +40,7 @@ class Note < ActiveRecord::Base
has_many :todos, dependent: :destroy has_many :todos, dependent: :destroy
delegate :gfm_reference, :local_reference, to: :noteable
delegate :name, to: :project, prefix: true delegate :name, to: :project, prefix: true
delegate :name, :email, to: :author, prefix: true delegate :name, :email, to: :author, prefix: true
...@@ -89,7 +90,7 @@ class Note < ActiveRecord::Base ...@@ -89,7 +90,7 @@ class Note < ActiveRecord::Base
next if discussion_ids.include?(note.discussion_id) next if discussion_ids.include?(note.discussion_id)
# don't group notes for the main target # don't group notes for the main target
if !note.for_diff_line? && note.noteable_type == "MergeRequest" if !note.for_diff_line? && note.for_merge_request?
discussions << [note] discussions << [note]
else else
discussions << notes.select do |other_note| discussions << notes.select do |other_note|
...@@ -133,9 +134,11 @@ class Note < ActiveRecord::Base ...@@ -133,9 +134,11 @@ class Note < ActiveRecord::Base
end end
def find_diff def find_diff
return nil unless noteable && noteable.diffs.present? return nil unless noteable
return @diff if defined?(@diff)
@diff ||= noteable.diffs.find do |d| # Don't use ||= because nil is a valid value for @diff
@diff = noteable.diffs(Commit.max_diff_options).find do |d|
Digest::SHA1.hexdigest(d.new_path) == diff_file_index if d.new_path Digest::SHA1.hexdigest(d.new_path) == diff_file_index if d.new_path
end end
end end
...@@ -167,20 +170,16 @@ class Note < ActiveRecord::Base ...@@ -167,20 +170,16 @@ class Note < ActiveRecord::Base
def active? def active?
return true unless self.diff return true unless self.diff
return false unless noteable return false unless noteable
return @active if defined?(@active)
noteable.diffs.each do |mr_diff| diffs = noteable.diffs(Commit.max_diff_options)
next unless mr_diff.new_path == self.diff.new_path notable_diff = diffs.find { |d| d.new_path == self.diff.new_path }
lines = Gitlab::Diff::Parser.new.parse(mr_diff.diff.lines.to_a) return @active = false if notable_diff.nil?
lines.each do |line| parsed_lines = Gitlab::Diff::Parser.new.parse(notable_diff.diff.each_line)
if line.text == diff_line # We cannot use ||= because @active may be false
return true @active = parsed_lines.any? { |line_obj| line_obj.text == diff_line }
end
end
end
false
end end
def outdated? def outdated?
...@@ -265,7 +264,7 @@ class Note < ActiveRecord::Base ...@@ -265,7 +264,7 @@ class Note < ActiveRecord::Base
end end
def diff_lines def diff_lines
@diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.lines) @diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
end end
def highlighted_diff_lines def highlighted_diff_lines
...@@ -317,20 +316,6 @@ class Note < ActiveRecord::Base ...@@ -317,20 +316,6 @@ class Note < ActiveRecord::Base
nil nil
end end
# Mentionable override.
def gfm_reference(from_project = nil)
noteable.gfm_reference(from_project)
end
# Mentionable override.
def local_reference
noteable
end
def noteable_type_name
noteable_type.downcase if noteable_type.present?
end
# FIXME: Hack for polymorphic associations with STI # FIXME: Hack for polymorphic associations with STI
# For more information visit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations # For more information visit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations
def noteable_type=(noteable_type) def noteable_type=(noteable_type)
...@@ -350,10 +335,6 @@ class Note < ActiveRecord::Base ...@@ -350,10 +335,6 @@ class Note < ActiveRecord::Base
Event.reset_event_cache_for(self) Event.reset_event_cache_for(self)
end end
def system?
read_attribute(:system)
end
def downvote? def downvote?
is_award && note == "thumbsdown" is_award && note == "thumbsdown"
end end
...@@ -387,7 +368,7 @@ class Note < ActiveRecord::Base ...@@ -387,7 +368,7 @@ class Note < ActiveRecord::Base
private private
def awards_supported? def awards_supported?
(noteable.kind_of?(Issue) || noteable.is_a?(MergeRequest)) && !for_diff_line? (for_issue? || for_merge_request?) && !for_diff_line?
end end
def contains_emoji_only? def contains_emoji_only?
......
...@@ -108,7 +108,8 @@ class JiraService < IssueTrackerService ...@@ -108,7 +108,8 @@ class JiraService < IssueTrackerService
}, },
entity: { entity: {
name: noteable_name.humanize.downcase, name: noteable_name.humanize.downcase,
url: entity_url url: entity_url,
title: noteable.title
} }
} }
...@@ -196,10 +197,11 @@ class JiraService < IssueTrackerService ...@@ -196,10 +197,11 @@ class JiraService < IssueTrackerService
user_url = data[:user][:url] user_url = data[:user][:url]
entity_name = data[:entity][:name] entity_name = data[:entity][:name]
entity_url = data[:entity][:url] entity_url = data[:entity][:url]
entity_title = data[:entity][:title]
project_name = data[:project][:name] project_name = data[:project][:name]
message = { message = {
body: "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]." body: %Q{[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]:\n'#{entity_title}'}
} }
unless existing_comment?(issue_name, message[:body]) unless existing_comment?(issue_name, message[:body])
......
require 'securerandom' require 'securerandom'
# Compare 2 branches for one repo or between repositories # Compare 2 branches for one repo or between repositories
# and return Gitlab::CompareResult object that responds to commits and diffs # and return Gitlab::Git::Compare object that responds to commits and diffs
class CompareService class CompareService
def execute(source_project, source_branch, target_project, target_branch, diff_options = {}) def execute(source_project, source_branch, target_project, target_branch, diff_options = {})
source_commit = source_project.commit(source_branch) source_commit = source_project.commit(source_branch)
...@@ -20,12 +20,10 @@ class CompareService ...@@ -20,12 +20,10 @@ class CompareService
) )
end end
Gitlab::CompareResult.new( Gitlab::Git::Compare.new(
Gitlab::Git::Compare.new( target_project.repository.raw_repository,
target_project.repository.raw_repository, target_branch,
target_branch, source_sha,
source_sha,
), diff_options
) )
end end
end end
...@@ -5,9 +5,7 @@ module MergeRequests ...@@ -5,9 +5,7 @@ module MergeRequests
# Set MR attributes # Set MR attributes
merge_request.can_be_created = false merge_request.can_be_created = false
merge_request.compare_failed = false
merge_request.compare_commits = [] merge_request.compare_commits = []
merge_request.compare_diffs = []
merge_request.source_project = project unless merge_request.source_project merge_request.source_project = project unless merge_request.source_project
merge_request.target_project ||= (project.forked_from_project || project) merge_request.target_project ||= (project.forked_from_project || project)
merge_request.target_branch ||= merge_request.target_project.default_branch merge_request.target_branch ||= merge_request.target_project.default_branch
...@@ -21,35 +19,23 @@ module MergeRequests ...@@ -21,35 +19,23 @@ module MergeRequests
return build_failed(merge_request, message) return build_failed(merge_request, message)
end end
compare_result = CompareService.new.execute( compare = CompareService.new.execute(
merge_request.source_project, merge_request.source_project,
merge_request.source_branch, merge_request.source_branch,
merge_request.target_project, merge_request.target_project,
merge_request.target_branch, merge_request.target_branch,
) )
commits = compare_result.commits commits = compare.commits
# At this point we decide if merge request can be created # At this point we decide if merge request can be created
# If we have at least one commit to merge -> creation allowed # If we have at least one commit to merge -> creation allowed
if commits.present? if commits.present?
merge_request.compare_commits = Commit.decorate(commits, merge_request.source_project) merge_request.compare_commits = Commit.decorate(commits, merge_request.source_project)
merge_request.can_be_created = true merge_request.can_be_created = true
merge_request.compare_failed = false merge_request.compare = compare
# Try to collect diff for merge request.
diffs = compare_result.diffs
if diffs.present?
merge_request.compare_diffs = diffs
elsif diffs == false
merge_request.can_be_created = false
merge_request.compare_failed = true
end
else else
merge_request.can_be_created = false merge_request.can_be_created = false
merge_request.compare_failed = false
end end
commits = merge_request.compare_commits commits = merge_request.compare_commits
......
...@@ -66,7 +66,7 @@ class SystemNoteService ...@@ -66,7 +66,7 @@ class SystemNoteService
def self.change_label(noteable, project, author, added_labels, removed_labels) def self.change_label(noteable, project, author, added_labels, removed_labels)
labels_count = added_labels.count + removed_labels.count labels_count = added_labels.count + removed_labels.count
references = ->(label) { label.to_reference(:id) } references = ->(label) { label.to_reference(format: :id) }
added_labels = added_labels.map(&references).join(' ') added_labels = added_labels.map(&references).join(' ')
removed_labels = removed_labels.map(&references).join(' ') removed_labels = removed_labels.map(&references).join(' ')
......
- page_title "Keys", @user.name, "Users" - page_title "SSH Keys", @user.name, "Users"
= render 'admin/users/head' = render 'admin/users/head'
= render 'profiles/keys/key_table', admin: true = render 'profiles/keys/key_table', admin: true
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
.nav-controls .nav-controls
= form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short projects-list-filter', spellcheck: false, id: 'project-filter-form-field' = search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short projects-list-filter', spellcheck: false, id: 'project-filter-form-field', tabindex: "2"
= render 'explore/projects/dropdown' = render 'explore/projects/dropdown'
- if current_user.can_create_project? - if current_user.can_create_project?
= link_to new_project_path, class: 'btn btn-new' do = link_to new_project_path, class: 'btn btn-new' do
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
- else - else
= sort_title_recently_updated = sort_title_recently_updated
%b.caret %b.caret
%ul.dropdown-menu %ul.dropdown-menu.dropdown-menu-align-right
%li %li
= link_to explore_projects_filter_path(sort: sort_value_name) do = link_to explore_projects_filter_path(sort: sort_value_name) do
= sort_title_name = sort_title_name
......
.search .search
= form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f| = form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f|
= search_field_tag "search", nil, placeholder: 'Search', class: "search-input form-control", spellcheck: false = search_field_tag "search", nil, placeholder: 'Search', class: "search-input form-control", spellcheck: false, tabindex: "1"
= hidden_field_tag :group_id, @group.try(:id) = hidden_field_tag :group_id, @group.try(:id)
- if @project && @project.persisted? - if @project && @project.persisted?
= hidden_field_tag :project_id, @project.id = hidden_field_tag :project_id, @project.id
......
- page_title "Emails" - page_title "Emails"
- header_title page_title, profile_emails_path - header_title page_title, profile_emails_path
.alert.alert-help.prepend-top-default .row.prepend-top-default
%ul .col-lg-3.profile-settings-sidebar
%li %h4.prepend-top-0
Your = page_title
%b Primary Email %p
will be used for avatar detection and web based operations, such as edits and merges. Control emails linked to your account
%li .col-lg-9
Your %h4.prepend-top-0
%b Notification Email Add email address
will be used for account notifications. = form_for 'email', url: profile_emails_path do |f|
%li .form-group
Your = f.label :email, class: 'label-light'
%b Public Email = f.text_field :email, class: 'form-control'
will be displayed on your public profile. .prepend-top-default
%li = f.submit 'Add email address', class: 'btn btn-create'
All email addresses will be used to identify your commits. %hr
%h4.prepend-top-0
.panel.panel-default Linked emails (#{@emails.count + 1})
.panel-heading .account-well.append-bottom-default
Emails (#{@emails.count + 1}) %ul
%ul.well-list#emails-table %li
%li Your Primary Email will be used for avatar detection and web based operations, such as edits and merges.
%strong= @primary %li
%span.label.label-success Primary Email Your Notification Email will be used for account notifications.
- if @primary === current_user.public_email %li
%span.label.label-info Public Email Your Public Email will be displayed on your public profile.
- if @primary === current_user.notification_email %li
%span.label.label-info Notification Email All email addresses will be used to identify your commits.
- @emails.each do |email| %ul.well-list
%li %li
%strong= email.email = @primary
- if email.email === current_user.public_email %span.pull-right
%span.label.label-info Public Email %span.label.label-success Primary Email
- if email.email === current_user.notification_email - if @primary === current_user.public_email
%span.label.label-info Notification Email %span.label.label-info Public Email
%span.cgray - if @primary === current_user.notification_email
added #{time_ago_with_tooltip(email.created_at)} %span.label.label-info Notification Email
= link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-remove pull-right' - @emails.each do |email|
%li
%h4 Add email address = email.email
= form_for 'email', url: profile_emails_path, html: { class: 'form-horizontal' } do |f| %span.pull-right
.form-group - if email.email === current_user.public_email
= f.label :email, class: 'control-label' %span.label.label-info Public Email
.col-sm-10 - if email.email === current_user.notification_email
= f.text_field :email, class: 'form-control' %span.label.label-info Notification Email
.form-actions = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-remove pull-right'
= f.submit 'Add email address', class: 'btn btn-create'
%div %div
= form_for [:profile, @key], html: { class: 'form-horizontal js-requires-input' } do |f| = form_for [:profile, @key], html: { class: 'js-requires-input' } do |f|
- if @key.errors.any? - if @key.errors.any?
.alert.alert-danger .alert.alert-danger
%ul %ul
...@@ -7,13 +7,11 @@ ...@@ -7,13 +7,11 @@
%li= msg %li= msg
.form-group .form-group
= f.label :key, class: 'control-label' = f.label :key, class: 'label-light'
.col-sm-10 = f.text_area :key, class: "form-control", rows: 8, required: true
= f.text_area :key, class: "form-control", rows: 8, autofocus: true, required: true
.form-group .form-group
= f.label :title, class: 'control-label' = f.label :title, class: 'label-light'
.col-sm-10= f.text_field :title, class: "form-control", required: true = f.text_field :title, class: "form-control", required: true
.form-actions .prepend-top-default
= f.submit 'Add key', class: "btn btn-create" = f.submit 'Add key', class: "btn btn-create"
= link_to "Cancel", profile_keys_path, class: "btn btn-cancel"
<<<<<<< HEAD
%tr %tr
%td %td
= link_to path_to_key(key, is_admin) do = link_to path_to_key(key, is_admin) do
...@@ -10,3 +11,19 @@ ...@@ -10,3 +11,19 @@
%td %td
- unless key.is_a? LDAPKey - unless key.is_a? LDAPKey
= link_to 'Remove', path_to_key(key, is_admin), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-sm btn-remove delete-key pull-right" = link_to 'Remove', path_to_key(key, is_admin), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-sm btn-remove delete-key pull-right"
=======
%li.key-list-item
.pull-left.append-right-10
= icon 'key', class: "key-icon hidden-xs"
.key-list-item-info
= link_to path_to_key(key, is_admin), class: "title" do
= key.title
.description
= key.fingerprint
.pull-right
%span.key-created-at
created #{time_ago_with_tooltip(key.created_at)} ago
= link_to path_to_key(key, is_admin), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent prepend-left-10" do
%span.sr-only Remove
= icon('trash')
>>>>>>> 99f08b3f727e9d155ab10ad285fe48e0279fb79e
- is_admin = defined?(admin) ? true : false - is_admin = defined?(admin) ? true : false
.row .row.prepend-top-default
.col-md-4 .col-md-4
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
......
- is_admin = defined?(admin) ? true : false - is_admin = local_assigns.fetch(:admin, false)
- if @keys.any? - if @keys.any?
.table-holder %ul.well-list
%table.table = render partial: 'profiles/keys/key', collection: @keys, locals: { is_admin: is_admin }
%thead.panel-heading
%tr
%th Title
%th Fingerprint
%th Added at
%th
%tbody
- @keys.each do |key|
= render 'profiles/keys/key', key: key, is_admin: is_admin
- else - else
.nothing-here-block %p.profile-settings-message.text-center
- if is_admin - if is_admin
User has no ssh keys There are no SSH keys associated with this account.
- else - else
There are no SSH keys with access to your account. There are no SSH keys with access to your account.
- page_title "SSH Keys" - page_title "SSH Keys"
- header_title page_title, profile_keys_path - header_title page_title, profile_keys_path
.top-area .row.prepend-top-default
.nav-text .col-lg-3.profile-settings-sidebar
Before you can add an SSH key you need to %h4.prepend-top-0
= link_to "generate it.", help_page_path("ssh", "README") = page_title
.nav-controls %p
= link_to new_profile_key_path, class: "btn btn-new" do SSH keys allow you to establish a secure connection between your computer and GitLab.
= icon('plus') .col-lg-9
Add SSH Key %h5.prepend-top-0
Add an SSH key
.prepend-top-default %p.profile-settings-content
= render 'key_table' Before you can add an SSH key you need to
= link_to "generate it.", help_page_path("ssh", "README")
= render 'form'
%hr
%h5
Your SSH keys (#{@keys.count})
%div.append-bottom-default
= render 'key_table'
- page_title "Add SSH Keys"
%h3.page-title Add an SSH Key
%p.light
Paste your public key here. Read more about how to generate a key on #{link_to "the SSH help page", help_page_path("ssh", "README")}.
%hr
= render 'form'
:javascript
$('#key_key').on('focusout', function(){
var title = $('#key_title'),
val = $('#key_key').val(),
comment = val.match(/^\S+ \S+ (.+)\n?$/);
if( comment && comment.length > 1 && title.val() == '' ){
$('#key_title').val( comment[1] ).change();
}
});
- page_title "Password" - page_title "Password"
- header_title page_title, edit_profile_password_path - header_title page_title, edit_profile_password_path
.alert.alert-help.prepend-top-default .row.prepend-top-default
- if @user.password_automatically_set? .col-lg-3.profile-settings-sidebar
Set your password. %h4.prepend-top-0
- else = page_title
Change your password or recover your current one. %p
After a successful password update, you will be redirected to the login page where you can log in with your new password.
.update-password.prepend-top-default .col-lg-9
= form_for @user, url: profile_password_path, method: :put, html: { class: 'form-horizontal' } do |f| %h5.prepend-top-0
%div Change your password
%p.slead - unless @user.password_automatically_set?
- unless @user.password_automatically_set? or recover your current one
You must provide current password in order to change it. = form_for @user, url: profile_password_path, method: :put, html: {class: "update-password"} do |f|
%br
After a successful password update, you will be redirected to the login page where you can log in with your new password.
-if @user.errors.any? -if @user.errors.any?
.alert.alert-danger .alert.alert-danger
%ul %ul
...@@ -22,19 +20,16 @@ ...@@ -22,19 +20,16 @@
%li= msg %li= msg
- unless @user.password_automatically_set? - unless @user.password_automatically_set?
.form-group .form-group
= f.label :current_password, class: 'control-label' = f.label :current_password, class: 'label-light'
.col-sm-10 = f.password_field :current_password, required: true, class: 'form-control'
= f.password_field :current_password, required: true, class: 'form-control' %p.help-block
%div You must provide your current password in order to change it.
= link_to "Forgot your password?", reset_profile_password_path, method: :put .form-group
= f.label :password, 'New password', class: 'label-light'
.form-group
= f.label :password, 'New password', class: 'control-label'
.col-sm-10
= f.password_field :password, required: true, class: 'form-control' = f.password_field :password, required: true, class: 'form-control'
.form-group .form-group
= f.label :password_confirmation, class: 'control-label' = f.label :password_confirmation, class: 'label-light'
.col-sm-10
= f.password_field :password_confirmation, required: true, class: 'form-control' = f.password_field :password_confirmation, required: true, class: 'form-control'
.form-actions .prepend-top-default.append-bottom-default
= f.submit 'Save password', class: "btn btn-create" = f.submit 'Save password', class: "btn btn-create append-right-10"
= link_to "I forgot my password", reset_profile_password_path, method: :put, class: "account-btn-link"
...@@ -10,8 +10,8 @@ ...@@ -10,8 +10,8 @@
= parallel_diff_btn = parallel_diff_btn
= render 'projects/diffs/stats', diff_files: diff_files = render 'projects/diffs/stats', diff_files: diff_files
- if diff_files.count < diffs.size - if diff_files.overflow?
= render 'projects/diffs/warning', diffs: diffs, shown_files_count: diff_files.count = render 'projects/diffs/warning', diff_files: diff_files
.files .files
- diff_files.each_with_index do |diff_file, index| - diff_files.each_with_index do |diff_file, index|
...@@ -21,10 +21,3 @@ ...@@ -21,10 +21,3 @@
= render 'projects/diffs/file', i: index, project: project, = render 'projects/diffs/file', i: index, project: project,
diff_file: diff_file, diff_commit: diff_commit, blob: blob diff_file: diff_file, diff_commit: diff_commit, blob: blob
- if @diff_timeout
.alert.alert-danger
%h4
Failed to collect changes
%p
Maybe diff is really big and operation failed with timeout. Try to get diff locally
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
%table.text-file.code.js-syntax-highlight{ class: too_big ? 'hide' : '' } %table.text-file.code.js-syntax-highlight{ class: too_big ? 'hide' : '' }
- last_line = 0 - last_line = 0
- raw_diff_lines = diff_file.diff_lines - raw_diff_lines = diff_file.diff_lines.to_a
- diff_file.highlighted_diff_lines.each_with_index do |line, index| - 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
......
...@@ -14,5 +14,5 @@ ...@@ -14,5 +14,5 @@
= link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-sm" = link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-sm"
%p %p
To preserve performance only To preserve performance only
%strong #{shown_files_count} of #{diffs.size} %strong #{diff_files.count} of #{diff_files.real_size}
files are displayed. files are displayed.
...@@ -5,8 +5,39 @@ ...@@ -5,8 +5,39 @@
= render "header_title" = render "header_title"
.issue .issue
.detail-page-header .detail-page-header.issuable-header
.pull-right .pull-left
.status-box{ class: "status-box-closed #{issue_button_visibility(@issue, false)}"}
%span.hidden-xs
Closed
%span.hidden-sm.hidden-md.hidden-lg
= icon('check')
.status-box{ class: "status-box-open #{issue_button_visibility(@issue, true)}"}
%span.hidden-xs
Open
%span.hidden-sm.hidden-md.hidden-lg
= icon('circle-o')
%a.btn.btn-default.pull-right.hidden-sm.hidden-md.hidden-lg.gutter-toggle{ href: "#" }
= icon('angle-double-left')
.issue-meta
%strong.identifier
Issue ##{@issue.iid}
%span.creator
by
.editor-details
.editor-details
%strong
= link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-xs")
%span.hidden-xs
= '@' + @issue.author.username
%strong
= link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg",
by_username: true, avatar: false)
= time_ago_with_tooltip(@issue.created_at)
.pull-right.issue-btn-group
- if can?(current_user, :create_issue, @project) - if can?(current_user, :create_issue, @project)
= link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do
= icon('plus') = icon('plus')
...@@ -19,19 +50,6 @@ ...@@ -19,19 +50,6 @@
= icon('pencil-square-o') = icon('pencil-square-o')
Edit Edit
.pull-left
.status-box{ class: "status-box-closed #{issue_button_visibility(@issue, false)}"} Closed
.status-box{ class: "status-box-open #{issue_button_visibility(@issue, true)}"} Open
.issue-meta
%span.identifier
Issue ##{@issue.iid}
%span.creator
&middot;
by #{link_to_member(@project, @issue.author, size: 24)}
= '@' + @issue.author.username
&middot;
= time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago')
.issue-details.issuable-details .issue-details.issuable-details
.detail-page-description.content-block .detail-page-description.content-block
......
...@@ -33,23 +33,18 @@ ...@@ -33,23 +33,18 @@
%div= msg %div= msg
- elsif @merge_request.source_branch.present? && @merge_request.target_branch.present? - elsif @merge_request.source_branch.present? && @merge_request.target_branch.present?
- if @merge_request.compare_failed .light-well.append-bottom-default
.alert.alert-danger .center
%h4 Compare failed %h4
%p We can't compare selected branches. It may be because of huge diff. Please try again or select different branches. There isn't anything to merge.
- else %p.slead
.light-well.append-bottom-default - if @merge_request.source_branch == @merge_request.target_branch
.center You'll need to use different branch names to get a valid comparison.
%h4 - else
There isn't anything to merge. %span.label-branch #{@merge_request.source_branch}
%p.slead and
- if @merge_request.source_branch == @merge_request.target_branch %span.label-branch #{@merge_request.target_branch}
You'll need to use different branch names to get a valid comparison. are the same.
- else
%span.label-branch #{@merge_request.source_branch}
and
%span.label-branch #{@merge_request.target_branch}
are the same.
.form-actions .form-actions
......
...@@ -31,22 +31,18 @@ ...@@ -31,22 +31,18 @@
%li.diffs-tab.active %li.diffs-tab.active
= link_to url_for(params), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do = link_to url_for(params), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
Changes Changes
%span.badge= @diffs.size %span.badge= @diffs.real_size
.tab-content .tab-content
#commits.commits.tab-pane #commits.commits.tab-pane
= 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 @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
= render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @merge_request.diff_refs
- 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.
%p To preserve performance the line changes are not shown. %p To preserve performance the line changes are not shown.
- else - else
.alert.alert-danger = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @merge_request.diff_refs
%h4 This comparison includes a huge diff.
%p To preserve performance the line changes are not shown.
- if @ci_commit - if @ci_commit
#builds.builds.tab-pane #builds.builds.tab-pane
= render "projects/merge_requests/show/builds" = render "projects/merge_requests/show/builds"
......
...@@ -34,6 +34,8 @@ ...@@ -34,6 +34,8 @@
%span into %span into
= link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do = link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
= @merge_request.target_branch = @merge_request.target_branch
- if @merge_request.open? && @merge_request.diverged_from_target_branch?
%span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind)
= render "projects/merge_requests/show/how_to_merge" = render "projects/merge_requests/show/how_to_merge"
= render "projects/merge_requests/widget/show.html.haml" = render "projects/merge_requests/widget/show.html.haml"
...@@ -62,7 +64,7 @@ ...@@ -62,7 +64,7 @@
%li.diffs-tab %li.diffs-tab
= link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
Changes Changes
%span.badge= @merge_request.diffs.size %span.badge= @merge_request.diff_size
.tab-content .tab-content
#notes.notes.tab-pane.voting_notes #notes.notes.tab-pane.voting_notes
......
- 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, = render "projects/diffs/diffs", diffs: @merge_request.diffs(diff_options),
project: @merge_request.project, diff_refs: @merge_request.diff_refs 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}
......
.detail-page-header .detail-page-header
.status-box{ class: status_box_class(@merge_request) } .status-box{ class: status_box_class(@merge_request) }
= @merge_request.state_human_name %span.hidden-xs
%span.identifier = @merge_request.state_human_name
Merge Request #{@merge_request.to_reference} %span.hidden-sm.hidden-md.hidden-lg
%span.creator = icon(@merge_request.state_icon_name)
&middot; %a.btn.btn-default.pull-right.hidden-sm.hidden-md.hidden-lg.gutter-toggle{ href: "#" }
by #{link_to_member(@project, @merge_request.author, size: 24)} = icon('angle-double-left')
= '@' + @merge_request.author.username .issue-meta
&middot; %strong.identifier
= time_ago_with_tooltip(@merge_request.created_at) Merge Request ##{@merge_request.iid}
%span.creator
by
.editor-details
%strong
= link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-xs")
%span.hidden-xs
= '@' + @merge_request.author.username
%strong
= link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg",
by_username: true, avatar: false)
= time_ago_with_tooltip(@merge_request.created_at)
.issue-btn-group.pull-right .issue-btn-group.pull-right
- if can?(current_user, :update_merge_request, @merge_request) - if can?(current_user, :update_merge_request, @merge_request)
......
...@@ -18,7 +18,9 @@ ...@@ -18,7 +18,9 @@
= render 'shared/projects/list', projects: @objects = render 'shared/projects/list', projects: @objects
- else - else
= render partial: "search/results/#{@scope.singularize}", collection: @objects = render partial: "search/results/#{@scope.singularize}", collection: @objects
= paginate @objects, theme: 'gitlab'
- if @scope != 'projects'
= paginate @objects, theme: 'gitlab'
:javascript :javascript
$(".search-results .term").highlight("#{escape_javascript(params[:search])}"); $(".search-results .term").highlight("#{escape_javascript(params[:search])}");
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
.line-numbers .line-numbers
- snippet_chunks.each do |chunk| - snippet_chunks.each do |chunk|
- unless chunk[:data].empty? - unless chunk[:data].empty?
- chunk[:data].lines.to_a.size.times do |index| - Gitlab::Git::Util.count_lines(chunk[:data]).times do |index|
- offset = defined?(chunk[:start_line]) ? chunk[:start_line] : 1 - offset = defined?(chunk[:start_line]) ? chunk[:start_line] : 1
- i = index + offset - i = index + offset
= link_to snippet_path+"#L#{i}", id: "L#{i}", rel: "#L#{i}", class: "diff-line-num" do = link_to snippet_path+"#L#{i}", id: "L#{i}", rel: "#L#{i}", class: "diff-line-num" do
......
- if cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key? - if cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key?
.no-ssh-key-message.alert.alert-warning.hidden-xs .no-ssh-key-message.alert.alert-warning.hidden-xs
You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_profile_key_path, class: 'alert-link'} to your profile You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', profile_keys_path, class: 'alert-link'} to your profile
.pull-right .pull-right
= link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'alert-link' = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'alert-link'
......
...@@ -141,7 +141,7 @@ class IrkerWorker ...@@ -141,7 +141,7 @@ class IrkerWorker
end end
def files_count(commit) def files_count(commit)
files = "#{commit.diffs.count} file" files = "#{commit.diffs.real_size} file"
files += 's' if commit.diffs.count > 1 files += 's' if commit.diffs.count > 1
files files
end end
......
...@@ -96,5 +96,9 @@ module Gitlab ...@@ -96,5 +96,9 @@ module Gitlab
# Gitlab Geo Middleware support # Gitlab Geo Middleware support
config.middleware.use 'Gitlab::Middleware::ReadonlyGeo' config.middleware.use 'Gitlab::Middleware::ReadonlyGeo'
config.generators do |g|
g.factory_girl false
end
end end
end end
...@@ -320,7 +320,7 @@ Rails.application.routes.draw do ...@@ -320,7 +320,7 @@ Rails.application.routes.draw do
end end
end end
resource :preferences, only: [:show, :update] resource :preferences, only: [:show, :update]
resources :keys resources :keys, except: [:new]
resources :emails, only: [:index, :create, :destroy] resources :emails, only: [:index, :create, :destroy]
resource :avatar, only: [:destroy] resource :avatar, only: [:destroy]
resource :two_factor_auth, only: [:new, :create, :destroy] do resource :two_factor_auth, only: [:new, :create, :destroy] do
......
user_args = {
email: ENV['GITLAB_ROOT_EMAIL'].presence || 'admin@example.com',
name: 'Administrator',
username: 'root',
admin: true
}
if ENV['GITLAB_ROOT_PASSWORD'].blank? if ENV['GITLAB_ROOT_PASSWORD'].blank?
password = '5iveL!fe' user_args[:password_automatically_set] = true
expire_time = Time.now user_args[:force_random_password] = true
else else
password = ENV['GITLAB_ROOT_PASSWORD'] user_args[:password] = ENV['GITLAB_ROOT_PASSWORD']
expire_time = nil
end end
email = ENV['GITLAB_ROOT_EMAIL'].presence || 'admin@example.com' user = User.new(user_args)
user.skip_confirmation!
admin = User.create(
email: email,
name: "Administrator",
username: 'root',
password: password,
password_expires_at: expire_time,
theme_id: Gitlab::Themes::APPLICATION_DEFAULT
)
admin.projects_limit = 10000 if user.save
admin.admin = true puts "Administrator account created:".green
admin.save! puts
admin.confirm puts "login: root".green
if admin.valid? if user_args.key?(:password)
puts %Q[ puts "password: #{user_args[:password]}".green
Administrator account created: else
puts "password: You'll be prompted to create one on your first visit.".green
end
puts
else
puts "Could not create the default administrator account:".red
puts
user.errors.full_messages.map do |message|
puts "--> #{message}".red
end
puts
login.........root exit 1
password......#{password}
]
end end
class AddRealSizeToMergeRequestDiffs < ActiveRecord::Migration
def change
add_column :merge_request_diffs, :real_size, :string
end
end
...@@ -590,6 +590,7 @@ ActiveRecord::Schema.define(version: 20160302141317) do ...@@ -590,6 +590,7 @@ ActiveRecord::Schema.define(version: 20160302141317) do
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.string "base_commit_sha" t.string "base_commit_sha"
t.string "real_size"
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
......
# Quick Start # Quick Start
Starting from version 8.0, GitLab Continuous Integration (CI) is fully >**Note:** Starting from version 8.0, GitLab [Continuous Integration][ci] (CI)
integrated into GitLab itself and is enabled by default on all projects. is fully integrated into GitLab itself and is [enabled] by default on all
projects.
This guide assumes that you: The TL;DR version of how GitLab CI works is the following.
- have a working GitLab instance of version 8.0 or higher or are using ---
[GitLab.com](https://gitlab.com/users/sign_in)
- have a project in GitLab that you would like to use CI for GitLab offers a [continuous integration][ci] service. If you
[add a `.gitlab-ci.yml` file][yaml] to the root directory of your repository,
and configure your GitLab project to use a [Runner], then each merge request or
push triggers a build.
The `.gitlab-ci.yml` file tells the GitLab runner what do to. By default it
runs three [stages]: `build`, `test`, and `deploy`.
If everything runs OK (no non-zero return values), you'll get a nice green
checkmark associated with the pushed commit or merge request. This makes it
easy to see whether a merge request will cause any of the tests to fail before
you even look at the code.
Most projects only use GitLab's CI service to run the test suite so that
developers get immediate feedback if they broke something.
In brief, the steps needed to have a working CI can be summed up to: So in brief, the steps needed to have a working CI can be summed up to:
1. Create a new project 1. Add `.gitlab-ci.yml` to the root directory of your repository
1. Add `.gitlab-ci.yml` to the git repository and push to GitLab
1. Configure a Runner 1. Configure a Runner
From there on, on every push to your git repository the build will be From there on, on every push to your Git repository, the build will be
automagically started by the Runner and will appear under the project's automagically started by the Runner and will appear under the project's
`/builds` page. `/builds` page.
Now, let's break it down to pieces and work on solving the GitLab CI puzzle. ---
This guide assumes that you:
- have a working GitLab instance of version 8.0 or higher or are using
[GitLab.com](https://gitlab.com/users/sign_in)
- have a project in GitLab that you would like to use CI for
Let's break it down to pieces and work on solving the GitLab CI puzzle.
## Creating a `.gitlab-ci.yml` file ## Creating a `.gitlab-ci.yml` file
...@@ -218,3 +240,8 @@ Visit our various languages examples at <https://gitlab.com/groups/gitlab-exampl ...@@ -218,3 +240,8 @@ Visit our various languages examples at <https://gitlab.com/groups/gitlab-exampl
[runner-install]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/tree/master#installation [runner-install]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/tree/master#installation
[blog-ci]: https://about.gitlab.com/2015/05/06/why-were-replacing-gitlab-ci-jobs-with-gitlab-ci-dot-yml/ [blog-ci]: https://about.gitlab.com/2015/05/06/why-were-replacing-gitlab-ci-jobs-with-gitlab-ci-dot-yml/
[examples]: ../examples/README.md [examples]: ../examples/README.md
[ci]: https://about.gitlab.com/gitlab-ci/
[yaml]: ../yaml/README.md
[runner]: ../runners/README.md
[enabled]: ../enable_or_disable_ci.md
[stages]: ../yaml/README.md#stages
...@@ -207,6 +207,7 @@ GFM also recognizes certain cross-project references: ...@@ -207,6 +207,7 @@ GFM also recognizes certain cross-project references:
| `namespace/project$123` | snippet | | `namespace/project$123` | snippet |
| `namespace/project@9ba12248` | specific commit | | `namespace/project@9ba12248` | specific commit |
| `namespace/project@9ba12248...b19a04f5` | commit range comparison | | `namespace/project@9ba12248...b19a04f5` | commit range comparison |
| `namespace/project~"Some label"` | issues with given label |
## Task Lists ## Task Lists
......
...@@ -9,7 +9,7 @@ Feature: Profile SSH Keys ...@@ -9,7 +9,7 @@ Feature: Profile SSH Keys
Then I should see my ssh keys Then I should see my ssh keys
Scenario: Add new ssh key Scenario: Add new ssh key
Given I click link "Add new" Given I should see new ssh key form
And I submit new ssh key "Laptop" And I submit new ssh key "Laptop"
Then I should see new ssh key "Laptop" Then I should see new ssh key "Laptop"
......
...@@ -20,3 +20,8 @@ Feature: Project Badges Build ...@@ -20,3 +20,8 @@ Feature: Project Badges Build
And project has another build that is running And project has another build that is running
When I display builds badge for a master branch When I display builds badge for a master branch
Then I should see a build running badge Then I should see a build running badge
Scenario: I want to see a fresh badge on each request
Given recent build is successful
When I display builds badge for a master branch
Then I should see a badge that has not been cached
...@@ -26,6 +26,16 @@ Feature: Project Merge Requests ...@@ -26,6 +26,16 @@ Feature: Project Merge Requests
When I visit project "Shop" merge requests page When I visit project "Shop" merge requests page
Then I should see "other_branch" branch Then I should see "other_branch" branch
Scenario: I should not see the numbers of diverged commits if the branch is rebased on the target
Given project "Shop" have "Bug NS-07" open merge request with rebased branch
When I visit merge request page "Bug NS-07"
Then I should not see the diverged commits count
Scenario: I should see the numbers of diverged commits if the branch diverged from the target
Given project "Shop" have "Bug NS-08" open merge request with diverged branch
When I visit merge request page "Bug NS-08"
Then I should see the diverged commits count
Scenario: I should see rejected merge requests Scenario: I should see rejected merge requests
Given I click link "Closed" Given I click link "Closed"
Then I should see "Feature NS-03" in merge requests Then I should see "Feature NS-03" in merge requests
......
...@@ -64,7 +64,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps ...@@ -64,7 +64,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
page.within '.update-password' do page.within '.update-password' do
fill_in "user_password", with: "22233344" fill_in "user_password", with: "22233344"
fill_in "user_password_confirmation", with: "22233344" fill_in "user_password_confirmation", with: "22233344"
click_button "Save" click_button "Save password"
end end
end end
...@@ -73,7 +73,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps ...@@ -73,7 +73,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
fill_in "user_current_password", with: "12345678" fill_in "user_current_password", with: "12345678"
fill_in "user_password", with: "22233344" fill_in "user_password", with: "22233344"
fill_in "user_password_confirmation", with: "22233344" fill_in "user_password_confirmation", with: "22233344"
click_button "Save" click_button "Save password"
end end
end end
...@@ -82,7 +82,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps ...@@ -82,7 +82,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
fill_in "user_current_password", with: "12345678" fill_in "user_current_password", with: "12345678"
fill_in "user_password", with: "password" fill_in "user_password", with: "password"
fill_in "user_password_confirmation", with: "confirmation" fill_in "user_password_confirmation", with: "confirmation"
click_button "Save" click_button "Save password"
end end
end end
......
...@@ -7,8 +7,8 @@ class Spinach::Features::ProfileSshKeys < Spinach::FeatureSteps ...@@ -7,8 +7,8 @@ class Spinach::Features::ProfileSshKeys < Spinach::FeatureSteps
end end
end end
step 'I click link "Add new"' do step 'I should see new ssh key form' do
click_link "Add SSH Key" expect(page).to have_content("Add an SSH key")
end end
step 'I submit new ssh key "Laptop"' do step 'I submit new ssh key "Laptop"' do
......
...@@ -20,6 +20,10 @@ class Spinach::Features::ProjectBadgesBuild < Spinach::FeatureSteps ...@@ -20,6 +20,10 @@ class Spinach::Features::ProjectBadgesBuild < Spinach::FeatureSteps
expect_badge('running') expect_badge('running')
end end
step 'I should see a badge that has not been cached' do
expect(page.response_headers).to include('Cache-Control' => 'no-cache')
end
def expect_badge(status) def expect_badge(status)
svg = Nokogiri::XML.parse(page.body) svg = Nokogiri::XML.parse(page.body)
expect(page.response_headers).to include('Content-Type' => 'image/svg+xml') expect(page.response_headers).to include('Content-Type' => 'image/svg+xml')
......
...@@ -126,8 +126,11 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps ...@@ -126,8 +126,11 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end end
step 'I visit big commit page' do step 'I visit big commit page' do
stub_const('Commit::DIFF_SAFE_FILES', 20) # Create a temporary scope to ensure that the stub_const is removed after user
visit namespace_project_commit_path(@project.namespace, @project, sample_big_commit.id) RSpec::Mocks.with_temporary_scope do
stub_const('Gitlab::Git::DiffCollection::DEFAULT_LIMITS', { max_lines: 1, max_files: 1 })
visit namespace_project_commit_path(@project.namespace, @project, sample_big_commit.id)
end
end end
step 'I see big commit warning' do step 'I see big commit warning' do
......
...@@ -60,7 +60,6 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -60,7 +60,6 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
expect(page).not_to have_content "Feature NS-03" expect(page).not_to have_content "Feature NS-03"
end end
step 'I should not see "Bug NS-04" in merge requests' do step 'I should not see "Bug NS-04" in merge requests' do
expect(page).not_to have_content "Bug NS-04" expect(page).not_to have_content "Bug NS-04"
end end
...@@ -121,6 +120,22 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -121,6 +120,22 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
author: project.users.first) author: project.users.first)
end end
step 'project "Shop" have "Bug NS-07" open merge request with rebased branch' do
create(:merge_request, :rebased,
title: "Bug NS-07",
source_project: project,
target_project: project,
author: project.users.first)
end
step 'project "Shop" have "Bug NS-08" open merge request with diverged branch' do
create(:merge_request, :diverged,
title: "Bug NS-08",
source_project: project,
target_project: project,
author: project.users.first)
end
step 'project "Shop" have "Feature NS-03" closed merge request' do step 'project "Shop" have "Feature NS-03" closed merge request' do
create(:closed_merge_request, create(:closed_merge_request,
title: "Feature NS-03", title: "Feature NS-03",
...@@ -615,6 +630,18 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -615,6 +630,18 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end end
end end
step 'I should see the diverged commits count' do
page.within ".mr-source-target" do
expect(page).to have_content /([0-9]+ commits behind)/
end
end
step 'I should not see the diverged commits count' do
page.within ".mr-source-target" do
expect(page).not_to have_content /([0-9]+ commit[s]? behind)/
end
end
step 'I should see "Bug NS-05" at the top' do 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") expect(page.find('ul.content-list.mr-list li.merge-request:first-child')).to have_content("Bug NS-05")
end end
......
...@@ -29,7 +29,7 @@ class Spinach::Features::ProjectMergeRequestsAcceptance < Spinach::FeatureSteps ...@@ -29,7 +29,7 @@ class Spinach::Features::ProjectMergeRequestsAcceptance < Spinach::FeatureSteps
step 'There is an open Merge Request' do step 'There is an open Merge Request' do
@user = create(:user) @user = create(:user)
@project = create(:project, :public) @project = create(:project, :public)
@project_member = create(:project_member, user: @user, project: @project, access_level: ProjectMember::DEVELOPER) @project_member = create(:project_member, :developer, user: @user, project: @project)
@merge_request = create(:merge_request, :with_diffs, :simple, source_project: @project) @merge_request = create(:merge_request, :with_diffs, :simple, source_project: @project)
end end
......
...@@ -36,7 +36,7 @@ class Spinach::Features::RevertMergeRequests < Spinach::FeatureSteps ...@@ -36,7 +36,7 @@ class Spinach::Features::RevertMergeRequests < Spinach::FeatureSteps
step 'There is an open Merge Request' do step 'There is an open Merge Request' do
@user = create(:user) @user = create(:user)
@project = create(:project, :public) @project = create(:project, :public)
@project_member = create(:project_member, user: @user, project: @project, access_level: ProjectMember::DEVELOPER) @project_member = create(:project_member, :developer, user: @user, project: @project)
@merge_request = create(:merge_request, :with_diffs, :simple, source_project: @project) @merge_request = create(:merge_request, :with_diffs, :simple, source_project: @project)
end end
......
...@@ -416,13 +416,19 @@ module SharedPaths ...@@ -416,13 +416,19 @@ module SharedPaths
end end
step 'I visit merge request page "Bug NS-04"' do step 'I visit merge request page "Bug NS-04"' do
mr = MergeRequest.find_by(title: "Bug NS-04") visit merge_request_path("Bug NS-04")
visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
end end
step 'I visit merge request page "Bug NS-05"' do step 'I visit merge request page "Bug NS-05"' do
mr = MergeRequest.find_by(title: "Bug NS-05") visit merge_request_path("Bug NS-05")
visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr) end
step 'I visit merge request page "Bug NS-07"' do
visit merge_request_path("Bug NS-07")
end
step 'I visit merge request page "Bug NS-08"' do
visit merge_request_path("Bug NS-08")
end end
step 'I visit merge request page "Bug CO-01"' do step 'I visit merge request page "Bug CO-01"' do
...@@ -531,6 +537,11 @@ module SharedPaths ...@@ -531,6 +537,11 @@ module SharedPaths
Project.find_by!(name: 'Shop') Project.find_by!(name: 'Shop')
end end
def merge_request_path(title)
mr = MergeRequest.find_by(title: title)
namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
end
# ---------------------------------------- # ----------------------------------------
# Errors # Errors
# ---------------------------------------- # ----------------------------------------
......
...@@ -48,7 +48,7 @@ module API ...@@ -48,7 +48,7 @@ module API
sha = params[:sha] sha = params[:sha]
commit = user_project.commit(sha) commit = user_project.commit(sha)
not_found! "Commit" unless commit not_found! "Commit" unless commit
commit.diffs commit.diffs.to_a
end end
# Get a commit's comments # Get a commit's comments
...@@ -90,9 +90,9 @@ module API ...@@ -90,9 +90,9 @@ module API
} }
if params[:path] && params[:line] && params[:line_type] if params[:path] && params[:line] && params[:line_type]
commit.diffs.each do |diff| commit.diffs(all_diffs: true).each do |diff|
next unless diff.new_path == params[:path] next unless diff.new_path == params[:path]
lines = Gitlab::Diff::Parser.new.parse(diff.diff.lines.to_a) lines = Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
lines.each do |line| lines.each do |line|
next unless line.new_pos == params[:line].to_i && line.type == params[:line_type] next unless line.new_pos == params[:line].to_i && line.type == params[:line_type]
......
...@@ -191,7 +191,7 @@ module API ...@@ -191,7 +191,7 @@ module API
class MergeRequestChanges < MergeRequest class MergeRequestChanges < MergeRequest
expose :diffs, as: :changes, using: Entities::RepoDiff do |compare, _| expose :diffs, as: :changes, using: Entities::RepoDiff do |compare, _|
compare.diffs compare.diffs(all_diffs: true).to_a
end end
end end
...@@ -313,11 +313,11 @@ module API ...@@ -313,11 +313,11 @@ module API
end end
expose :diffs, using: Entities::RepoDiff do |compare, options| expose :diffs, using: Entities::RepoDiff do |compare, options|
compare.diffs compare.diffs(all_diffs: true).to_a
end end
expose :compare_timeout do |compare, options| expose :compare_timeout do |compare, options|
compare.timeout compare.diffs.overflow?
end end
expose :same, as: :compare_same_ref expose :same, as: :compare_same_ref
......
...@@ -7,6 +7,10 @@ module Banzai ...@@ -7,6 +7,10 @@ module Banzai
Renderer.render_result(text, context) Renderer.render_result(text, context)
end end
def self.pre_process(text, context)
Renderer.pre_process(text, context)
end
def self.post_process(html, context) def self.post_process(html, context)
Renderer.post_process(html, context) Renderer.post_process(html, context)
end end
......
...@@ -94,6 +94,8 @@ module Banzai ...@@ -94,6 +94,8 @@ module Banzai
object_link_filter(link, object_class.link_reference_pattern, link_text: text) object_link_filter(link, object_class.link_reference_pattern, link_text: text)
end end
end end
doc
end end
# Replace references (like `!123` for merge requests) in text with links # Replace references (like `!123` for merge requests) in text with links
......
...@@ -26,6 +26,10 @@ module Banzai ...@@ -26,6 +26,10 @@ module Banzai
# * [[http://example.com/images/logo.png]] # * [[http://example.com/images/logo.png]]
# * [[http://example.com/images/logo.png|alt=Logo]] # * [[http://example.com/images/logo.png|alt=Logo]]
# #
# - Insert a Table of Contents list:
#
# * [[_TOC_]]
#
# Based on Gollum::Filter::Tags # Based on Gollum::Filter::Tags
# #
# Context options: # Context options:
...@@ -61,8 +65,6 @@ module Banzai ...@@ -61,8 +65,6 @@ module Banzai
# before this one, it will be converted into `[[<em>TOC</em>]]`, so it # before this one, it will be converted into `[[<em>TOC</em>]]`, so it
# needs special-case handling # needs special-case handling
if toc_tag?(node) if toc_tag?(node)
next unless result[:toc].present?
process_toc_tag(node) process_toc_tag(node)
else else
content = node.content content = node.content
...@@ -85,7 +87,7 @@ module Banzai ...@@ -85,7 +87,7 @@ module Banzai
# Replace an entire `[[<em>TOC</em>]]` node with the result generated by # Replace an entire `[[<em>TOC</em>]]` node with the result generated by
# TableOfContentsFilter # TableOfContentsFilter
def process_toc_tag(node) def process_toc_tag(node)
node.parent.parent.replace(result[:toc]) node.parent.parent.replace(result[:toc].presence || '')
end end
# Process a single tag into its final HTML form. # Process a single tag into its final HTML form.
......
module Banzai module Banzai
module Filter module Filter
# HTML filter that replaces label references with links. # HTML filter that replaces label references with links.
class LabelReferenceFilter < ReferenceFilter class LabelReferenceFilter < AbstractReferenceFilter
# Public: Find label references in text def self.object_class
# Label
# LabelReferenceFilter.references_in(text) do |match, id, name|
# "<a href=...>#{Label.find(id)}</a>"
# end
#
# text - String text to search.
#
# Yields the String match, an optional Integer label ID, and an optional
# String label name.
#
# Returns a String replaced with the return of the block.
def self.references_in(text)
text.gsub(Label.reference_pattern) do |match|
yield match, $~[:label_id].to_i, $~[:label_name]
end
end end
def self.referenced_by(node) def find_object(project, id)
{ label: LazyReference.new(Label, node.attr("data-label")) } project.labels.find(id)
end end
def call def self.references_in(text, pattern = Label.reference_pattern)
replace_text_nodes_matching(Label.reference_pattern) do |content| text.gsub(pattern) do |match|
label_link_filter(content) yield match, $~[:label_id].to_i, $~[:label_name], $~[:project], $~
end
replace_link_nodes_with_href(Label.reference_pattern) do |link, text|
label_link_filter(link, link_text: text)
end end
end end
# Replace label references in text with links to the label specified. def references_in(text, pattern = Label.reference_pattern)
# text.gsub(pattern) do |match|
# text - String text to replace references in. project = project_from_ref($~[:project])
# params = label_params($~[:label_id].to_i, $~[:label_name])
# Returns a String with label references replaced with links. All links label = project.labels.find_by(params)
# have `gfm` and `gfm-label` class names attached for styling.
def label_link_filter(text, link_text: nil)
project = context[:project]
self.class.references_in(text) do |match, id, name|
params = label_params(id, name)
if label = project.labels.find_by(params)
url = url_for_label(project, label)
klass = reference_class(:label)
data = data_attribute(
original: link_text || match,
project: project.id,
label: label.id
)
text = link_text || render_colored_label(label) if label
yield match, label.id, $~[:project], $~
%(<a href="#{url}" #{data}
class="#{klass}">#{escape_once(text)}</a>)
else else
match match
end end
end end
end end
def url_for_label(project, label) def url_for_object(label, project)
h = Gitlab::Application.routes.url_helpers h = Gitlab::Application.routes.url_helpers
h.namespace_project_issues_url( project.namespace, project, label_name: label.name, h.namespace_project_issues_url(project.namespace, project, label_name: label.name,
only_path: context[:only_path]) only_path: context[:only_path])
end end
def render_colored_label(label) def object_link_text(object, matches)
LabelsHelper.render_colored_label(label) if context[:project] == object.project
LabelsHelper.render_colored_label(object)
else
LabelsHelper.render_colored_cross_project_label(object)
end
end end
# Parameters to pass to `Label.find_by` based on the given arguments # Parameters to pass to `Label.find_by` based on the given arguments
......
require 'html/pipeline/filter'
require 'yaml'
module Banzai
module Filter
class YamlFrontMatterFilter < HTML::Pipeline::Filter
DELIM = '---'.freeze
# Hat-tip to Middleman: https://git.io/v2e0z
PATTERN = %r{
\A(?:[^\r\n]*coding:[^\r\n]*\r?\n)?
(?<start>#{DELIM})[ ]*\r?\n
(?<frontmatter>.*?)[ ]*\r?\n?
^(?<stop>#{DELIM})[ ]*\r?\n?
\r?\n?
(?<content>.*)
}mx.freeze
def call
match = PATTERN.match(html)
return html unless match
"```yaml\n#{match['frontmatter']}\n```\n\n#{match['content']}"
end
end
end
end
module Banzai
module Pipeline
class PreProcessPipeline < BasePipeline
def self.filters
FilterArray[
Filter::YamlFrontMatterFilter
]
end
def self.transform_context(context)
context.merge(
pre_process: true
)
end
end
end
end
...@@ -31,6 +31,12 @@ module Banzai ...@@ -31,6 +31,12 @@ module Banzai
Pipeline[context[:pipeline]].call(text, context) Pipeline[context[:pipeline]].call(text, context)
end end
def self.pre_process(text, context)
pipeline = Pipeline[:pre_process]
pipeline.to_html(text, context)
end
# Perform post-processing on an HTML String # Perform post-processing on an HTML String
# #
# This method is used to perform state-dependent changes to a String of # This method is used to perform state-dependent changes to a String of
......
module Gitlab
class CompareResult
attr_reader :commits, :diffs
def initialize(compare, diff_options = {})
@commits, @diffs = compare.commits, compare.diffs(nil, diff_options)
end
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment