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)
- Fix issue when pushing to projects ending in .wiki
- Fix avatar stretching by providing a cropping feature (Johann Pardanaud)
- 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)
- 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
- Allow search for logged out users
- 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
- 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
- Fix sidebar overlapping content when screen width was below 1200px
......
......@@ -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
1. Keeps the GitLab code base clean and well structured
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
(no squashing). If necessary, you will be asked to squash when the review is
over, before merging.
......
......@@ -54,7 +54,7 @@ gem "browser", '~> 1.0.0'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
gem "gitlab_git", '~> 8.2'
gem "gitlab_git", '~> 9.0'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
......@@ -80,7 +80,7 @@ gem "kaminari", "~> 0.16.3"
gem "haml-rails", '~> 0.9.0'
# Files attachments
gem "carrierwave", '~> 0.9.0'
gem "carrierwave", '~> 0.10.0'
# Image editing
gem "mini_magick", '~> 4.4.0'
......@@ -270,10 +270,10 @@ group :development, :test do
gem 'awesome_print', '~> 1.2.0', require: false
gem 'fuubar', '~> 2.0.0'
gem 'database_cleaner', '~> 1.4.0'
gem 'factory_girl_rails', '~> 4.3.0'
gem 'rspec-rails', '~> 3.3.0'
gem 'spinach-rails', '~> 0.2.1'
gem 'database_cleaner', '~> 1.4.0'
gem 'factory_girl_rails', '~> 4.6.0'
gem 'rspec-rails', '~> 3.3.0'
gem 'spinach-rails', '~> 0.2.1'
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.7.0'
......@@ -288,7 +288,7 @@ group :development, :test do
gem 'teaspoon', '~> 1.0.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-spinach', '~> 1.0.0'
gem 'spring-commands-teaspoon', '~> 0.0.2'
......
......@@ -117,10 +117,11 @@ GEM
capybara-screenshot (1.0.11)
capybara (>= 1.0, < 3)
launchy
carrierwave (0.9.0)
carrierwave (0.10.0)
activemodel (>= 3.2.0)
activesupport (>= 3.2.0)
json (>= 1.7)
mime-types (>= 1.16)
cause (0.1)
charlock_holmes (0.7.3)
chunky_png (1.3.5)
......@@ -207,10 +208,10 @@ GEM
excon (0.45.4)
execjs (2.6.0)
expression_parser (0.9.0)
factory_girl (4.3.0)
factory_girl (4.5.0)
activesupport (>= 3.0.0)
factory_girl_rails (4.3.0)
factory_girl (~> 4.3.0)
factory_girl_rails (4.6.0)
factory_girl (~> 4.5.0)
railties (>= 3.0.0)
faraday (0.9.2)
multipart-post (>= 1.2, < 3)
......@@ -379,7 +380,7 @@ GEM
gitlab-license (0.0.4)
gitlab_emoji (0.3.1)
gemojione (~> 2.2, >= 2.2.1)
gitlab_git (8.2.0)
gitlab_git (9.0.0)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
......@@ -787,7 +788,7 @@ GEM
capybara (>= 2.0.0)
railties (>= 3)
spinach (>= 0.4)
spring (1.3.6)
spring (1.6.4)
spring-commands-rspec (1.0.4)
spring (>= 0.9.1)
spring-commands-spinach (1.0.0)
......@@ -925,7 +926,7 @@ DEPENDENCIES
cal-heatmap-rails (~> 3.5.0)
capybara (~> 2.4.0)
capybara-screenshot (~> 1.0.0)
carrierwave (~> 0.9.0)
carrierwave (~> 0.10.0)
charlock_holmes (~> 0.7.3)
coffee-rails (~> 4.1.0)
colorize (~> 0.7.0)
......@@ -945,7 +946,7 @@ DEPENDENCIES
elasticsearch-rails
email_reply_parser (~> 0.5.8)
email_spec (~> 1.6.0)
factory_girl_rails (~> 4.3.0)
factory_girl_rails (~> 4.6.0)
ffaker (~> 2.0.0)
flay
flog
......@@ -960,7 +961,7 @@ DEPENDENCIES
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 0.0.4)
gitlab_emoji (~> 0.3.0)
gitlab_git (~> 8.2)
gitlab_git (~> 9.0)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.1.0)
......@@ -1049,7 +1050,7 @@ DEPENDENCIES
six (~> 0.2.0)
slack-notifier (~> 1.2.0)
spinach-rails (~> 0.2.1)
spring (~> 1.3.6)
spring (~> 1.6.4)
spring-commands-rspec (~> 1.0.4)
spring-commands-spinach (~> 1.0.0)
spring-commands-teaspoon (~> 0.0.2)
......
......@@ -224,41 +224,41 @@ $ ->
.off 'breakpoint:change'
.on 'breakpoint:change', (e, breakpoint) ->
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')
$gutterIcon.closest('a').trigger('click')
$(document)
.off 'click', 'aside .gutter-toggle'
.on 'click', 'aside .gutter-toggle', (e) ->
.on 'click', 'aside .gutter-toggle', (e, triggered) ->
e.preventDefault()
$this = $(this)
$thisIcon = $this.find 'i'
$allGutterToggleIcons = $('.gutter-toggle i')
if $thisIcon.hasClass('fa-angle-double-right')
$thisIcon
$allGutterToggleIcons
.removeClass('fa-angle-double-right')
.addClass('fa-angle-double-left')
$this
.closest('aside')
$('aside.right-sidebar')
.removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed')
$('.page-with-sidebar')
.removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed')
else
$thisIcon
$allGutterToggleIcons
.removeClass('fa-angle-double-left')
.addClass('fa-angle-double-right')
$this
.closest('aside')
$('aside.right-sidebar')
.removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded')
$('.page-with-sidebar')
.removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded')
$.cookie("collapsed_gutter",
$('.right-sidebar')
.hasClass('right-sidebar-collapsed'), { path: '/' })
if not triggered
$.cookie("collapsed_gutter",
$('.right-sidebar')
.hasClass('right-sidebar-collapsed'), { path: '/' })
bootstrapBreakpoint = undefined;
checkBootstrapBreakpoints = ->
......
......@@ -24,10 +24,7 @@ keyCodeIs = (e, keyCode) ->
$(document).on 'keydown.quick_submit', '.js-quick-submit', (e) ->
return unless keyCodeIs(e, 13) # Enter
if isMac()
return unless (e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey)
else
return unless (e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey)
return unless (e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey) || (e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey)
e.preventDefault()
......
......@@ -195,6 +195,6 @@ class @MergeRequestTabs
setTimeout( ->
# Only when sidebar is collapsed
if $gutterIcon.is('.fa-angle-double-right')
$gutterIcon.closest('a').trigger('click')
$gutterIcon.closest('a').trigger('click',[true])
, 0)
......@@ -16,11 +16,13 @@ class @Notes
@view = view
@noteable_url = document.URL
@notesCountBadge ||= $(".issuable-details").find(".notes-tab .badge")
@basePollingInterval = 15000
@maxPollingSteps = 4
@initRefresh()
@setupMainTargetNoteForm()
@cleanBinding()
@addBinding()
@setPollingInterval()
@setupMainTargetNoteForm()
@initTaskList()
addBinding: ->
......@@ -37,7 +39,7 @@ class @Notes
# Reopen and close actions for Issue/MR combined with note form submit
$(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)
$(document).on "click", ".js-note-delete", @removeNote
......@@ -91,9 +93,11 @@ class @Notes
clearInterval(Notes.interval)
Notes.interval = setInterval =>
@refresh()
, 15000
, @pollingInterval
refresh: ->
return if @refreshing is true
refreshing = true
if not document.hidden and document.URL.indexOf(@noteable_url) is 0
@getContent()
......@@ -105,12 +109,31 @@ class @Notes
success: (data) =>
notes = data.notes
@last_fetched_at = data.last_fetched_at
@setPollingInterval(data.notes.length)
$.each notes, (i, note) =>
if note.discussion_with_diff_html?
@renderDiscussionNote(note)
else
@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.
......
......@@ -62,3 +62,12 @@ class @Profile
$modalCropImg.attr('src', event.target.result)
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 @@
@import "framework/calendar.scss";
@import "framework/callout.scss";
@import "framework/common.scss";
@import "framework/dropdowns.scss";
@import "framework/files.scss";
@import "framework/filters.scss";
@import "framework/flash.scss";
......
......@@ -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 {
width: 100%;
margin: 0;
......
......@@ -60,25 +60,6 @@ hr {
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 {
@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 @@
*/
.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);
display: block;
float: left;
padding: 0 $gl-btn-padding;
margin-top: 5px;
margin-right: 10px;
color: #FFF;
font-size: $gl-font-size;
......
......@@ -3,6 +3,7 @@
*
*/
.well-list {
position: relative;
margin: 0;
padding: 0;
list-style: none;
......
......@@ -27,7 +27,14 @@
}
&.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 @@
padding-left: $sidebar_width;
&.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 {
......@@ -225,7 +237,12 @@
padding-left: $sidebar_collapsed_width;
&.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 {
......@@ -292,7 +309,13 @@
}
.page-sidebar-collapsed {
/* Extra small devices (phones, less than 768px) */
@include collapsed-sidebar;
padding-right: 0;
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) {
@include collapsed-sidebar;
}
}
.page-sidebar-expanded {
......
......@@ -22,7 +22,7 @@
// Components
@import "bootstrap/component-animations";
@import "bootstrap/dropdowns";
// @import "bootstrap/dropdowns";
@import "bootstrap/button-groups";
@import "bootstrap/input-groups";
@import "bootstrap/navs";
......
......@@ -36,6 +36,11 @@ $list-title-color: #333333;
$list-text-color: #555555;
$profile-settings-link-color: $md-link-color;
$btn-transparent-color: #8F8F8F;
$ssh-key-icon-color: #8F8F8F;
$ssh-key-icon-size: 18px;
/*
* Color schema
*/
......@@ -93,6 +98,9 @@ $border-red-light: #E52C5A;
$border-red-normal: #D22852;
$border-red-dark: #CA264F;
$help-well-bg: #FAFAFA;
$help-well-border: #E5E5E5;
/* header */
$light-grey-header: #faf9f9;
......@@ -118,3 +126,15 @@ $deleted: #f77;
*/
$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;
/*
* 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 @@
}
.issue-meta {
margin-left: 65px
display: inline-block;
line-height: 20px;
}
}
......
......@@ -151,7 +151,6 @@
}
}
.right-sidebar {
position: fixed;
top: 58px;
......@@ -184,6 +183,13 @@
}
&.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;
padding-top: 0;
......@@ -247,6 +253,10 @@
}
}
.btn-default.gutter-toggle {
margin-top: 4px;
}
.detail-page-description {
small {
color: $gray-darkest;
......
......@@ -99,18 +99,17 @@ form.edit-issue {
.btn {
width: 100%;
margin-top: -1px;
&:first-child:not(:last-child) {
border-radius: 4px 4px 0 0;
}
&:not(:first-child):not(:last-child) {
border-radius: 0;
margin-top: 10px;
}
&:last-child:not(:first-child) {
border-radius: 0 0 4px 4px;
margin-top: 10px;
}
}
}
......@@ -134,3 +133,11 @@ form.edit-issue {
color: $secondary-text;
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 @@
}
}
.profile-settings-sidebar {
a {
color: $profile-settings-link-color;
}
}
.avatar-image {
@media (min-width: $screen-sm-min) {
float: left;
......@@ -24,6 +18,11 @@
display: inline-block;
}
.account-btn-link,
.profile-settings-sidebar a {
color: $profile-settings-link-color;
}
.oauth-buttons {
.btn-group {
margin-right: 10px;
......@@ -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 {
margin-top: -12px;
float: right;
......@@ -134,3 +145,33 @@
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 @@
}
}
.project-home-dropdown {
margin: 13px 0px 0;
}
.notifications-btn {
margin-top: -28px;
......@@ -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 {
.radio {
margin-bottom: 10px;
......
......@@ -31,7 +31,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
end
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.sort(@sort = params[:sort])
......
......@@ -23,6 +23,14 @@ class PasswordsController < Devise::PasswordsController
end
end
def update
super do |resource|
if resource.valid? && resource.require_password?
resource.update_attribute(:password_automatically_set, false)
end
end
end
protected
def resource_from_email
......
......@@ -3,23 +3,21 @@ class Profiles::KeysController < Profiles::ApplicationController
def index
@keys = current_user.keys
@key = Key.new
end
def show
@key = current_user.keys.find(params[:id])
end
def new
@key = current_user.keys.new
end
def create
@key = current_user.keys.new(key_params)
if @key.save
redirect_to profile_key_path(@key)
else
render 'new'
@keys = current_user.keys.select(&:persisted?)
render :index
end
end
......
class Projects::BadgesController < Projects::ApplicationController
before_action :set_no_cache
def build
respond_to do |format|
format.html { render_404 }
......@@ -8,4 +10,15 @@ class Projects::BadgesController < Projects::ApplicationController
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
......@@ -3,6 +3,7 @@
# Not to be confused with CommitsController, plural.
class Projects::CommitController < Projects::ApplicationController
include CreatesCommit
include DiffHelper
# Authorize
before_action :require_non_empty_project
......@@ -100,12 +101,10 @@ class Projects::CommitController < Projects::ApplicationController
def define_show_vars
return git_not_found! unless commit
if params[:w].to_i == 1
@diffs = commit.diffs({ ignore_whitespace_change: true })
else
@diffs = commit.diffs
end
opts = diff_options
opts[:ignore_whitespace_change] = true if params[:format] == 'diff'
@diffs = commit.diffs(opts)
@diff_refs = [commit.parent || commit, commit]
@notes_count = commit.notes.count
......
require 'addressable/uri'
class Projects::CompareController < Projects::ApplicationController
include DiffHelper
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!
......@@ -11,16 +13,14 @@ class Projects::CompareController < Projects::ApplicationController
end
def show
diff_options = { ignore_whitespace_change: true } if params[:w] == '1'
compare_result = CompareService.new.
compare = CompareService.new.
execute(@project, @head_ref, @project, @base_ref, diff_options)
if compare_result
@commits = Commit.decorate(compare_result.commits, @project)
@diffs = compare_result.diffs
if compare
@commits = Commit.decorate(compare.commits, @project)
@commit = @project.commit(@head_ref)
@base_commit = @project.merge_base_commit(@base_ref, @head_ref)
@diffs = compare.diffs(diff_options)
@diff_refs = [@base_commit, @commit]
@line_notes = []
end
......
class Projects::MergeRequestsController < Projects::ApplicationController
include DiffHelper
before_action :module_enabled
before_action :merge_request, only: [
:edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check,
......@@ -111,7 +113,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@commits = @merge_request.compare_commits.reverse
@commit = @merge_request.last_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
@statuses = @ci_commit.statuses if @ci_commit
......
......@@ -4,6 +4,7 @@ class SessionsController < Devise::SessionsController
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 :store_redirect_path, only: [:new]
before_action :gitlab_geo_login, only: [:new]
......@@ -34,6 +35,22 @@ class SessionsController < Devise::SessionsController
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
params.require(:user).permit(:login, :password, :remember_me, :otp_attempt)
end
......
......@@ -67,12 +67,8 @@ class UsersController < ApplicationController
end
def calendar_activities
@calendar_date = Date.parse(params[:date]) rescue nil
@events = []
if @calendar_date
@events = contributions_calendar.events_by_date(@calendar_date)
end
@calendar_date = Date.parse(params[:date]) rescue Date.today
@events = contributions_calendar.events_by_date(@calendar_date)
render 'calendar_activities', layout: false
end
......
......@@ -12,40 +12,20 @@ module DiffHelper
params[:view] == 'parallel' ? 'parallel' : 'inline'
end
def allowed_diff_size
if diff_hard_limit_enabled?
Commit::DIFF_HARD_LIMIT_FILES
else
Commit::DIFF_SAFE_FILES
end
def diff_hard_limit_enabled?
params[:force_show_diff].present?
end
def allowed_diff_lines
def diff_options
options = { ignore_whitespace_change: params[:w] == '1' }
if diff_hard_limit_enabled?
Commit::DIFF_HARD_LIMIT_LINES
else
Commit::DIFF_SAFE_LINES
options.merge!(Commit.max_diff_options)
end
options
end
def safe_diff_files(diffs, diff_refs)
lines = 0
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
diffs.decorate! { |diff| Gitlab::Diff::File.new(diff, diff_refs) }
end
def generate_line_code(file_path, line)
......
......@@ -50,6 +50,8 @@ module GitlabMarkdownHelper
context[:project] ||= @project
text = Banzai.pre_process(text, context)
html = Banzai.render(text, context)
context.merge!(
......
......@@ -50,19 +50,25 @@ module LabelsHelper
@project.labels.pluck(:title)
end
def render_colored_label(label)
def render_colored_label(label, label_suffix = '')
label_color = label.color || Label::DEFAULT_COLOR
text_color = text_color_for_bg(label_color)
# Intentionally not using content_tag here so that this method can be called
# by LabelReferenceFilter
span = %(<span class="label color-label") +
%( style="background-color: #{label_color}; color: #{text_color}">) +
escape_once(label.name) + '</span>'
%(style="background-color: #{label_color}; color: #{text_color}">) +
%(#{escape_once(label.name)}#{label_suffix}</span>)
span.html_safe
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
[
'#0033CC',
......@@ -119,5 +125,6 @@ module LabelsHelper
end
# 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
......@@ -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]
# 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
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
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
......
......@@ -202,6 +202,7 @@ class Ability
def project_dev_rules
@project_dev_rules ||= project_report_rules + [
:admin_merge_request,
:update_merge_request,
:create_commit_status,
:update_commit_status,
:create_build,
......@@ -227,7 +228,6 @@ class Ability
:read_pages,
:push_code_to_protected_branches,
:update_project_snippet,
:update_merge_request,
:update_pages,
:admin_milestone,
:admin_project_snippet,
......
......@@ -12,12 +12,7 @@ class Commit
attr_accessor :project
# Safe amount of changes (files and lines) in one commit to render
# 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)
DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines]
# Commits above this size will not be rendered in HTML
DIFF_HARD_LIMIT_FILES = 1000 unless defined?(DIFF_HARD_LIMIT_FILES)
......@@ -36,13 +31,20 @@ class Commit
# Calculate number of lines to render for 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
# Truncate sha to 8 characters
def truncate_sha(sha)
sha[0..7]
end
def max_diff_options
{
max_files: DIFF_HARD_LIMIT_FILES,
max_lines: DIFF_HARD_LIMIT_LINES,
}
end
end
attr_accessor :raw
......
class DiffLine
attr_accessor :type, :content, :num, :code
end
......@@ -48,10 +48,15 @@ class Label < ActiveRecord::Base
'~'
end
##
# Pattern used to extract label references from text
#
# This pattern supports cross-project references.
#
def self.reference_pattern
%r{
#{reference_prefix}
(#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}
(?:
(?<label_id>\d+) | # Integer-based label ID, or
(?<label_name>
......@@ -62,24 +67,31 @@ class Label < ActiveRecord::Base
}x
end
def self.link_reference_pattern
nil
end
##
# Returns the String necessary to reference this Label in Markdown
#
# 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:
#
# Label.first.to_reference # => "~1"
# Label.first.to_reference(:name) # => "~\"bug\""
# Label.first.to_reference # => "~1"
# Label.first.to_reference(format: :name) # => "~\"bug\""
# Label.first.to_reference(project) # => "gitlab-org/gitlab-ce~1"
#
# Returns a String
def to_reference(format = :id)
if format == :name && !name.include?('"')
%(#{self.class.reference_prefix}"#{name}")
#
def to_reference(from_project = nil, format: :id)
format_reference = label_format_reference(format)
reference = "#{self.class.reference_prefix}#{format_reference}"
if cross_project_reference?(from_project)
project.to_reference + reference
else
"#{self.class.reference_prefix}#{id}"
reference
end
end
......@@ -98,4 +110,16 @@ class Label < ActiveRecord::Base
def template?
template
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
......@@ -51,7 +51,7 @@ class MergeRequest < ActiveRecord::Base
after_create :create_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
# It allows us to close or modify broken merge requests
......@@ -59,8 +59,7 @@ class MergeRequest < ActiveRecord::Base
# Temporary fields to store compare vars
# when creating new merge request
attr_accessor :can_be_created, :compare_failed,
:compare_commits, :compare_diffs
attr_accessor :can_be_created, :compare_commits, :compare
state_machine :state, initial: :opened do
event :close do
......@@ -186,6 +185,10 @@ class MergeRequest < ActiveRecord::Base
merge_request_diff ? merge_request_diff.first_commit : compare_commits.first
end
def diff_size
merge_request_diff.size
end
def diff_base_commit
if merge_request_diff
merge_request_diff.base_commit
......@@ -543,6 +546,16 @@ class MergeRequest < ActiveRecord::Base
end
end
def state_icon_name
if merged?
"check"
elsif closed?
"times"
else
"circle-o"
end
end
def target_sha
@target_sha ||= target_project.repository.commit(target_branch).sha
end
......@@ -600,6 +613,29 @@ class MergeRequest < ActiveRecord::Base
File.exist?(rebase_dir_path)
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
@ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project
end
......
......@@ -19,14 +19,15 @@ class MergeRequestDiff < ActiveRecord::Base
# Prevent store of diff if commits amount more then 500
COMMITS_SAFE_SIZE = 500
attr_reader :commits, :diffs, :diffs_no_whitespace
belongs_to :merge_request
delegate :target_branch, :source_branch, to: :merge_request, prefix: nil
state_machine :state, initial: :empty do
state :collected
state :overflow
# Deprecated states: these are no longer used but these values may still occur
# in the database.
state :timeout
state :overflow_commits_safe_size
state :overflow_diff_files_limit
......@@ -43,19 +44,23 @@ class MergeRequestDiff < ActiveRecord::Base
reload_diffs
end
def diffs
@diffs ||= (load_diffs(st_diffs) || [])
def size
real_size.presence || diffs.size
end
def diffs_no_whitespace
compare_result = Gitlab::CompareResult.new(
Gitlab::Git::Compare.new(
self.repository.raw_repository,
self.target_branch,
self.source_sha,
), { ignore_whitespace_change: true }
)
@diffs_no_whitespace ||= load_diffs(dump_commits(compare_result.diffs))
def diffs(options={})
if options[:ignore_whitespace_change]
@diffs_no_whitespace ||= begin
compare = Gitlab::Git::Compare.new(
self.repository.raw_repository,
self.target_branch,
self.source_sha,
)
compare.diffs(options)
end
else
@diffs ||= load_diffs(st_diffs, options)
end
end
def commits
......@@ -94,16 +99,18 @@ class MergeRequestDiff < ActiveRecord::Base
end
end
def load_diffs(raw)
if raw.respond_to?(:map)
raw.map { |hash| Gitlab::Git::Diff.new(hash) }
def load_diffs(raw, options)
if raw.respond_to?(:each)
Gitlab::Git::DiffCollection.new(raw, options)
else
Gitlab::Git::DiffCollection.new([])
end
end
# Collect array of Git::Commit objects
# between target and source branches
def unmerged_commits
commits = compare_result.commits
commits = compare.commits
if commits.present?
commits = Commit.decorate(commits, merge_request.source_project).
......@@ -133,27 +140,21 @@ class MergeRequestDiff < ActiveRecord::Base
if commits.size.zero?
self.state = :empty
elsif commits.size > COMMITS_SAFE_SIZE
self.state = :overflow_commits_safe_size
else
new_diffs = unmerged_diffs
end
diff_collection = unmerged_diffs
if new_diffs.any?
if new_diffs.size > Commit::DIFF_HARD_LIMIT_FILES
self.state = :overflow_diff_files_limit
new_diffs = new_diffs.first(Commit::DIFF_HARD_LIMIT_LINES)
if diff_collection.overflow?
# Set our state to 'overflow' to make the #empty? and #collected?
# methods (generated by StateMachine) return false.
self.state = :overflow
end
if new_diffs.sum { |diff| diff.diff.lines.count } > Commit::DIFF_HARD_LIMIT_LINES
self.state = :overflow_diff_lines_limit
new_diffs = new_diffs.first(Commit::DIFF_HARD_LIMIT_LINES)
end
end
self.real_size = diff_collection.real_size
if new_diffs.present?
new_diffs = dump_commits(new_diffs)
self.state = :collected
if diff_collection.any?
new_diffs = dump_diffs(diff_collection)
self.state = :collected
end
end
self.st_diffs = new_diffs
......@@ -166,10 +167,7 @@ class MergeRequestDiff < ActiveRecord::Base
# Collect array of Git::Diff objects
# between target and source branches
def unmerged_diffs
compare_result.diffs || []
rescue Gitlab::Git::Diff::TimeoutError
self.state = :timeout
[]
compare.diffs(Commit.max_diff_options)
end
def repository
......@@ -181,18 +179,16 @@ class MergeRequestDiff < ActiveRecord::Base
source_commit.try(:sha)
end
def compare_result
@compare_result ||=
def compare
@compare ||=
begin
# Update ref for merge request
merge_request.fetch_ref
Gitlab::CompareResult.new(
Gitlab::Git::Compare.new(
self.repository.raw_repository,
self.target_branch,
self.source_sha
)
Gitlab::Git::Compare.new(
self.repository.raw_repository,
self.target_branch,
self.source_sha
)
end
end
......
......@@ -40,6 +40,7 @@ class Note < ActiveRecord::Base
has_many :todos, dependent: :destroy
delegate :gfm_reference, :local_reference, to: :noteable
delegate :name, to: :project, prefix: true
delegate :name, :email, to: :author, prefix: true
......@@ -89,7 +90,7 @@ class Note < ActiveRecord::Base
next if discussion_ids.include?(note.discussion_id)
# 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]
else
discussions << notes.select do |other_note|
......@@ -133,9 +134,11 @@ class Note < ActiveRecord::Base
end
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
end
end
......@@ -167,20 +170,16 @@ class Note < ActiveRecord::Base
def active?
return true unless self.diff
return false unless noteable
return @active if defined?(@active)
noteable.diffs.each do |mr_diff|
next unless mr_diff.new_path == self.diff.new_path
diffs = noteable.diffs(Commit.max_diff_options)
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|
if line.text == diff_line
return true
end
end
end
false
parsed_lines = Gitlab::Diff::Parser.new.parse(notable_diff.diff.each_line)
# We cannot use ||= because @active may be false
@active = parsed_lines.any? { |line_obj| line_obj.text == diff_line }
end
def outdated?
......@@ -265,7 +264,7 @@ class Note < ActiveRecord::Base
end
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
def highlighted_diff_lines
......@@ -317,20 +316,6 @@ class Note < ActiveRecord::Base
nil
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
# For more information visit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations
def noteable_type=(noteable_type)
......@@ -350,10 +335,6 @@ class Note < ActiveRecord::Base
Event.reset_event_cache_for(self)
end
def system?
read_attribute(:system)
end
def downvote?
is_award && note == "thumbsdown"
end
......@@ -387,7 +368,7 @@ class Note < ActiveRecord::Base
private
def awards_supported?
(noteable.kind_of?(Issue) || noteable.is_a?(MergeRequest)) && !for_diff_line?
(for_issue? || for_merge_request?) && !for_diff_line?
end
def contains_emoji_only?
......
......@@ -108,7 +108,8 @@ class JiraService < IssueTrackerService
},
entity: {
name: noteable_name.humanize.downcase,
url: entity_url
url: entity_url,
title: noteable.title
}
}
......@@ -196,10 +197,11 @@ class JiraService < IssueTrackerService
user_url = data[:user][:url]
entity_name = data[:entity][:name]
entity_url = data[:entity][:url]
entity_title = data[:entity][:title]
project_name = data[:project][:name]
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])
......
require 'securerandom'
# 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
def execute(source_project, source_branch, target_project, target_branch, diff_options = {})
source_commit = source_project.commit(source_branch)
......@@ -20,12 +20,10 @@ class CompareService
)
end
Gitlab::CompareResult.new(
Gitlab::Git::Compare.new(
target_project.repository.raw_repository,
target_branch,
source_sha,
), diff_options
Gitlab::Git::Compare.new(
target_project.repository.raw_repository,
target_branch,
source_sha,
)
end
end
......@@ -5,9 +5,7 @@ module MergeRequests
# Set MR attributes
merge_request.can_be_created = false
merge_request.compare_failed = false
merge_request.compare_commits = []
merge_request.compare_diffs = []
merge_request.source_project = project unless merge_request.source_project
merge_request.target_project ||= (project.forked_from_project || project)
merge_request.target_branch ||= merge_request.target_project.default_branch
......@@ -21,35 +19,23 @@ module MergeRequests
return build_failed(merge_request, message)
end
compare_result = CompareService.new.execute(
compare = CompareService.new.execute(
merge_request.source_project,
merge_request.source_branch,
merge_request.target_project,
merge_request.target_branch,
)
commits = compare_result.commits
commits = compare.commits
# At this point we decide if merge request can be created
# If we have at least one commit to merge -> creation allowed
if commits.present?
merge_request.compare_commits = Commit.decorate(commits, merge_request.source_project)
merge_request.can_be_created = true
merge_request.compare_failed = false
# 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
merge_request.compare = compare
else
merge_request.can_be_created = false
merge_request.compare_failed = false
end
commits = merge_request.compare_commits
......
......@@ -66,7 +66,7 @@ class SystemNoteService
def self.change_label(noteable, project, author, added_labels, removed_labels)
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(' ')
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 'profiles/keys/key_table', admin: true
......@@ -14,7 +14,7 @@
.nav-controls
= 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'
- if current_user.can_create_project?
= link_to new_project_path, class: 'btn btn-new' do
......
......@@ -6,7 +6,7 @@
- else
= sort_title_recently_updated
%b.caret
%ul.dropdown-menu
%ul.dropdown-menu.dropdown-menu-align-right
%li
= link_to explore_projects_filter_path(sort: sort_value_name) do
= sort_title_name
......
.search
= 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)
- if @project && @project.persisted?
= hidden_field_tag :project_id, @project.id
......
- page_title "Emails"
- header_title page_title, profile_emails_path
.alert.alert-help.prepend-top-default
%ul
%li
Your
%b Primary Email
will be used for avatar detection and web based operations, such as edits and merges.
%li
Your
%b Notification Email
will be used for account notifications.
%li
Your
%b Public Email
will be displayed on your public profile.
%li
All email addresses will be used to identify your commits.
.panel.panel-default
.panel-heading
Emails (#{@emails.count + 1})
%ul.well-list#emails-table
%li
%strong= @primary
%span.label.label-success Primary Email
- if @primary === current_user.public_email
%span.label.label-info Public Email
- if @primary === current_user.notification_email
%span.label.label-info Notification Email
- @emails.each do |email|
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
%h4.prepend-top-0
= page_title
%p
Control emails linked to your account
.col-lg-9
%h4.prepend-top-0
Add email address
= form_for 'email', url: profile_emails_path do |f|
.form-group
= f.label :email, class: 'label-light'
= f.text_field :email, class: 'form-control'
.prepend-top-default
= f.submit 'Add email address', class: 'btn btn-create'
%hr
%h4.prepend-top-0
Linked emails (#{@emails.count + 1})
.account-well.append-bottom-default
%ul
%li
Your Primary Email will be used for avatar detection and web based operations, such as edits and merges.
%li
Your Notification Email will be used for account notifications.
%li
Your Public Email will be displayed on your public profile.
%li
All email addresses will be used to identify your commits.
%ul.well-list
%li
%strong= email.email
- if email.email === current_user.public_email
%span.label.label-info Public Email
- if email.email === current_user.notification_email
%span.label.label-info Notification Email
%span.cgray
added #{time_ago_with_tooltip(email.created_at)}
= link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-remove pull-right'
%h4 Add email address
= form_for 'email', url: profile_emails_path, html: { class: 'form-horizontal' } do |f|
.form-group
= f.label :email, class: 'control-label'
.col-sm-10
= f.text_field :email, class: 'form-control'
.form-actions
= f.submit 'Add email address', class: 'btn btn-create'
= @primary
%span.pull-right
%span.label.label-success Primary Email
- if @primary === current_user.public_email
%span.label.label-info Public Email
- if @primary === current_user.notification_email
%span.label.label-info Notification Email
- @emails.each do |email|
%li
= email.email
%span.pull-right
- if email.email === current_user.public_email
%span.label.label-info Public Email
- if email.email === current_user.notification_email
%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'
%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?
.alert.alert-danger
%ul
......@@ -7,13 +7,11 @@
%li= msg
.form-group
= f.label :key, class: 'control-label'
.col-sm-10
= f.text_area :key, class: "form-control", rows: 8, autofocus: true, required: true
= f.label :key, class: 'label-light'
= f.text_area :key, class: "form-control", rows: 8, required: true
.form-group
= f.label :title, class: 'control-label'
.col-sm-10= f.text_field :title, class: "form-control", required: true
= f.label :title, class: 'label-light'
= f.text_field :title, class: "form-control", required: true
.form-actions
.prepend-top-default
= f.submit 'Add key', class: "btn btn-create"
= link_to "Cancel", profile_keys_path, class: "btn btn-cancel"
<<<<<<< HEAD
%tr
%td
= link_to path_to_key(key, is_admin) do
......@@ -10,3 +11,19 @@
%td
- 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"
=======
%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
.row
.row.prepend-top-default
.col-md-4
.panel.panel-default
.panel-heading
......
- is_admin = defined?(admin) ? true : false
- is_admin = local_assigns.fetch(:admin, false)
- if @keys.any?
.table-holder
%table.table
%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
%ul.well-list
= render partial: 'profiles/keys/key', collection: @keys, locals: { is_admin: is_admin }
- else
.nothing-here-block
%p.profile-settings-message.text-center
- if is_admin
User has no ssh keys
There are no SSH keys associated with this account.
- else
There are no SSH keys with access to your account.
- page_title "SSH Keys"
- header_title page_title, profile_keys_path
.top-area
.nav-text
Before you can add an SSH key you need to
= link_to "generate it.", help_page_path("ssh", "README")
.nav-controls
= link_to new_profile_key_path, class: "btn btn-new" do
= icon('plus')
Add SSH Key
.prepend-top-default
= render 'key_table'
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
%h4.prepend-top-0
= page_title
%p
SSH keys allow you to establish a secure connection between your computer and GitLab.
.col-lg-9
%h5.prepend-top-0
Add an SSH key
%p.profile-settings-content
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"
- header_title page_title, edit_profile_password_path
.alert.alert-help.prepend-top-default
- if @user.password_automatically_set?
Set your password.
- else
Change your password or recover your current one.
.update-password.prepend-top-default
= form_for @user, url: profile_password_path, method: :put, html: { class: 'form-horizontal' } do |f|
%div
%p.slead
- unless @user.password_automatically_set?
You must provide current password in order to change it.
%br
After a successful password update, you will be redirected to the login page where you can log in with your new password.
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
%h4.prepend-top-0
= page_title
%p
After a successful password update, you will be redirected to the login page where you can log in with your new password.
.col-lg-9
%h5.prepend-top-0
Change your password
- unless @user.password_automatically_set?
or recover your current one
= form_for @user, url: profile_password_path, method: :put, html: {class: "update-password"} do |f|
-if @user.errors.any?
.alert.alert-danger
%ul
......@@ -22,19 +20,16 @@
%li= msg
- unless @user.password_automatically_set?
.form-group
= f.label :current_password, class: 'control-label'
.col-sm-10
= f.password_field :current_password, required: true, class: 'form-control'
%div
= link_to "Forgot your password?", reset_profile_password_path, method: :put
.form-group
= f.label :password, 'New password', class: 'control-label'
.col-sm-10
= f.label :current_password, class: 'label-light'
= f.password_field :current_password, required: true, class: 'form-control'
%p.help-block
You must provide your current password in order to change it.
.form-group
= f.label :password, 'New password', class: 'label-light'
= f.password_field :password, required: true, class: 'form-control'
.form-group
= f.label :password_confirmation, class: 'control-label'
.col-sm-10
.form-group
= f.label :password_confirmation, class: 'label-light'
= f.password_field :password_confirmation, required: true, class: 'form-control'
.form-actions
= f.submit 'Save password', class: "btn btn-create"
.prepend-top-default.append-bottom-default
= 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 @@
= parallel_diff_btn
= render 'projects/diffs/stats', diff_files: diff_files
- if diff_files.count < diffs.size
= render 'projects/diffs/warning', diffs: diffs, shown_files_count: diff_files.count
- if diff_files.overflow?
= render 'projects/diffs/warning', diff_files: diff_files
.files
- diff_files.each_with_index do |diff_file, index|
......@@ -21,10 +21,3 @@
= render 'projects/diffs/file', i: index, project: project,
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 @@
%table.text-file.code.js-syntax-highlight{ class: too_big ? 'hide' : '' }
- 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|
- type = line.type
- last_line = line.new_pos
......
......@@ -14,5 +14,5 @@
= link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-sm"
%p
To preserve performance only
%strong #{shown_files_count} of #{diffs.size}
%strong #{diff_files.count} of #{diff_files.real_size}
files are displayed.
......@@ -5,8 +5,39 @@
= render "header_title"
.issue
.detail-page-header
.pull-right
.detail-page-header.issuable-header
.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)
= 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')
......@@ -19,19 +50,6 @@
= icon('pencil-square-o')
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
.detail-page-description.content-block
......
......@@ -33,23 +33,18 @@
%div= msg
- elsif @merge_request.source_branch.present? && @merge_request.target_branch.present?
- if @merge_request.compare_failed
.alert.alert-danger
%h4 Compare failed
%p We can't compare selected branches. It may be because of huge diff. Please try again or select different branches.
- else
.light-well.append-bottom-default
.center
%h4
There isn't anything to merge.
%p.slead
- if @merge_request.source_branch == @merge_request.target_branch
You'll need to use different branch names to get a valid comparison.
- else
%span.label-branch #{@merge_request.source_branch}
and
%span.label-branch #{@merge_request.target_branch}
are the same.
.light-well.append-bottom-default
.center
%h4
There isn't anything to merge.
%p.slead
- if @merge_request.source_branch == @merge_request.target_branch
You'll need to use different branch names to get a valid comparison.
- else
%span.label-branch #{@merge_request.source_branch}
and
%span.label-branch #{@merge_request.target_branch}
are the same.
.form-actions
......
......@@ -31,22 +31,18 @@
%li.diffs-tab.active
= link_to url_for(params), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
Changes
%span.badge= @diffs.size
%span.badge= @diffs.real_size
.tab-content
#commits.commits.tab-pane
= render "projects/merge_requests/show/commits"
#diffs.diffs.tab-pane.active
- if @diffs.present?
= render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @merge_request.diff_refs
- elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
- if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
.alert.alert-danger
%h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits.
%p To preserve performance the line changes are not shown.
- else
.alert.alert-danger
%h4 This comparison includes a huge diff.
%p To preserve performance the line changes are not shown.
= render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @merge_request.diff_refs
- if @ci_commit
#builds.builds.tab-pane
= render "projects/merge_requests/show/builds"
......
......@@ -34,6 +34,8 @@
%span into
= link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
= @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/widget/show.html.haml"
......@@ -62,7 +64,7 @@
%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
Changes
%span.badge= @merge_request.diffs.size
%span.badge= @merge_request.diff_size
.tab-content
#notes.notes.tab-pane.voting_notes
......
- 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
- elsif @merge_request_diff.empty?
.nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
......
.detail-page-header
.status-box{ class: status_box_class(@merge_request) }
= @merge_request.state_human_name
%span.identifier
Merge Request #{@merge_request.to_reference}
%span.creator
&middot;
by #{link_to_member(@project, @merge_request.author, size: 24)}
= '@' + @merge_request.author.username
&middot;
= time_ago_with_tooltip(@merge_request.created_at)
%span.hidden-xs
= @merge_request.state_human_name
%span.hidden-sm.hidden-md.hidden-lg
= icon(@merge_request.state_icon_name)
%a.btn.btn-default.pull-right.hidden-sm.hidden-md.hidden-lg.gutter-toggle{ href: "#" }
= icon('angle-double-left')
.issue-meta
%strong.identifier
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
- if can?(current_user, :update_merge_request, @merge_request)
......
......@@ -18,7 +18,9 @@
= render 'shared/projects/list', projects: @objects
- else
= render partial: "search/results/#{@scope.singularize}", collection: @objects
= paginate @objects, theme: 'gitlab'
- if @scope != 'projects'
= paginate @objects, theme: 'gitlab'
:javascript
$(".search-results .term").highlight("#{escape_javascript(params[:search])}");
......@@ -30,7 +30,7 @@
.line-numbers
- snippet_chunks.each do |chunk|
- 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
- i = index + offset
= 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?
.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
= 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
end
def files_count(commit)
files = "#{commit.diffs.count} file"
files = "#{commit.diffs.real_size} file"
files += 's' if commit.diffs.count > 1
files
end
......
......@@ -96,5 +96,9 @@ module Gitlab
# Gitlab Geo Middleware support
config.middleware.use 'Gitlab::Middleware::ReadonlyGeo'
config.generators do |g|
g.factory_girl false
end
end
end
......@@ -320,7 +320,7 @@ Rails.application.routes.draw do
end
end
resource :preferences, only: [:show, :update]
resources :keys
resources :keys, except: [:new]
resources :emails, only: [:index, :create, :destroy]
resource :avatar, only: [:destroy]
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?
password = '5iveL!fe'
expire_time = Time.now
user_args[:password_automatically_set] = true
user_args[:force_random_password] = true
else
password = ENV['GITLAB_ROOT_PASSWORD']
expire_time = nil
user_args[:password] = ENV['GITLAB_ROOT_PASSWORD']
end
email = ENV['GITLAB_ROOT_EMAIL'].presence || 'admin@example.com'
admin = User.create(
email: email,
name: "Administrator",
username: 'root',
password: password,
password_expires_at: expire_time,
theme_id: Gitlab::Themes::APPLICATION_DEFAULT
)
user = User.new(user_args)
user.skip_confirmation!
admin.projects_limit = 10000
admin.admin = true
admin.save!
admin.confirm
if user.save
puts "Administrator account created:".green
puts
puts "login: root".green
if admin.valid?
puts %Q[
Administrator account created:
if user_args.key?(:password)
puts "password: #{user_args[:password]}".green
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
password......#{password}
]
exit 1
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
t.datetime "created_at"
t.datetime "updated_at"
t.string "base_commit_sha"
t.string "real_size"
end
add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree
......
# Quick Start
Starting from version 8.0, GitLab Continuous Integration (CI) is fully
integrated into GitLab itself and is enabled by default on all projects.
>**Note:** Starting from version 8.0, GitLab [Continuous Integration][ci] (CI)
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 git repository and push to GitLab
1. Add `.gitlab-ci.yml` to the root directory of your repository
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
`/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
......@@ -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
[blog-ci]: https://about.gitlab.com/2015/05/06/why-were-replacing-gitlab-ci-jobs-with-gitlab-ci-dot-yml/
[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:
| `namespace/project$123` | snippet |
| `namespace/project@9ba12248` | specific commit |
| `namespace/project@9ba12248...b19a04f5` | commit range comparison |
| `namespace/project~"Some label"` | issues with given label |
## Task Lists
......
......@@ -9,7 +9,7 @@ Feature: Profile SSH Keys
Then I should see my ssh keys
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"
Then I should see new ssh key "Laptop"
......
......@@ -20,3 +20,8 @@ Feature: Project Badges Build
And project has another build that is running
When I display builds badge for a master branch
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
When I visit project "Shop" merge requests page
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
Given I click link "Closed"
Then I should see "Feature NS-03" in merge requests
......
......@@ -64,7 +64,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
page.within '.update-password' do
fill_in "user_password", with: "22233344"
fill_in "user_password_confirmation", with: "22233344"
click_button "Save"
click_button "Save password"
end
end
......@@ -73,7 +73,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
fill_in "user_current_password", with: "12345678"
fill_in "user_password", with: "22233344"
fill_in "user_password_confirmation", with: "22233344"
click_button "Save"
click_button "Save password"
end
end
......@@ -82,7 +82,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
fill_in "user_current_password", with: "12345678"
fill_in "user_password", with: "password"
fill_in "user_password_confirmation", with: "confirmation"
click_button "Save"
click_button "Save password"
end
end
......
......@@ -7,8 +7,8 @@ class Spinach::Features::ProfileSshKeys < Spinach::FeatureSteps
end
end
step 'I click link "Add new"' do
click_link "Add SSH Key"
step 'I should see new ssh key form' do
expect(page).to have_content("Add an SSH key")
end
step 'I submit new ssh key "Laptop"' do
......
......@@ -20,6 +20,10 @@ class Spinach::Features::ProjectBadgesBuild < Spinach::FeatureSteps
expect_badge('running')
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)
svg = Nokogiri::XML.parse(page.body)
expect(page.response_headers).to include('Content-Type' => 'image/svg+xml')
......
......@@ -126,8 +126,11 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'I visit big commit page' do
stub_const('Commit::DIFF_SAFE_FILES', 20)
visit namespace_project_commit_path(@project.namespace, @project, sample_big_commit.id)
# Create a temporary scope to ensure that the stub_const is removed after user
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
step 'I see big commit warning' do
......
......@@ -60,7 +60,6 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
expect(page).not_to have_content "Feature NS-03"
end
step 'I should not see "Bug NS-04" in merge requests' do
expect(page).not_to have_content "Bug NS-04"
end
......@@ -121,6 +120,22 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
author: project.users.first)
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
create(:closed_merge_request,
title: "Feature NS-03",
......@@ -615,6 +630,18 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
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
expect(page.find('ul.content-list.mr-list li.merge-request:first-child')).to have_content("Bug NS-05")
end
......
......@@ -29,7 +29,7 @@ class Spinach::Features::ProjectMergeRequestsAcceptance < Spinach::FeatureSteps
step 'There is an open Merge Request' do
@user = create(:user)
@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)
end
......
......@@ -36,7 +36,7 @@ class Spinach::Features::RevertMergeRequests < Spinach::FeatureSteps
step 'There is an open Merge Request' do
@user = create(:user)
@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)
end
......
......@@ -416,13 +416,19 @@ module SharedPaths
end
step 'I visit merge request page "Bug NS-04"' do
mr = MergeRequest.find_by(title: "Bug NS-04")
visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
visit merge_request_path("Bug NS-04")
end
step 'I visit merge request page "Bug NS-05"' do
mr = MergeRequest.find_by(title: "Bug NS-05")
visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
visit merge_request_path("Bug NS-05")
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
step 'I visit merge request page "Bug CO-01"' do
......@@ -531,6 +537,11 @@ module SharedPaths
Project.find_by!(name: 'Shop')
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
# ----------------------------------------
......
......@@ -48,7 +48,7 @@ module API
sha = params[:sha]
commit = user_project.commit(sha)
not_found! "Commit" unless commit
commit.diffs
commit.diffs.to_a
end
# Get a commit's comments
......@@ -90,9 +90,9 @@ module API
}
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]
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|
next unless line.new_pos == params[:line].to_i && line.type == params[:line_type]
......
......@@ -191,7 +191,7 @@ module API
class MergeRequestChanges < MergeRequest
expose :diffs, as: :changes, using: Entities::RepoDiff do |compare, _|
compare.diffs
compare.diffs(all_diffs: true).to_a
end
end
......@@ -313,11 +313,11 @@ module API
end
expose :diffs, using: Entities::RepoDiff do |compare, options|
compare.diffs
compare.diffs(all_diffs: true).to_a
end
expose :compare_timeout do |compare, options|
compare.timeout
compare.diffs.overflow?
end
expose :same, as: :compare_same_ref
......
......@@ -7,6 +7,10 @@ module Banzai
Renderer.render_result(text, context)
end
def self.pre_process(text, context)
Renderer.pre_process(text, context)
end
def self.post_process(html, context)
Renderer.post_process(html, context)
end
......
......@@ -94,6 +94,8 @@ module Banzai
object_link_filter(link, object_class.link_reference_pattern, link_text: text)
end
end
doc
end
# Replace references (like `!123` for merge requests) in text with links
......
......@@ -26,6 +26,10 @@ module Banzai
# * [[http://example.com/images/logo.png]]
# * [[http://example.com/images/logo.png|alt=Logo]]
#
# - Insert a Table of Contents list:
#
# * [[_TOC_]]
#
# Based on Gollum::Filter::Tags
#
# Context options:
......@@ -61,8 +65,6 @@ module Banzai
# before this one, it will be converted into `[[<em>TOC</em>]]`, so it
# needs special-case handling
if toc_tag?(node)
next unless result[:toc].present?
process_toc_tag(node)
else
content = node.content
......@@ -85,7 +87,7 @@ module Banzai
# Replace an entire `[[<em>TOC</em>]]` node with the result generated by
# TableOfContentsFilter
def process_toc_tag(node)
node.parent.parent.replace(result[:toc])
node.parent.parent.replace(result[:toc].presence || '')
end
# Process a single tag into its final HTML form.
......
module Banzai
module Filter
# HTML filter that replaces label references with links.
class LabelReferenceFilter < ReferenceFilter
# Public: Find label references in text
#
# 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
class LabelReferenceFilter < AbstractReferenceFilter
def self.object_class
Label
end
def self.referenced_by(node)
{ label: LazyReference.new(Label, node.attr("data-label")) }
def find_object(project, id)
project.labels.find(id)
end
def call
replace_text_nodes_matching(Label.reference_pattern) do |content|
label_link_filter(content)
end
replace_link_nodes_with_href(Label.reference_pattern) do |link, text|
label_link_filter(link, link_text: text)
def self.references_in(text, pattern = Label.reference_pattern)
text.gsub(pattern) do |match|
yield match, $~[:label_id].to_i, $~[:label_name], $~[:project], $~
end
end
# Replace label references in text with links to the label specified.
#
# text - String text to replace references in.
#
# Returns a String with label references replaced with links. All links
# 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
)
def references_in(text, pattern = Label.reference_pattern)
text.gsub(pattern) do |match|
project = project_from_ref($~[:project])
params = label_params($~[:label_id].to_i, $~[:label_name])
label = project.labels.find_by(params)
text = link_text || render_colored_label(label)
%(<a href="#{url}" #{data}
class="#{klass}">#{escape_once(text)}</a>)
if label
yield match, label.id, $~[:project], $~
else
match
end
end
end
def url_for_label(project, label)
def url_for_object(label, project)
h = Gitlab::Application.routes.url_helpers
h.namespace_project_issues_url( project.namespace, project, label_name: label.name,
only_path: context[:only_path])
h.namespace_project_issues_url(project.namespace, project, label_name: label.name,
only_path: context[:only_path])
end
def render_colored_label(label)
LabelsHelper.render_colored_label(label)
def object_link_text(object, matches)
if context[:project] == object.project
LabelsHelper.render_colored_label(object)
else
LabelsHelper.render_colored_cross_project_label(object)
end
end
# 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
Pipeline[context[:pipeline]].call(text, context)
end
def self.pre_process(text, context)
pipeline = Pipeline[:pre_process]
pipeline.to_html(text, context)
end
# Perform post-processing on an HTML String
#
# 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