Commit a4ffdf92 authored by Rémy Coutable's avatar Rémy Coutable

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

parents 49fa8495 be4cdd93
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.5.0 (unreleased) v 8.5.0 (unreleased)
- Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu)
- Cache various Repository methods to improve performance (Yorick Peterse) - Cache various Repository methods to improve performance (Yorick Peterse)
- Fix duplicated branch creation/deletion Web hooks/service notifications when using Web UI (Stan Hu) - Fix duplicated branch creation/deletion Web hooks/service notifications when using Web UI (Stan Hu)
- Ensure rake tasks that don't need a DB connection can be run without one - Ensure rake tasks that don't need a DB connection can be run without one
...@@ -17,6 +18,7 @@ v 8.5.0 (unreleased) ...@@ -17,6 +18,7 @@ v 8.5.0 (unreleased)
set it up set it up
- API: Added "merge_requests/:merge_request_id/closes_issues" (Gal Schlezinger) - API: Added "merge_requests/:merge_request_id/closes_issues" (Gal Schlezinger)
- Fix diff comments loaded by AJAX to load comment with diff in discussion tab - Fix diff comments loaded by AJAX to load comment with diff in discussion tab
- Fix relative links in other markup formats (Ben Boeckel)
- Whitelist raw "abbr" elements when parsing Markdown (Benedict Etzel) - Whitelist raw "abbr" elements when parsing Markdown (Benedict Etzel)
- Fix label links for a merge request pointing to issues list - Fix label links for a merge request pointing to issues list
- Don't vendor minified JS - Don't vendor minified JS
...@@ -32,6 +34,11 @@ v 8.5.0 (unreleased) ...@@ -32,6 +34,11 @@ v 8.5.0 (unreleased)
- Update the ExternalIssue regex pattern (Blake Hitchcock) - Update the ExternalIssue regex pattern (Blake Hitchcock)
- Remember user's inline/side-by-side diff view preference in a cookie (Kirill Katsnelson) - Remember user's inline/side-by-side diff view preference in a cookie (Kirill Katsnelson)
- Optimized performance of finding issues to be closed by a merge request - Optimized performance of finding issues to be closed by a merge request
- Add `avatar_url`, `description`, `git_ssh_url`, `git_http_url`, `path_with_namespace`
and `default_branch` in `project` in push, issue, merge-request and note webhooks data (Kirill Zaitsev)
- Deprecate the `ssh_url` in favor of `git_ssh_url` and `http_url` in favor of `git_http_url`
in `project` for push, issue, merge-request and note webhooks data (Kirill Zaitsev)
- Deprecate the `repository` key in push, issue, merge-request and note webhooks data, use `project` instead (Kirill Zaitsev)
- API: Expose MergeRequest#merge_status (Andrei Dziahel) - API: Expose MergeRequest#merge_status (Andrei Dziahel)
- Revert "Add IP check against DNSBLs at account sign-up" - Revert "Add IP check against DNSBLs at account sign-up"
- Actually use the `skip_merges` option in Repository#commits (Tony Chu) - Actually use the `skip_merges` option in Repository#commits (Tony Chu)
...@@ -47,12 +54,14 @@ v 8.5.0 (unreleased) ...@@ -47,12 +54,14 @@ v 8.5.0 (unreleased)
- Fixed logo animation on Safari (Roman Rott) - Fixed logo animation on Safari (Roman Rott)
- Hide remove source branch button when the MR is merged but new commits are pushed (Zeger-Jan van de Weg) - Hide remove source branch button when the MR is merged but new commits are pushed (Zeger-Jan van de Weg)
- In seach autocomplete show only groups and projects you are member of - In seach autocomplete show only groups and projects you are member of
- Don't process cross-reference notes from forks
- Fix: init.d script not working on OS X - Fix: init.d script not working on OS X
- Faster snippet search - Faster snippet search
- Title for milestones should be unique (Zeger-Jan van de Weg) - Title for milestones should be unique (Zeger-Jan van de Weg)
- Validate correctness of maximum attachment size application setting - Validate correctness of maximum attachment size application setting
- Replaces "Create merge request" link with one to the "Merge Request" when one exists - Replaces "Create merge request" link with one to the "Merge Request" when one exists
- Fix CI builds badge, add a new link to builds badge, deprecate the old one - Fix CI builds badge, add a new link to builds badge, deprecate the old one
- Fix broken link to project in build notification emails
v 8.4.4 v 8.4.4
- Update omniauth-saml gem to 1.4.2 - Update omniauth-saml gem to 1.4.2
......
...@@ -21,7 +21,7 @@ gem "pg", '~> 0.18.2', group: :postgres ...@@ -21,7 +21,7 @@ gem "pg", '~> 0.18.2', group: :postgres
gem 'devise', '~> 3.5.4' gem 'devise', '~> 3.5.4'
gem 'devise-async', '~> 0.9.0' gem 'devise-async', '~> 0.9.0'
gem 'doorkeeper', '~> 2.2.0' gem 'doorkeeper', '~> 2.2.0'
gem 'omniauth', '~> 1.2.2' gem 'omniauth', '~> 1.3.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6' gem 'omniauth-azure-oauth2', '~> 0.0.6'
gem 'omniauth-bitbucket', '~> 0.0.2' gem 'omniauth-bitbucket', '~> 0.0.2'
gem 'omniauth-cas3', '~> 1.1.2' gem 'omniauth-cas3', '~> 1.1.2'
......
...@@ -50,7 +50,7 @@ GEM ...@@ -50,7 +50,7 @@ GEM
after_commit_queue (1.3.0) after_commit_queue (1.3.0)
activerecord (>= 3.0) activerecord (>= 3.0)
akismet (2.0.0) akismet (2.0.0)
allocations (1.0.3) allocations (1.0.4)
annotate (2.6.10) annotate (2.6.10)
activerecord (>= 3.2, <= 4.3) activerecord (>= 3.2, <= 4.3)
rake (~> 10.4) rake (~> 10.4)
...@@ -491,9 +491,9 @@ GEM ...@@ -491,9 +491,9 @@ GEM
rack (~> 1.2) rack (~> 1.2)
octokit (3.8.0) octokit (3.8.0)
sawyer (~> 0.6.0, >= 0.5.3) sawyer (~> 0.6.0, >= 0.5.3)
omniauth (1.2.2) omniauth (1.3.1)
hashie (>= 1.2, < 4) hashie (>= 1.2, < 4)
rack (~> 1.0) rack (>= 1.0, < 3)
omniauth-azure-oauth2 (0.0.6) omniauth-azure-oauth2 (0.0.6)
jwt (~> 1.0) jwt (~> 1.0)
omniauth (~> 1.0) omniauth (~> 1.0)
...@@ -963,7 +963,7 @@ DEPENDENCIES ...@@ -963,7 +963,7 @@ DEPENDENCIES
nprogress-rails (~> 0.1.6.7) nprogress-rails (~> 0.1.6.7)
oauth2 (~> 1.0.0) oauth2 (~> 1.0.0)
octokit (~> 3.8.0) octokit (~> 3.8.0)
omniauth (~> 1.2.2) omniauth (~> 1.3.1)
omniauth-azure-oauth2 (~> 0.0.6) omniauth-azure-oauth2 (~> 0.0.6)
omniauth-bitbucket (~> 0.0.2) omniauth-bitbucket (~> 0.0.2)
omniauth-cas3 (~> 1.1.2) omniauth-cas3 (~> 1.1.2)
......
...@@ -49,10 +49,11 @@ class @AwardsHandler ...@@ -49,10 +49,11 @@ class @AwardsHandler
counter.text(parseInt(counter.text()) - 1) counter.text(parseInt(counter.text()) - 1)
emojiIcon.removeClass("active") emojiIcon.removeClass("active")
@removeMeFromAuthorList(emoji) @removeMeFromAuthorList(emoji)
else if emoji =="thumbsup" || emoji == "thumbsdown" else if emoji == "thumbsup" || emoji == "thumbsdown"
emojiIcon.tooltip("destroy") emojiIcon.tooltip("destroy")
counter.text(0) counter.text(0)
emojiIcon.removeClass("active") emojiIcon.removeClass("active")
@removeMeFromAuthorList(emoji)
else else
emojiIcon.tooltip("destroy") emojiIcon.tooltip("destroy")
emojiIcon.remove() emojiIcon.remove()
......
...@@ -118,3 +118,19 @@ body { ...@@ -118,3 +118,19 @@ body {
@include gitlab-theme(#9988CC, $theme-violet, #443366, #332255); @include gitlab-theme(#9988CC, $theme-violet, #443366, #332255);
} }
} }
::-webkit-scrollbar{
width: 3px;
}
::-webkit-scrollbar-thumb{
background-color:$theme-charcoal; border-radius: 0;
}
::-webkit-scrollbar-thumb:hover{
background-color:$theme-charcoal;
}
::-webkit-scrollbar-track{
background-color:#FFF;
}
\ No newline at end of file
...@@ -12,6 +12,14 @@ ...@@ -12,6 +12,14 @@
.identifier { .identifier {
color: #5c5d5e; color: #5c5d5e;
} }
.issue_created_ago, .author_link {
white-space: nowrap;
}
.issue-meta {
margin-left: 65px
}
} }
.detail-page-description { .detail-page-description {
......
...@@ -80,6 +80,10 @@ ...@@ -80,6 +80,10 @@
display: inline-block; display: inline-block;
} }
.select2-container span {
margin-top: 0;
}
.issuable-count { .issuable-count {
} }
...@@ -88,6 +92,10 @@ ...@@ -88,6 +92,10 @@
margin-left: 20px; margin-left: 20px;
border-left: 1px solid $border-gray-light; border-left: 1px solid $border-gray-light;
padding-left: 10px; padding-left: 10px;
&:hover {
color: $gray-darkest;
}
} }
} }
...@@ -192,6 +200,10 @@ ...@@ -192,6 +200,10 @@
.btn { .btn {
background: $gray-normal; background: $gray-normal;
border: 1px solid $border-gray-normal; border: 1px solid $border-gray-normal;
&:hover {
background: $gray-dark;
border: 1px solid $border-gray-dark;
}
} }
&.right-sidebar-collapsed { &.right-sidebar-collapsed {
...@@ -223,6 +235,19 @@ ...@@ -223,6 +235,19 @@
display: block; display: block;
margin-top: 0; margin-top: 0;
} }
.btn-clipboard {
border: none;
&:hover {
background: transparent;
}
i {
color: #999999;
}
}
} }
} }
......
...@@ -164,7 +164,7 @@ class ApplicationController < ActionController::Base ...@@ -164,7 +164,7 @@ class ApplicationController < ActionController::Base
end end
def git_not_found! def git_not_found!
render html: "errors/git_not_found", layout: "errors", status: 404 render "errors/git_not_found.html", layout: "errors", status: 404
end end
def method_missing(method_sym, *arguments, &block) def method_missing(method_sym, *arguments, &block)
......
...@@ -11,8 +11,6 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -11,8 +11,6 @@ class Projects::CommitController < Projects::ApplicationController
before_action :define_show_vars, only: [:show, :builds] before_action :define_show_vars, only: [:show, :builds]
def show def show
return git_not_found! unless @commit
apply_diff_view_cookie! apply_diff_view_cookie!
@line_notes = commit.notes.inline @line_notes = commit.notes.inline
...@@ -68,6 +66,8 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -68,6 +66,8 @@ class Projects::CommitController < Projects::ApplicationController
end end
def define_show_vars def define_show_vars
return git_not_found! unless commit
if params[:w].to_i == 1 if params[:w].to_i == 1
@diffs = commit.diffs({ ignore_whitespace_change: true }) @diffs = commit.diffs({ ignore_whitespace_change: true })
else else
......
...@@ -3,6 +3,7 @@ class Projects::ImportsController < Projects::ApplicationController ...@@ -3,6 +3,7 @@ class Projects::ImportsController < Projects::ApplicationController
before_action :authorize_admin_project! before_action :authorize_admin_project!
before_action :require_no_repo, only: [:new, :create] before_action :require_no_repo, only: [:new, :create]
before_action :redirect_if_progress, only: [:new, :create] before_action :redirect_if_progress, only: [:new, :create]
before_action :redirect_if_no_import, only: :show
def new def new
end end
...@@ -63,14 +64,19 @@ class Projects::ImportsController < Projects::ApplicationController ...@@ -63,14 +64,19 @@ class Projects::ImportsController < Projects::ApplicationController
def require_no_repo def require_no_repo
if @project.repository_exists? if @project.repository_exists?
redirect_to(namespace_project_path(@project.namespace, @project)) redirect_to namespace_project_path(@project.namespace, @project)
end end
end end
def redirect_if_progress def redirect_if_progress
if @project.import_in_progress? if @project.import_in_progress?
redirect_to namespace_project_import_path(@project.namespace, @project) && redirect_to namespace_project_import_path(@project.namespace, @project)
return end
end
def redirect_if_no_import
if @project.repository_exists? && @project.no_import?
redirect_to namespace_project_path(@project.namespace, @project)
end end
end end
end end
...@@ -212,8 +212,7 @@ module ApplicationHelper ...@@ -212,8 +212,7 @@ module ApplicationHelper
file_content file_content
end end
else else
GitHub::Markup.render(file_name, file_content). other_markup(file_name, file_content)
force_encoding(file_content.encoding).html_safe
end end
rescue RuntimeError rescue RuntimeError
simple_format(file_content) simple_format(file_content)
...@@ -281,76 +280,6 @@ module ApplicationHelper ...@@ -281,76 +280,6 @@ module ApplicationHelper
end end
end end
def issuable_link_next(project,issuable)
if project.nil?
nil
elsif current_controller?(:issues)
namespace_project_issue_path(project.namespace, project, next_issuable_for(project, issuable.id).try(:iid))
elsif current_controller?(:merge_requests)
namespace_project_merge_request_path(project.namespace, project, next_issuable_for(project, issuable.id).try(:iid))
end
end
def issuable_link_prev(project,issuable)
if project.nil?
nil
elsif current_controller?(:issues)
namespace_project_issue_path(project.namespace, project, prev_issuable_for(project, issuable.id).try(:iid))
elsif current_controller?(:merge_requests)
namespace_project_merge_request_path(project.namespace, project, prev_issuable_for(project, issuable.id).try(:iid))
end
end
def issuable_count(entity, project)
if project.nil?
0
elsif current_controller?(:issues)
project.issues.send(entity).count
elsif current_controller?(:merge_requests)
project.merge_requests.send(entity).count
end
end
def next_issuable_for(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id > ?", id).last
elsif current_controller?(:merge_requests)
project.merge_requests.where("id > ?", id).last
end
end
def has_next_issuable?(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id > ?", id).last
elsif current_controller?(:merge_requests)
project.merge_requests.where("id > ?", id).last
end
end
def prev_issuable_for(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id < ?", id).first
elsif current_controller?(:merge_requests)
project.merge_requests.where("id < ?", id).first
end
end
def has_prev_issuable?(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id < ?", id).first
elsif current_controller?(:merge_requests)
project.merge_requests.where("id < ?", id).first
end
end
def state_filters_text_for(entity, project) def state_filters_text_for(entity, project)
titles = { titles = {
opened: "Open" opened: "Open"
......
...@@ -69,7 +69,7 @@ module DiffHelper ...@@ -69,7 +69,7 @@ module DiffHelper
end end
def line_comments def line_comments
@line_comments ||= @line_notes.select(&:active?).group_by(&:line_code) @line_comments ||= @line_notes.select(&:active?).sort_by(&:created_at).group_by(&:line_code)
end end
def organize_comments(type_left, type_right, line_code_left, line_code_right) def organize_comments(type_left, type_right, line_code_left, line_code_right)
......
...@@ -78,6 +78,21 @@ module GitlabMarkdownHelper ...@@ -78,6 +78,21 @@ module GitlabMarkdownHelper
) )
end end
def other_markup(file_name, text)
Gitlab::OtherMarkup.render(
file_name,
text,
project: @project,
current_user: (current_user if defined?(current_user)),
# RelativeLinkFilter
project_wiki: @project_wiki,
requested_path: @path,
ref: @ref,
commit: @commit
)
end
# Return the first line of +text+, up to +max_chars+, after parsing the line # Return the first line of +text+, up to +max_chars+, after parsing the line
# as Markdown. HTML tags in the parsed output are not counted toward the # as Markdown. HTML tags in the parsed output are not counted toward the
# +max_chars+ limit. If the length limit falls within a tag's contents, then # +max_chars+ limit. If the length limit falls within a tag's contents, then
......
module IssuablesHelper
def sidebar_gutter_toggle_icon
sidebar_gutter_collapsed? ? icon('angle-double-left') : icon('angle-double-right')
end
def sidebar_gutter_collapsed_class
"right-sidebar-#{sidebar_gutter_collapsed? ? 'collapsed' : 'expanded'}"
end
def issuables_count(issuable)
base_issuable_scope(issuable).maximum(:iid)
end
def next_issuable_for(issuable)
base_issuable_scope(issuable).where('iid > ?', issuable.iid).last
end
def prev_issuable_for(issuable)
base_issuable_scope(issuable).where('iid < ?', issuable.iid).first
end
private
def sidebar_gutter_collapsed?
cookies[:collapsed_gutter] == 'true'
end
def base_issuable_scope(issuable)
issuable.project.send(issuable.class.table_name).send(issuable_state_scope(issuable))
end
def issuable_state_scope(issuable)
issuable.open? ? :opened : :closed
end
end
...@@ -3,18 +3,6 @@ module NavHelper ...@@ -3,18 +3,6 @@ module NavHelper
cookies[:collapsed_nav] == 'true' cookies[:collapsed_nav] == 'true'
end end
def sidebar_gutter_collapsed_class
if cookies[:collapsed_gutter] == 'true'
"right-sidebar-collapsed"
else
"right-sidebar-expanded"
end
end
def sidebar_gutter_collapsed?
cookies[:collapsed_gutter] == 'true'
end
def nav_sidebar_class def nav_sidebar_class
if nav_menu_collapsed? if nav_menu_collapsed?
"sidebar-collapsed" "sidebar-collapsed"
...@@ -32,9 +20,9 @@ module NavHelper ...@@ -32,9 +20,9 @@ module NavHelper
end end
def page_gutter_class def page_gutter_class
if current_path?('merge_requests#show') || if current_path?('merge_requests#show') ||
current_path?('merge_requests#diffs') || current_path?('merge_requests#diffs') ||
current_path?('merge_requests#commits') || current_path?('merge_requests#commits') ||
current_path?('issues#show') current_path?('issues#show')
if cookies[:collapsed_gutter] == 'true' if cookies[:collapsed_gutter] == 'true'
"page-gutter right-sidebar-collapsed" "page-gutter right-sidebar-collapsed"
......
...@@ -3,26 +3,27 @@ module Emails ...@@ -3,26 +3,27 @@ module Emails
def build_fail_email(build_id, to) def build_fail_email(build_id, to)
@build = Ci::Build.find(build_id) @build = Ci::Build.find(build_id)
@project = @build.project @project = @build.project
add_project_headers add_project_headers
add_build_headers add_build_headers('failed')
headers['X-GitLab-Build-Status'] = "failed"
mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha)) mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha))
end end
def build_success_email(build_id, to) def build_success_email(build_id, to)
@build = Ci::Build.find(build_id) @build = Ci::Build.find(build_id)
@project = @build.project @project = @build.project
add_project_headers add_project_headers
add_build_headers add_build_headers('success')
headers['X-GitLab-Build-Status'] = "success"
mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha)) mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha))
end end
private private
def add_build_headers
def add_build_headers(status)
headers['X-GitLab-Build-Id'] = @build.id headers['X-GitLab-Build-Id'] = @build.id
headers['X-GitLab-Build-Ref'] = @build.ref headers['X-GitLab-Build-Ref'] = @build.ref
headers['X-GitLab-Build-Status'] = status.to_s
end end
end end
end end
...@@ -129,13 +129,10 @@ module Issuable ...@@ -129,13 +129,10 @@ module Issuable
hook_data = { hook_data = {
object_kind: self.class.name.underscore, object_kind: self.class.name.underscore,
user: user.hook_attrs, user: user.hook_attrs,
repository: { project: project.hook_attrs,
name: project.name, object_attributes: hook_attrs,
url: project.url_to_repo, # DEPRECATED
description: project.description, repository: project.hook_attrs.slice(:name, :url, :description, :homepage)
homepage: project.web_url
},
object_attributes: hook_attrs
} }
hook_data.merge!(assignee: assignee.hook_attrs) if assignee hook_data.merge!(assignee: assignee.hook_attrs) if assignee
......
...@@ -342,7 +342,7 @@ class Project < ActiveRecord::Base ...@@ -342,7 +342,7 @@ class Project < ActiveRecord::Base
end end
def repository def repository
@repository ||= Repository.new(path_with_namespace, nil, self) @repository ||= Repository.new(path_with_namespace, self)
end end
def commit(id = 'HEAD') def commit(id = 'HEAD')
...@@ -382,6 +382,10 @@ class Project < ActiveRecord::Base ...@@ -382,6 +382,10 @@ class Project < ActiveRecord::Base
external_import? || forked? external_import? || forked?
end end
def no_import?
import_status == 'none'
end
def external_import? def external_import?
import_url.present? import_url.present?
end end
...@@ -738,11 +742,20 @@ class Project < ActiveRecord::Base ...@@ -738,11 +742,20 @@ class Project < ActiveRecord::Base
def hook_attrs def hook_attrs
{ {
name: name, name: name,
ssh_url: ssh_url_to_repo, description: description,
http_url: http_url_to_repo,
web_url: web_url, web_url: web_url,
avatar_url: avatar_url,
git_ssh_url: ssh_url_to_repo,
git_http_url: http_url_to_repo,
namespace: namespace.name, namespace: namespace.name,
visibility_level: visibility_level visibility_level: visibility_level,
path_with_namespace: path_with_namespace,
default_branch: default_branch,
# Backward compatibility
homepage: web_url,
url: url_to_repo,
ssh_url: ssh_url_to_repo,
http_url: http_url_to_repo
} }
end end
......
...@@ -112,7 +112,7 @@ class PushoverService < Service ...@@ -112,7 +112,7 @@ class PushoverService < Service
priority: priority, priority: priority,
title: "#{project.name_with_namespace}", title: "#{project.name_with_namespace}",
message: message, message: message,
url: data[:repository][:homepage], url: data[:project][:web_url],
url_title: "See project #{project.name_with_namespace}" url_title: "See project #{project.name_with_namespace}"
} }
......
...@@ -123,7 +123,7 @@ class ProjectWiki ...@@ -123,7 +123,7 @@ class ProjectWiki
end end
def repository def repository
Repository.new(path_with_namespace, default_branch, @project) Repository.new(path_with_namespace, @project)
end end
def default_branch def default_branch
......
...@@ -15,7 +15,7 @@ class Repository ...@@ -15,7 +15,7 @@ class Repository
Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete)) Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
end end
def initialize(path_with_namespace, default_branch = nil, project = nil) def initialize(path_with_namespace, project)
@path_with_namespace = path_with_namespace @path_with_namespace = path_with_namespace
@project = project @project = project
end end
...@@ -238,6 +238,15 @@ class Repository ...@@ -238,6 +238,15 @@ class Repository
expire_branch_cache(branch_name) expire_branch_cache(branch_name)
end end
# Expires _all_ caches, including those that would normally only be expired
# under specific conditions.
def expire_all_caches!
expire_cache
expire_root_ref_cache
expire_emptiness_caches
expire_has_visible_content_cache
end
def expire_branch_cache(branch_name = nil) def expire_branch_cache(branch_name = nil)
# When we push to the root branch we have to flush the cache for all other # When we push to the root branch we have to flush the cache for all other
# branches as their statistics are based on the commits relative to the # branches as their statistics are based on the commits relative to the
...@@ -258,6 +267,14 @@ class Repository ...@@ -258,6 +267,14 @@ class Repository
@root_ref = nil @root_ref = nil
end end
# Expires the cache(s) used to determine if a repository is empty or not.
def expire_emptiness_caches
cache.expire(:empty?)
@empty = nil
expire_has_visible_content_cache
end
def expire_has_visible_content_cache def expire_has_visible_content_cache
cache.expire(:has_visible_content?) cache.expire(:has_visible_content?)
@has_visible_content = nil @has_visible_content = nil
...@@ -611,6 +628,8 @@ class Repository ...@@ -611,6 +628,8 @@ class Repository
end end
def merge_base(first_commit_id, second_commit_id) def merge_base(first_commit_id, second_commit_id)
first_commit_id = commit(first_commit_id).try(:id) || first_commit_id
second_commit_id = commit(second_commit_id).try(:id) || second_commit_id
rugged.merge_base(first_commit_id, second_commit_id) rugged.merge_base(first_commit_id, second_commit_id)
rescue Rugged::ReferenceError rescue Rugged::ReferenceError
nil nil
......
...@@ -16,11 +16,15 @@ module Projects ...@@ -16,11 +16,15 @@ module Projects
return false unless can?(current_user, :remove_project, project) return false unless can?(current_user, :remove_project, project)
project.team.truncate project.team.truncate
project.repository.expire_cache unless project.empty_repo?
repo_path = project.path_with_namespace repo_path = project.path_with_namespace
wiki_path = repo_path + '.wiki' wiki_path = repo_path + '.wiki'
# Flush the cache for both repositories. This has to be done _before_
# removing the physical repositories as some expiration code depends on
# Git data (e.g. a list of branch names).
flush_caches(project, wiki_path)
Project.transaction do Project.transaction do
project.destroy! project.destroy!
...@@ -70,5 +74,13 @@ module Projects ...@@ -70,5 +74,13 @@ module Projects
def removal_path(path) def removal_path(path)
"#{path}+#{project.id}#{DELETED_FLAG}" "#{path}+#{project.id}#{DELETED_FLAG}"
end end
def flush_caches(project, wiki_path)
project.repository.expire_all_caches! if project.repository.exists?
wiki_repo = Repository.new(wiki_path, project)
wiki_repo.expire_all_caches! if wiki_repo.exists?
end
end end
end end
...@@ -274,12 +274,15 @@ class SystemNoteService ...@@ -274,12 +274,15 @@ class SystemNoteService
# Check if a cross reference to a noteable from a mentioner already exists # Check if a cross reference to a noteable from a mentioner already exists
# #
# This method is used to prevent multiple notes being created for a mention # This method is used to prevent multiple notes being created for a mention
# when a issue is updated, for example. # when a issue is updated, for example. The method also calls notes_for_mentioner
# to check if the mentioner is a commit, and return matches only on commit hash
# instead of project + commit, to avoid repeated mentions from forks.
# #
# noteable - Noteable object being referenced # noteable - Noteable object being referenced
# mentioner - Mentionable object # mentioner - Mentionable object
# #
# Returns Boolean # Returns Boolean
def self.cross_reference_exists?(noteable, mentioner) def self.cross_reference_exists?(noteable, mentioner)
# Initial scope should be system notes of this noteable type # Initial scope should be system notes of this noteable type
notes = Note.system.where(noteable_type: noteable.class) notes = Note.system.where(noteable_type: noteable.class)
...@@ -291,14 +294,20 @@ class SystemNoteService ...@@ -291,14 +294,20 @@ class SystemNoteService
notes = notes.where(noteable_id: noteable.id) notes = notes.where(noteable_id: noteable.id)
end end
gfm_reference = mentioner.gfm_reference(noteable.project) notes_for_mentioner(mentioner, noteable, notes).count > 0
notes = notes.where(note: cross_reference_note_content(gfm_reference))
notes.count > 0
end end
private private
def self.notes_for_mentioner(mentioner, noteable, notes)
if mentioner.is_a?(Commit)
notes.where('note LIKE ?', "#{cross_reference_note_prefix}%#{mentioner.to_reference(nil)}")
else
gfm_reference = mentioner.gfm_reference(noteable.project)
notes.where(note: cross_reference_note_content(gfm_reference))
end
end
def self.create_note(args = {}) def self.create_note(args = {})
Note.create(args.merge(system: true)) Note.create(args.merge(system: true))
end end
......
- content_for :header do - content_for :header do
%h1{style: "background: #c40834; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"} %h1{style: "background: #c40834; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"}
GitLab (build failed) GitLab (build failed)
%h3 %h3
Project: Project:
= link_to ci_project_url(@project) do = link_to namespace_project_url(@project.namespace, @project) do
= @project.name = @project.name
%p %p
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
%h3 %h3
Project: Project:
= link_to ci_project_url(@project) do = link_to namespace_project_url(@project.namespace, @project) do
= @project.name = @project.name
%p %p
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
= render "download", blob: blob = render "download", blob: blob
- elsif blob.text? - elsif blob.text?
- if blob_svg?(blob) - if blob_svg?(blob)
= render "image", blob: sanitize_svg(blob) = render "image", blob: blob
- else - else
= render "text", blob: blob = render "text", blob: blob
- elsif blob.image? - elsif blob.image?
......
.file-content.image_file .file-content.image_file
%img{ src: namespace_project_raw_path(@project.namespace, @project, @id)} - if blob_svg?(blob)
- # We need to scrub SVG but we cannot do so in the RawController: it would
- # be wrong/strange if RawController modified the data.
- blob.load_all_data!(@repository)
- blob = sanitize_svg(blob)
%img{src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}"}
- else
%img{src: namespace_project_raw_path(@project.namespace, @project, @id)}
- diff = diff_file.diff - diff = diff_file.diff
- file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path))
- old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.parent_id, diff.old_path))
- if diff.renamed_file || diff.new_file || diff.deleted_file - if diff.renamed_file || diff.new_file || diff.deleted_file
.image .image
%span.wrap %span.wrap
.frame{class: image_diff_class(diff)} .frame{class: image_diff_class(diff)}
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} %img{src: diff.deleted_file ? old_file_raw_path : file_raw_path}
%p.image-info= "#{number_to_human_size file.size}" %p.image-info= "#{number_to_human_size file.size}"
- else - else
.image .image
...@@ -11,7 +13,7 @@ ...@@ -11,7 +13,7 @@
%span.wrap %span.wrap
.frame.deleted .frame.deleted
%a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(@commit.parent_id, diff.old_path))} %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(@commit.parent_id, diff.old_path))}
%img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"} %img{src: old_file_raw_path}
%p.image-info.hide %p.image-info.hide
%span.meta-filesize= "#{number_to_human_size old_file.size}" %span.meta-filesize= "#{number_to_human_size old_file.size}"
| |
...@@ -23,7 +25,7 @@ ...@@ -23,7 +25,7 @@
%span.wrap %span.wrap
.frame.added .frame.added
%a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path))} %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path))}
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} %img{src: file_raw_path}
%p.image-info.hide %p.image-info.hide
%span.meta-filesize= "#{number_to_human_size file.size}" %span.meta-filesize= "#{number_to_human_size file.size}"
| |
...@@ -36,10 +38,10 @@ ...@@ -36,10 +38,10 @@
%div.swipe.view.hide %div.swipe.view.hide
.swipe-frame .swipe-frame
.frame.deleted .frame.deleted
%img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"} %img{src: old_file_raw_path}
.swipe-wrap .swipe-wrap
.frame.added .frame.added
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} %img{src: file_raw_path}
%span.swipe-bar %span.swipe-bar
%span.top-handle %span.top-handle
%span.bottom-handle %span.bottom-handle
...@@ -47,9 +49,9 @@ ...@@ -47,9 +49,9 @@
%div.onion-skin.view.hide %div.onion-skin.view.hide
.onion-skin-frame .onion-skin-frame
.frame.deleted .frame.deleted
%img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"} %img{src: old_file_raw_path}
.frame.added .frame.added
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} %img{src: file_raw_path}
.controls .controls
.transparent .transparent
.drag-track .drag-track
......
...@@ -6,16 +6,6 @@ ...@@ -6,16 +6,6 @@
.issue .issue
.detail-page-header .detail-page-header
.status-box{ class: "status-box-closed #{issue_button_visibility(@issue, false)}"} Closed
.status-box{ class: "status-box-open #{issue_button_visibility(@issue, true)}"} Open
%span.identifier
Issue ##{@issue.iid}
%span.creator
&middot;
opened by #{link_to_member(@project, @issue.author, size: 24)}
&middot;
= time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago')
.pull-right .pull-right
- 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
...@@ -29,6 +19,19 @@ ...@@ -29,6 +19,19 @@
= 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)}
&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
%h2.title %h2.title
......
$('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}"; $('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}";
$('aside.right-sidebar').effect('highlight'); $('aside.right-sidebar').effect('highlight');
new Issue(); new IssuableContext();
\ No newline at end of file
$('aside.right-sidebar')[0].outerHTML= "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}"; $('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}";
$('aside.right-sidebar').effect('highlight') $('aside.right-sidebar').effect('highlight');
merge_request = new MergeRequest(); new IssuableContext();
...@@ -4,21 +4,18 @@ ...@@ -4,21 +4,18 @@
%span.issuable-count.pull-left %span.issuable-count.pull-left
= issuable.iid = issuable.iid
of of
= issuable_count(:all, @project) = issuables_count(issuable)
%span.pull-right %span.pull-right
%a.gutter-toggle{href: '#'} %a.gutter-toggle{href: '#'}
- if sidebar_gutter_collapsed? = sidebar_gutter_toggle_icon
= icon('angle-double-left')
- else
= icon('angle-double-right')
.issuable-nav.pull-right.btn-group{role: 'group', "aria-label" => '...'} .issuable-nav.pull-right.btn-group{role: 'group', "aria-label" => '...'}
- if has_prev_issuable?(@project, issuable.id) - if prev_issuable = prev_issuable_for(issuable)
= link_to 'Prev', issuable_link_prev(@project, issuable), class: 'btn btn-default prev-btn' = link_to 'Prev', [@project.namespace.becomes(Namespace), @project, prev_issuable], class: 'btn btn-default prev-btn'
- else - else
%a.btn.btn-default.disabled{href: '#'} %a.btn.btn-default.disabled{href: '#'}
Prev Prev
- if has_next_issuable?(@project, issuable.id) - if next_issuable = next_issuable_for(issuable)
= link_to 'Next', issuable_link_next(@project, issuable), class: 'btn btn-default next-btn' = link_to 'Next', [@project.namespace.becomes(Namespace), @project, next_issuable], class: 'btn btn-default next-btn'
- else - else
%a.btn.btn-default.disabled{href: '#'} %a.btn.btn-default.disabled{href: '#'}
Next Next
...@@ -50,7 +47,7 @@ ...@@ -50,7 +47,7 @@
.block.milestone .block.milestone
.sidebar-collapsed-icon .sidebar-collapsed-icon
= icon('balance-scale') = icon('clock-o')
%span %span
- if issuable.milestone - if issuable.milestone
= issuable.milestone.title = issuable.milestone.title
...@@ -118,7 +115,7 @@ ...@@ -118,7 +115,7 @@
- project_ref = cross_project_reference(@project, issuable) - project_ref = cross_project_reference(@project, issuable)
.block.project-reference .block.project-reference
.sidebar-collapsed-icon .sidebar-collapsed-icon
= icon('clipboard') = clipboard_button(clipboard_text: project_ref)
.title .title
.cross-project-reference .cross-project-reference
%span %span
......
...@@ -27,6 +27,7 @@ class RepositoryForkWorker ...@@ -27,6 +27,7 @@ class RepositoryForkWorker
return return
end end
project.repository.expire_emptiness_caches
project.import_finish project.import_finish
end end
end end
...@@ -18,6 +18,7 @@ class RepositoryImportWorker ...@@ -18,6 +18,7 @@ class RepositoryImportWorker
return return
end end
project.repository.expire_emptiness_caches
project.import_finish project.import_finish
end end
end end
...@@ -16,7 +16,7 @@ The API_TOKEN will take the Secure Variable value: `SECURE`. ...@@ -16,7 +16,7 @@ The API_TOKEN will take the Secure Variable value: `SECURE`.
### Predefined variables (Environment Variables) ### Predefined variables (Environment Variables)
| Variable | Runner | Description | | Variable | Runner | Description |
|-------------------------|-------------| |-------------------------|-----|--------|
| **CI** | 0.4 | Mark that build is executed in CI environment | | **CI** | 0.4 | Mark that build is executed in CI environment |
| **GITLAB_CI** | all | Mark that build is executed in GitLab CI environment | | **GITLAB_CI** | all | Mark that build is executed in GitLab CI environment |
| **CI_SERVER** | all | Mark that build is executed in CI environment | | **CI_SERVER** | all | Mark that build is executed in CI environment |
......
...@@ -8,6 +8,8 @@ In addition, having to take a server offline for a an upgrade small or big is ...@@ -8,6 +8,8 @@ In addition, having to take a server offline for a an upgrade small or big is
a big burden for most organizations. For this reason it is important that your a big burden for most organizations. For this reason it is important that your
migrations are written carefully, can be applied online and adhere to the style guide below. migrations are written carefully, can be applied online and adhere to the style guide below.
It's advised to have offline migrations only in major GitLab releases.
When writing your migrations, also consider that databases might have stale data When writing your migrations, also consider that databases might have stale data
or inconsistencies and guard for that. Try to make as little assumptions as possible or inconsistencies and guard for that. Try to make as little assumptions as possible
about the state of the database. about the state of the database.
...@@ -33,6 +35,8 @@ It is always preferable to have a migration run online. If you expect the migrat ...@@ -33,6 +35,8 @@ It is always preferable to have a migration run online. If you expect the migrat
to take particularly long (for instance, if it loops through all notes), to take particularly long (for instance, if it loops through all notes),
this is valuable information to add. this is valuable information to add.
If you don't provide the information it means that a migration is safe to run online.
### Reversibility ### Reversibility
Your migration should be reversible. This is very important, as it should Your migration should be reversible. This is very important, as it should
...@@ -85,4 +89,4 @@ select_all("SELECT name, COUNT(id) as cnt FROM tags GROUP BY name HAVING COUNT(i ...@@ -85,4 +89,4 @@ select_all("SELECT name, COUNT(id) as cnt FROM tags GROUP BY name HAVING COUNT(i
execute("UPDATE taggings SET tag_id = #{origin_tag_id} WHERE tag_id IN(#{duplicate_ids.join(",")})") execute("UPDATE taggings SET tag_id = #{origin_tag_id} WHERE tag_id IN(#{duplicate_ids.join(",")})")
execute("DELETE FROM tags WHERE id IN(#{duplicate_ids.join(",")})") execute("DELETE FROM tags WHERE id IN(#{duplicate_ids.join(",")})")
end end
``` ```
\ No newline at end of file
...@@ -182,25 +182,20 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ...@@ -182,25 +182,20 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
## 6. Redis ## 6. Redis
As of this writing, most Debian/Ubuntu distributions ship with Redis 2.2 or GitLab requires at least Redis 2.8.
2.4. GitLab requires at least Redis 2.8.
Ubuntu users [can use a PPA](https://launchpad.net/~chris-lea/+archive/ubuntu/redis-server) If you are using Debian 8 or Ubuntu 14.04 and up, then you can simply install
to install a recent version of Redis. Redis 2.8 with:
The following instructions cover building and installing Redis from scratch:
```sh ```sh
# Build Redis sudo apt-get install redis-server
wget http://download.redis.io/releases/redis-2.8.23.tar.gz ```
tar xzf redis-2.8.23.tar.gz
cd redis-2.8.23
make
# Install Redis If you are using Debian 7 or Ubuntu 12.04, follow the special documentation
cd utils on [an alternate Redis installation](redis.md). Once done, follow the rest of
sudo ./install_server.sh the guide here.
```
# Configure redis to use sockets # Configure redis to use sockets
sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.orig sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.orig
...@@ -224,7 +219,7 @@ if [ -d /etc/tmpfiles.d ]; then ...@@ -224,7 +219,7 @@ if [ -d /etc/tmpfiles.d ]; then
fi fi
# Activate the changes to redis.conf # Activate the changes to redis.conf
sudo service redis_6379 start sudo service redis-server restart
# Add git to the redis group # Add git to the redis group
sudo usermod -aG redis git sudo usermod -aG redis git
......
# Install Redis on old distributions
GitLab requires at least Redis 2.8. The following guide is for Debian 7 and
Ubuntu 12.04. If you are using Debian 8 or Ubuntu 14.04 and up, follow the
[installation guide](installation.md).
## Install Redis 2.8 in Debian 7
Redis 2.8 is included in the Debian Wheezy [backports] repository.
1. Edit `/etc/apt/sources.list` and add the following line:
```
deb http://http.debian.net/debian wheezy-backports main
```
1. Update the repositories:
```
sudo apt-get update
```
1. Install `redis-server`:
```
sudo apt-get -t wheezy-backports install redis-server
```
1. Follow the rest of the [installation guide](installation.md).
## Install Redis 2.8 in Ubuntu 12.04
We will [use a PPA](https://launchpad.net/~chris-lea/+archive/ubuntu/redis-server)
to install a recent version of Redis.
1. Install the PPA repository:
```
sudo add-apt-repository ppa:chris-lea/redis-server
```
Your system will now fetch the PPA's key. This enables your Ubuntu system to
verify that the packages in the PPA have not been interfered with since they
were built.
1. Update the repositories:
```
sudo apt-get update
```
1. Install `redis-server`:
```
sudo apt-get install redis-server
```
1. Follow the rest of the [installation guide](installation.md).
[backports]: http://backports.debian.org/Instructions/ "Debian backports website"
...@@ -18,8 +18,6 @@ for two-factor authentication. If you restore a GitLab backup without ...@@ -18,8 +18,6 @@ for two-factor authentication. If you restore a GitLab backup without
restoring the database encryption key, users who have two-factor restoring the database encryption key, users who have two-factor
authentication enabled will lose access to your GitLab server. authentication enabled will lose access to your GitLab server.
If you are interested in GitLab CI backup please follow to the [CI backup documentation](https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/raketasks/backup_restore.md)*
``` ```
# use this command if you've installed GitLab with the Omnibus package # use this command if you've installed GitLab with the Omnibus package
sudo gitlab-rake gitlab:backup:create sudo gitlab-rake gitlab:backup:create
......
This diff is collapsed.
...@@ -187,12 +187,15 @@ If you have an issue that spans across multiple repositories, the best thing is ...@@ -187,12 +187,15 @@ If you have an issue that spans across multiple repositories, the best thing is
![Vim screen showing the rebase view](rebase.png) ![Vim screen showing the rebase view](rebase.png)
With git you can use an interactive rebase (`rebase -i`) to squash multiple commits into one and reorder them. With git you can use an interactive rebase (`rebase -i`) to squash multiple commits into one and reorder them.
In GitLab EE and .com you can also [rebase before merge](http://doc.gitlab.com/ee/workflow/rebase_before_merge.html) from the web interface.
This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical. This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical.
However you should never rebase commits you have pushed to a remote server. However you should never rebase commits you have pushed to a remote server.
Somebody can have referred to the commits or cherry-picked them. Somebody can have referred to the commits or cherry-picked them.
When you rebase you change the identifier (SHA-1) of the commit and this is confusing. When you rebase you change the identifier (SHA-1) of the commit and this is confusing.
If you do that the same change will be known under multiple identifiers and this can cause much confusion. If you do that the same change will be known under multiple identifiers and this can cause much confusion.
If people already reviewed your code it will be hard for them to review only the improvements you made since then if you have rebased everything into one commit. If people already reviewed your code it will be hard for them to review only the improvements you made since then if you have rebased everything into one commit.
Another reasons not to rebase is that you lose authorship information, maybe someone created a merge request, another person pushed a commit on there to improve it and a third one merged it.
In this case rebasing all the commits into one prevent the other authors from being properly attributed and sharing part of the [git blame](https://git-scm.com/docs/git-blame).
People are encouraged to commit often and to frequently push to the remote repository so other people are aware what everyone is working on. People are encouraged to commit often and to frequently push to the remote repository so other people are aware what everyone is working on.
This will lead to many commits per change which makes the history harder to understand. This will lead to many commits per change which makes the history harder to understand.
...@@ -221,13 +224,11 @@ You can reuse recorded resolutions (rerere) sometimes, but without rebasing you ...@@ -221,13 +224,11 @@ You can reuse recorded resolutions (rerere) sometimes, but without rebasing you
There has to be a better way to avoid many merge commits. There has to be a better way to avoid many merge commits.
The way to prevent creating many merge commits is to not frequently merge master into the feature branch. The way to prevent creating many merge commits is to not frequently merge master into the feature branch.
We'll discuss the three reasons to merge in master: leveraging code, solving merge conflicts and long running branches. We'll discuss the three reasons to merge in master: leveraging code, merge conflicts, and long running branches.
If you need to leverage some code that was introduced in master after you created the feature branch you can sometimes solve this by just cherry-picking a commit. If you need to leverage some code that was introduced in master after you created the feature branch you can sometimes solve this by just cherry-picking a commit.
If your feature branch has a merge conflict, creating a merge commit is a normal way of solving this. If your feature branch has a merge conflict, creating a merge commit is a normal way of solving this.
You should aim to prevent merge conflicts where they are likely to occur. You can prevent some merge conflicts by using [gitattributes](http://git-scm.com/docs/gitattributes) for files that can be in a random order.
One example is the CHANGELOG file where each significant change in the codebase is documented under a version header. For example in GitLab our changelog file is specified in .gitattributes as `CHANGELOG merge=union` so that there are fewer merge conflicts in it.
Instead of everyone adding their change at the bottom of the list for the current version it is better to randomly insert it in the current list for that version.
This it is likely that multiple feature branches that add to the CHANGELOG can be merged before a conflict occurs.
The last reason for creating merge commits is having long lived branches that you want to keep up to date with the latest state of the project. The last reason for creating merge commits is having long lived branches that you want to keep up to date with the latest state of the project.
Martin Fowler, in [his article about feature branches](http://martinfowler.com/bliki/FeatureBranch.html) talks about this Continuous Integration (CI). Martin Fowler, in [his article about feature branches](http://martinfowler.com/bliki/FeatureBranch.html) talks about this Continuous Integration (CI).
At GitLab we are guilty of confusing CI with branch testing. Quoting Martin Fowler: "I've heard people say they are doing CI because they are running builds, perhaps using a CI server, on every branch with every commit. At GitLab we are guilty of confusing CI with branch testing. Quoting Martin Fowler: "I've heard people say they are doing CI because they are running builds, perhaps using a CI server, on every branch with every commit.
......
...@@ -7,7 +7,16 @@ Feature: Award Emoji ...@@ -7,7 +7,16 @@ Feature: Award Emoji
And I visit "Bugfix" issue page And I visit "Bugfix" issue page
@javascript @javascript
Scenario: I add and remove award in the issue Scenario: I repeatedly add and remove thumbsup award in the issue
Given I click the thumbsup award Emoji
Then I have award added
Given I click the thumbsup award Emoji
Then I have no awards added
Given I click the thumbsup award Emoji
Then I have award added
@javascript
Scenario: I add and remove custom award in the issue
Given I click to emoji-picker Given I click to emoji-picker
Then The search field is focused Then The search field is focused
And I click to emoji in the picker And I click to emoji in the picker
......
...@@ -25,9 +25,16 @@ Feature: Project Issues ...@@ -25,9 +25,16 @@ Feature: Project Issues
Scenario: I visit issue page Scenario: I visit issue page
Given I click link "Release 0.4" Given I click link "Release 0.4"
Then I should see issue "Release 0.4" Then I should see issue "Release 0.4"
And I should see "1 of 2" in the sidebar
Scenario: I navigate between issues
Given I click link "Release 0.4"
Then I click link "Next" in the sidebar
Then I should see issue "Tweet control"
And I should see "2 of 2" in the sidebar
@javascript @javascript
Scenario: I visit issue page Scenario: I filter by author
Given I add a user to project "Shop" Given I add a user to project "Shop"
And I click "author" dropdown And I click "author" dropdown
Then I see current user as the first user Then I see current user as the first user
......
...@@ -39,6 +39,7 @@ Feature: Project Merge Requests ...@@ -39,6 +39,7 @@ Feature: Project Merge Requests
Scenario: I visit merge request page Scenario: I visit merge request page
Given I click link "Bug NS-04" Given I click link "Bug NS-04"
Then I should see merge request "Bug NS-04" Then I should see merge request "Bug NS-04"
And I should see "1 of 1" in the sidebar
Scenario: I close merge request page Scenario: I close merge request page
Given I click link "Bug NS-04" Given I click link "Bug NS-04"
......
...@@ -8,6 +8,15 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps ...@@ -8,6 +8,15 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
visit namespace_project_issue_path(@project.namespace, @project, @issue) visit namespace_project_issue_path(@project.namespace, @project, @issue)
end end
step 'I click the thumbsup award Emoji' do
page.within '.awards' do
thumbsup = page.find('.award .emoji-1F44D')
thumbsup.click
thumbsup.hover
sleep 0.3
end
end
step 'I click to emoji-picker' do step 'I click to emoji-picker' do
page.within '.awards-controls' do page.within '.awards-controls' do
page.find('.add-award').click page.find('.add-award').click
...@@ -37,9 +46,28 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps ...@@ -37,9 +46,28 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
end end
step 'I have award added' do step 'I have award added' do
sleep 0.2
page.within '.awards' do page.within '.awards' do
expect(page).to have_selector '.award' expect(page).to have_selector '.award'
expect(page.find('.award.active .counter')).to have_content '1' expect(page.find('.award.active .counter')).to have_content '1'
expect(page.find('.award.active')['data-original-title']).to eq('me')
end
end
step 'I have no awards added' do
page.within '.awards' do
expect(page).to have_selector '.award'
expect(page.all('.award').size).to eq(2)
# Check tooltip data
page.all('.award').each do |element|
expect(element['title']).to eq("")
end
page.all('.award .counter').each do |element|
expect(element).to have_content '0'
end
end end
end end
......
...@@ -54,6 +54,10 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps ...@@ -54,6 +54,10 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
expect(page).to have_content "Release 0.4" expect(page).to have_content "Release 0.4"
end end
step 'I should see issue "Tweet control"' do
expect(page).to have_content "Tweet control"
end
step 'I click link "New Issue"' do step 'I click link "New Issue"' do
click_link "New Issue" click_link "New Issue"
end end
...@@ -301,4 +305,5 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps ...@@ -301,4 +305,5 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
def filter_issue(text) def filter_issue(text)
fill_in 'issue_search', with: text fill_in 'issue_search', with: text
end end
end end
...@@ -119,6 +119,24 @@ module SharedIssuable ...@@ -119,6 +119,24 @@ module SharedIssuable
end end
end end
step 'I should see "1 of 1" in the sidebar' do
expect_sidebar_content('1 of 1')
end
step 'I should see "1 of 2" in the sidebar' do
expect_sidebar_content('1 of 2')
end
step 'I should see "2 of 2" in the sidebar' do
expect_sidebar_content('2 of 2')
end
step 'I click link "Next" in the sidebar' do
page.within '.issuable-sidebar' do
click_link 'Next'
end
end
def create_issuable_for_project(project_name:, title:, type: :issue) def create_issuable_for_project(project_name:, title:, type: :issue)
project = Project.find_by(name: project_name) project = Project.find_by(name: project_name)
...@@ -159,4 +177,10 @@ module SharedIssuable ...@@ -159,4 +177,10 @@ module SharedIssuable
expect(page).to have_content("mentioned in #{issuable.class.to_s.titleize.downcase} #{issuable.to_reference(project)}") expect(page).to have_content("mentioned in #{issuable.class.to_s.titleize.downcase} #{issuable.to_reference(project)}")
end end
def expect_sidebar_content(content)
page.within '.issuable-sidebar' do
expect(page).to have_content content
end
end
end end
module Banzai
module Pipeline
class AsciidocPipeline < BasePipeline
def self.filters
[
Filter::RelativeLinkFilter
]
end
end
end
end
...@@ -31,9 +31,7 @@ module Gitlab ...@@ -31,9 +31,7 @@ module Gitlab
html = ::Asciidoctor.convert(input, asciidoc_opts) html = ::Asciidoctor.convert(input, asciidoc_opts)
if context[:project] html = Banzai.post_process(html, context)
html = Banzai.render(html, context.merge(pipeline: :asciidoc))
end
html.html_safe html.html_safe
end end
......
...@@ -17,7 +17,7 @@ module Gitlab ...@@ -17,7 +17,7 @@ module Gitlab
end end
def true_value def true_value
if self.class.postgresql? if Gitlab::Database.postgresql?
"'t'" "'t'"
else else
1 1
...@@ -25,7 +25,7 @@ module Gitlab ...@@ -25,7 +25,7 @@ module Gitlab
end end
def false_value def false_value
if self.class.postgresql? if Gitlab::Database.postgresql?
"'f'" "'f'"
else else
0 0
...@@ -47,9 +47,5 @@ module Gitlab ...@@ -47,9 +47,5 @@ module Gitlab
row.first row.first
end end
end end
def connection
self.class.connection
end
end end
end end
...@@ -53,13 +53,10 @@ module Gitlab ...@@ -53,13 +53,10 @@ module Gitlab
object_kind: "note", object_kind: "note",
user: user.hook_attrs, user: user.hook_attrs,
project_id: project.id, project_id: project.id,
repository: { project: project.hook_attrs,
name: project.name, object_attributes: note.hook_attrs,
url: project.url_to_repo, # DEPRECATED
description: project.description, repository: project.hook_attrs.slice(:name, :url, :description, :homepage)
homepage: project.web_url,
},
object_attributes: note.hook_attrs
} }
base_data[:object_attributes][:url] = base_data[:object_attributes][:url] =
......
module Gitlab
# Parser/renderer for markups without other special support code.
module OtherMarkup
# Public: Converts the provided markup into HTML.
#
# input - the source text in a markup format
# context - a Hash with the template context:
# :commit
# :project
# :project_wiki
# :requested_path
# :ref
#
def self.render(file_name, input, context)
html = GitHub::Markup.render(file_name, input).
force_encoding(input.encoding)
html = Banzai.post_process(html, context)
html.html_safe
end
end
end
...@@ -22,6 +22,8 @@ module Gitlab ...@@ -22,6 +22,8 @@ module Gitlab
# } # }
# #
def build(project, user, oldrev, newrev, ref, commits = [], message = nil) def build(project, user, oldrev, newrev, ref, commits = [], message = nil)
commits = Array(commits)
# Total commits count # Total commits count
commits_count = commits.size commits_count = commits.size
...@@ -47,18 +49,14 @@ module Gitlab ...@@ -47,18 +49,14 @@ module Gitlab
user_id: user.id, user_id: user.id,
user_name: user.name, user_name: user.name,
user_email: user.email, user_email: user.email,
user_avatar: user.avatar_url,
project_id: project.id, project_id: project.id,
repository: { project: project.hook_attrs,
name: project.name,
url: project.url_to_repo,
description: project.description,
homepage: project.web_url,
git_http_url: project.http_url_to_repo,
git_ssh_url: project.ssh_url_to_repo,
visibility_level: project.visibility_level
},
commits: commit_attrs, commits: commit_attrs,
total_commits_count: commits_count total_commits_count: commits_count,
# DEPRECATED
repository: project.hook_attrs.slice(:name, :url, :description, :homepage,
:git_http_url, :git_ssh_url, :visibility_level)
} }
data data
......
require 'rails_helper'
describe Projects::CommitController do
describe 'GET show' do
let(:project) { create(:project) }
before do
user = create(:user)
project.team << [user, :master]
sign_in(user)
end
context 'with valid id' do
it 'responds with 200' do
go id: project.commit.id
expect(response).to be_ok
end
end
context 'with invalid id' do
it 'responds with 404' do
go id: project.commit.id.reverse
expect(response).to be_not_found
end
end
def go(id:)
get :show,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: id
end
end
end
...@@ -104,6 +104,18 @@ describe Projects::ImportsController do ...@@ -104,6 +104,18 @@ describe Projects::ImportsController do
end end
end end
end end
context 'when import never happened' do
before do
project.update_attribute(:import_status, :none)
end
it 'redirects to namespace_project_path' do
get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
expect(response).to redirect_to namespace_project_path(project.namespace, project)
end
end
end end
end end
end end
...@@ -293,6 +293,10 @@ describe ApplicationHelper do ...@@ -293,6 +293,10 @@ describe ApplicationHelper do
describe 'render_markup' do describe 'render_markup' do
let(:content) { 'Noël' } let(:content) { 'Noël' }
let(:user) { create(:user) }
before do
allow(helper).to receive(:current_user).and_return(user)
end
it 'should preserve encoding' do it 'should preserve encoding' do
expect(content.encoding.name).to eq('UTF-8') expect(content.encoding.name).to eq('UTF-8')
......
...@@ -42,22 +42,6 @@ module Gitlab ...@@ -42,22 +42,6 @@ module Gitlab
end end
end end
context "with project in context" do
let(:context) { { project: create(:project) } }
it "should filter converted input via HTML pipeline and return result" do
filtered_html = '<b>ASCII</b>'
allow(Asciidoctor).to receive(:convert).and_return(html)
expect(Banzai).to receive(:render)
.with(html, context.merge(pipeline: :asciidoc))
.and_return(filtered_html)
expect( render('foo', context) ).to eql filtered_html
end
end
def render(*args) def render(*args)
described_class.render(*args) described_class.render(*args)
end end
......
require 'spec_helper' require 'spec_helper'
class MigrationTest
include Gitlab::Database
end
describe Gitlab::Database, lib: true do describe Gitlab::Database, lib: true do
# These are just simple smoke tests to check if the methods work (regardless # These are just simple smoke tests to check if the methods work (regardless
# of what they may return). # of what they may return).
...@@ -34,4 +38,32 @@ describe Gitlab::Database, lib: true do ...@@ -34,4 +38,32 @@ describe Gitlab::Database, lib: true do
end end
end end
end end
describe '#true_value' do
it 'returns correct value for PostgreSQL' do
expect(described_class).to receive(:postgresql?).and_return(true)
expect(MigrationTest.new.true_value).to eq "'t'"
end
it 'returns correct value for MySQL' do
expect(described_class).to receive(:postgresql?).and_return(false)
expect(MigrationTest.new.true_value).to eq 1
end
end
describe '#false_value' do
it 'returns correct value for PostgreSQL' do
expect(described_class).to receive(:postgresql?).and_return(true)
expect(MigrationTest.new.false_value).to eq "'f'"
end
it 'returns correct value for MySQL' do
expect(described_class).to receive(:postgresql?).and_return(false)
expect(MigrationTest.new.false_value).to eq 0
end
end
end end
...@@ -16,62 +16,80 @@ describe 'Gitlab::NoteDataBuilder', lib: true do ...@@ -16,62 +16,80 @@ describe 'Gitlab::NoteDataBuilder', lib: true do
end end
describe 'When asking for a note on commit' do describe 'When asking for a note on commit' do
let(:note) { create(:note_on_commit) } let(:note) { create(:note_on_commit, project: project) }
it 'returns the note and commit-specific data' do it 'returns the note and commit-specific data' do
expect(data).to have_key(:commit) expect(data).to have_key(:commit)
end end
include_examples 'project hook data'
include_examples 'deprecated repository hook data'
end end
describe 'When asking for a note on commit diff' do describe 'When asking for a note on commit diff' do
let(:note) { create(:note_on_commit_diff) } let(:note) { create(:note_on_commit_diff, project: project) }
it 'returns the note and commit-specific data' do it 'returns the note and commit-specific data' do
expect(data).to have_key(:commit) expect(data).to have_key(:commit)
end end
include_examples 'project hook data'
include_examples 'deprecated repository hook data'
end end
describe 'When asking for a note on issue' do describe 'When asking for a note on issue' do
let(:issue) { create(:issue, created_at: fixed_time, updated_at: fixed_time) } let(:issue) { create(:issue, created_at: fixed_time, updated_at: fixed_time) }
let(:note) { create(:note_on_issue, noteable_id: issue.id) } let(:note) { create(:note_on_issue, noteable_id: issue.id, project: project) }
it 'returns the note and issue-specific data' do it 'returns the note and issue-specific data' do
expect(data).to have_key(:issue) expect(data).to have_key(:issue)
expect(data[:issue].except('updated_at')).to eq(issue.hook_attrs.except('updated_at')) expect(data[:issue].except('updated_at')).to eq(issue.hook_attrs.except('updated_at'))
expect(data[:issue]['updated_at']).to be > issue.hook_attrs['updated_at'] expect(data[:issue]['updated_at']).to be > issue.hook_attrs['updated_at']
end end
include_examples 'project hook data'
include_examples 'deprecated repository hook data'
end end
describe 'When asking for a note on merge request' do describe 'When asking for a note on merge request' do
let(:merge_request) { create(:merge_request, created_at: fixed_time, updated_at: fixed_time) } let(:merge_request) { create(:merge_request, created_at: fixed_time, updated_at: fixed_time) }
let(:note) { create(:note_on_merge_request, noteable_id: merge_request.id) } let(:note) { create(:note_on_merge_request, noteable_id: merge_request.id, project: project) }
it 'returns the note and merge request data' do it 'returns the note and merge request data' do
expect(data).to have_key(:merge_request) expect(data).to have_key(:merge_request)
expect(data[:merge_request].except('updated_at')).to eq(merge_request.hook_attrs.except('updated_at')) expect(data[:merge_request].except('updated_at')).to eq(merge_request.hook_attrs.except('updated_at'))
expect(data[:merge_request]['updated_at']).to be > merge_request.hook_attrs['updated_at'] expect(data[:merge_request]['updated_at']).to be > merge_request.hook_attrs['updated_at']
end end
include_examples 'project hook data'
include_examples 'deprecated repository hook data'
end end
describe 'When asking for a note on merge request diff' do describe 'When asking for a note on merge request diff' do
let(:merge_request) { create(:merge_request, created_at: fixed_time, updated_at: fixed_time) } let(:merge_request) { create(:merge_request, created_at: fixed_time, updated_at: fixed_time) }
let(:note) { create(:note_on_merge_request_diff, noteable_id: merge_request.id) } let(:note) { create(:note_on_merge_request_diff, noteable_id: merge_request.id, project: project) }
it 'returns the note and merge request diff data' do it 'returns the note and merge request diff data' do
expect(data).to have_key(:merge_request) expect(data).to have_key(:merge_request)
expect(data[:merge_request].except('updated_at')).to eq(merge_request.hook_attrs.except('updated_at')) expect(data[:merge_request].except('updated_at')).to eq(merge_request.hook_attrs.except('updated_at'))
expect(data[:merge_request]['updated_at']).to be > merge_request.hook_attrs['updated_at'] expect(data[:merge_request]['updated_at']).to be > merge_request.hook_attrs['updated_at']
end end
include_examples 'project hook data'
include_examples 'deprecated repository hook data'
end end
describe 'When asking for a note on project snippet' do describe 'When asking for a note on project snippet' do
let!(:snippet) { create(:project_snippet, created_at: fixed_time, updated_at: fixed_time) } let!(:snippet) { create(:project_snippet, created_at: fixed_time, updated_at: fixed_time) }
let!(:note) { create(:note_on_project_snippet, noteable_id: snippet.id) } let!(:note) { create(:note_on_project_snippet, noteable_id: snippet.id, project: project) }
it 'returns the note and project snippet data' do it 'returns the note and project snippet data' do
expect(data).to have_key(:snippet) expect(data).to have_key(:snippet)
expect(data[:snippet].except('updated_at')).to eq(snippet.hook_attrs.except('updated_at')) expect(data[:snippet].except('updated_at')).to eq(snippet.hook_attrs.except('updated_at'))
expect(data[:snippet]['updated_at']).to be > snippet.hook_attrs['updated_at'] expect(data[:snippet]['updated_at']).to be > snippet.hook_attrs['updated_at']
end end
include_examples 'project hook data'
include_examples 'deprecated repository hook data'
end end
end end
require 'spec_helper' require 'spec_helper'
describe 'Gitlab::PushDataBuilder', lib: true do describe Gitlab::PushDataBuilder, lib: true do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:user) { create(:user) } let(:user) { create(:user) }
describe :build_sample do describe '.build_sample' do
let(:data) { Gitlab::PushDataBuilder.build_sample(project, user) } let(:data) { described_class.build_sample(project, user) }
it { expect(data).to be_a(Hash) } it { expect(data).to be_a(Hash) }
it { expect(data[:before]).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') } it { expect(data[:before]).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
it { expect(data[:after]).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') } it { expect(data[:after]).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
it { expect(data[:ref]).to eq('refs/heads/master') } it { expect(data[:ref]).to eq('refs/heads/master') }
it { expect(data[:commits].size).to eq(3) } it { expect(data[:commits].size).to eq(3) }
it { expect(data[:repository][:git_http_url]).to eq(project.http_url_to_repo) }
it { expect(data[:repository][:git_ssh_url]).to eq(project.ssh_url_to_repo) }
it { expect(data[:repository][:visibility_level]).to eq(project.visibility_level) }
it { expect(data[:total_commits_count]).to eq(3) } it { expect(data[:total_commits_count]).to eq(3) }
it { expect(data[:commits].first[:added]).to eq(["gitlab-grack"]) } it { expect(data[:commits].first[:added]).to eq(["gitlab-grack"]) }
it { expect(data[:commits].first[:modified]).to eq([".gitmodules"]) } it { expect(data[:commits].first[:modified]).to eq([".gitmodules"]) }
it { expect(data[:commits].first[:removed]).to eq([]) } it { expect(data[:commits].first[:removed]).to eq([]) }
include_examples 'project hook data'
include_examples 'deprecated repository hook data'
end end
describe :build do describe '.build' do
let(:data) do let(:data) do
Gitlab::PushDataBuilder.build(project, described_class.build(project, user, Gitlab::Git::BLANK_SHA,
user, '8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b',
Gitlab::Git::BLANK_SHA, 'refs/tags/v1.1.0')
'8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b',
'refs/tags/v1.1.0')
end end
it { expect(data).to be_a(Hash) } it { expect(data).to be_a(Hash) }
...@@ -38,5 +36,10 @@ describe 'Gitlab::PushDataBuilder', lib: true do ...@@ -38,5 +36,10 @@ describe 'Gitlab::PushDataBuilder', lib: true do
it { expect(data[:ref]).to eq('refs/tags/v1.1.0') } it { expect(data[:ref]).to eq('refs/tags/v1.1.0') }
it { expect(data[:commits]).to be_empty } it { expect(data[:commits]).to be_empty }
it { expect(data[:total_commits_count]).to be_zero } it { expect(data[:total_commits_count]).to be_zero }
it 'does not raise an error when given nil commits' do
expect { described_class.build(spy, spy, spy, spy, spy, nil) }.
not_to raise_error
end
end end
end end
require 'spec_helper'
require 'email_spec'
require 'mailers/shared/notify'
describe Notify do
include EmailSpec::Matchers
include_context 'gitlab email notification'
describe 'build notification email' do
let(:build) { create(:ci_build) }
let(:project) { build.project }
shared_examples 'build email' do
it 'contains name of project' do
is_expected.to have_body_text build.project_name
end
it 'contains link to project' do
is_expected.to have_body_text namespace_project_path(project.namespace, project)
end
end
shared_examples 'an email with X-GitLab headers containing build details' do
it 'has X-GitLab-Build* headers' do
is_expected.to have_header 'X-GitLab-Build-Id', /#{build.id}/
is_expected.to have_header 'X-GitLab-Build-Ref', /#{build.ref}/
end
end
describe 'build success' do
subject { Notify.build_success_email(build.id, 'wow@example.com') }
before { build.success }
it_behaves_like 'build email'
it_behaves_like 'an email with X-GitLab headers containing build details'
it_behaves_like 'an email with X-GitLab headers containing project details'
it 'has header indicating build status' do
is_expected.to have_header 'X-GitLab-Build-Status', 'success'
end
it 'has the correct subject' do
is_expected.to have_subject /Build success for/
end
end
describe 'build fail' do
subject { Notify.build_fail_email(build.id, 'wow@example.com') }
before { build.drop }
it_behaves_like 'build email'
it_behaves_like 'an email with X-GitLab headers containing build details'
it_behaves_like 'an email with X-GitLab headers containing project details'
it 'has header indicating build status' do
is_expected.to have_header 'X-GitLab-Build-Status', 'failed'
end
it 'has the correct subject' do
is_expected.to have_subject /Build failed for/
end
end
end
end
require 'spec_helper'
require 'email_spec'
require 'mailers/shared/notify'
describe Notify do
include EmailSpec::Matchers
include_context 'gitlab email notification'
describe 'profile notifications' do
describe 'for new users, the email' do
let(:example_site_path) { root_path }
let(:new_user) { create(:user, email: new_user_address, created_by_id: 1) }
let(:token) { 'kETLwRaayvigPq_x3SNM' }
subject { Notify.new_user_email(new_user.id, token) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'a new user email'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'contains the password text' do
is_expected.to have_body_text /Click here to set your password/
end
it 'includes a link for user to set password' do
params = "reset_password_token=#{token}"
is_expected.to have_body_text(
%r{http://localhost(:\d+)?/users/password/edit\?#{params}}
)
end
it 'explains the reset link expiration' do
is_expected.to have_body_text(/This link is valid for \d+ (hours?|days?)/)
is_expected.to have_body_text(new_user_password_url)
is_expected.to have_body_text(/\?user_email=.*%40.*/)
end
end
describe 'for users that signed up, the email' do
let(:example_site_path) { root_path }
let(:new_user) { create(:user, email: new_user_address, password: "securePassword") }
subject { Notify.new_user_email(new_user.id) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'a new user email'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'should not contain the new user\'s password' do
is_expected.not_to have_body_text /password/
end
end
describe 'user added ssh key' do
let(:key) { create(:personal_key) }
subject { Notify.new_ssh_key_email(key.id) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'is sent to the new user' do
is_expected.to deliver_to key.user.email
end
it 'has the correct subject' do
is_expected.to have_subject /^SSH key was added to your account$/i
end
it 'contains the new ssh key title' do
is_expected.to have_body_text /#{key.title}/
end
it 'includes a link to ssh keys page' do
is_expected.to have_body_text /#{profile_keys_path}/
end
end
describe 'user added email' do
let(:email) { create(:email) }
subject { Notify.new_email_email(email.id) }
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'is sent to the new user' do
is_expected.to deliver_to email.user.email
end
it 'has the correct subject' do
is_expected.to have_subject /^Email was added to your account$/i
end
it 'contains the new email address' do
is_expected.to have_body_text /#{email.email}/
end
it 'includes a link to emails page' do
is_expected.to have_body_text /#{profile_emails_path}/
end
end
end
end
require 'spec_helper' require 'spec_helper'
require 'email_spec' require 'email_spec'
require 'mailers/shared/notify'
describe Notify do describe Notify do
include EmailSpec::Helpers include EmailSpec::Helpers
include EmailSpec::Matchers include EmailSpec::Matchers
include RepoHelpers include RepoHelpers
new_user_address = 'newguy@example.com' include_context 'gitlab email notification'
let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name }
let(:gitlab_sender) { Gitlab.config.gitlab.email_from }
let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to }
let(:recipient) { create(:user, email: 'recipient@example.com') }
let(:project) { create(:project) }
let(:build) { create(:ci_build) }
before(:each) do
ActionMailer::Base.deliveries.clear
email = recipient.emails.create(email: "notifications@example.com")
recipient.update_attribute(:notification_email, email.email)
end
shared_examples 'a multiple recipients email' do
it 'is sent to the given recipient' do
is_expected.to deliver_to recipient.notification_email
end
end
shared_examples 'an email sent from GitLab' do
it 'is sent from GitLab' do
sender = subject.header[:from].addrs[0]
expect(sender.display_name).to eq(gitlab_sender_display_name)
expect(sender.address).to eq(gitlab_sender)
end
it 'has a Reply-To address' do
reply_to = subject.header[:reply_to].addresses
expect(reply_to).to eq([gitlab_sender_reply_to])
end
end
shared_examples 'an email with X-GitLab headers containing project details' do
it 'has X-GitLab-Project* headers' do
is_expected.to have_header 'X-GitLab-Project', /#{project.name}/
is_expected.to have_header 'X-GitLab-Project-Id', /#{project.id}/
is_expected.to have_header 'X-GitLab-Project-Path', /#{project.path_with_namespace}/
end
end
shared_examples 'an email with X-GitLab headers containing build details' do
it 'has X-GitLab-Build* headers' do
is_expected.to have_header 'X-GitLab-Build-Id', /#{build.id}/
is_expected.to have_header 'X-GitLab-Build-Ref', /#{build.ref}/
end
end
shared_examples 'an email that contains a header with author username' do
it 'has X-GitLab-Author header containing author\'s username' do
is_expected.to have_header 'X-GitLab-Author', user.username
end
end
shared_examples 'an email starting a new thread' do |message_id_prefix|
include_examples 'an email with X-GitLab headers containing project details'
it 'has a discussion identifier' do
is_expected.to have_header 'Message-ID', /<#{message_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
end
end
shared_examples 'an answer to an existing thread' do |thread_id_prefix|
include_examples 'an email with X-GitLab headers containing project details'
it 'has a subject that begins with Re: ' do
is_expected.to have_subject /^Re: /
end
it 'has headers that reference an existing thread' do
is_expected.to have_header 'Message-ID', /<(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'References', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
end
end
shared_examples 'a new user email' do |user_email, site_path|
it 'is sent to the new user' do
is_expected.to deliver_to user_email
end
it 'has the correct subject' do
is_expected.to have_subject /^Account was created for you$/i
end
it 'contains the new user\'s login name' do
is_expected.to have_body_text /#{user_email}/
end
it 'includes a link to the site' do
is_expected.to have_body_text /#{site_path}/
end
end
shared_examples 'it should have Gmail Actions links' do
it { is_expected.to have_body_text /ViewAction/ }
end
shared_examples 'it should not have Gmail Actions links' do
it { is_expected.to_not have_body_text /ViewAction/ }
end
shared_examples 'it should show Gmail Actions View Issue link' do
it_behaves_like 'it should have Gmail Actions links'
it { is_expected.to have_body_text /View Issue/ }
end
shared_examples 'it should show Gmail Actions View Merge request link' do
it_behaves_like 'it should have Gmail Actions links'
it { is_expected.to have_body_text /View Merge request/ }
end
shared_examples 'it should show Gmail Actions View Commit link' do
it_behaves_like 'it should have Gmail Actions links'
it { is_expected.to have_body_text /View Commit/ }
end
shared_examples 'an unsubscribeable thread' do
it { is_expected.to have_body_text /unsubscribe/ }
end
shared_examples "a user cannot unsubscribe through footer link" do
it { is_expected.not_to have_body_text /unsubscribe/ }
end
describe 'for new users, the email' do
let(:example_site_path) { root_path }
let(:new_user) { create(:user, email: new_user_address, created_by_id: 1) }
token = 'kETLwRaayvigPq_x3SNM'
subject { Notify.new_user_email(new_user.id, token) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'a new user email', new_user_address
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'contains the password text' do
is_expected.to have_body_text /Click here to set your password/
end
it 'includes a link for user to set password' do
params = "reset_password_token=#{token}"
is_expected.to have_body_text(
%r{http://localhost(:\d+)?/users/password/edit\?#{params}}
)
end
it 'explains the reset link expiration' do
is_expected.to have_body_text(/This link is valid for \d+ (hours?|days?)/)
is_expected.to have_body_text(new_user_password_url)
is_expected.to have_body_text(/\?user_email=.*%40.*/)
end
end
describe 'for users that signed up, the email' do
let(:example_site_path) { root_path }
let(:new_user) { create(:user, email: new_user_address, password: "securePassword") }
subject { Notify.new_user_email(new_user.id) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'a new user email', new_user_address
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'should not contain the new user\'s password' do
is_expected.not_to have_body_text /password/
end
end
describe 'user added ssh key' do
let(:key) { create(:personal_key) }
subject { Notify.new_ssh_key_email(key.id) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'is sent to the new user' do
is_expected.to deliver_to key.user.email
end
it 'has the correct subject' do
is_expected.to have_subject /^SSH key was added to your account$/i
end
it 'contains the new ssh key title' do
is_expected.to have_body_text /#{key.title}/
end
it 'includes a link to ssh keys page' do
is_expected.to have_body_text /#{profile_keys_path}/
end
end
describe 'user added email' do
let(:email) { create(:email) }
subject { Notify.new_email_email(email.id) }
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'is sent to the new user' do
is_expected.to deliver_to email.user.email
end
it 'has the correct subject' do
is_expected.to have_subject /^Email was added to your account$/i
end
it 'contains the new email address' do
is_expected.to have_body_text /#{email.email}/
end
it 'includes a link to emails page' do
is_expected.to have_body_text /#{profile_emails_path}/
end
end
context 'for a project' do context 'for a project' do
describe 'items that are assignable, the email' do describe 'items that are assignable, the email' do
...@@ -971,49 +747,4 @@ describe Notify do ...@@ -971,49 +747,4 @@ describe Notify do
end end
end end
describe 'build success' do
before { build.success }
subject { Notify.build_success_email(build.id, 'wow@example.com') }
it_behaves_like 'an email with X-GitLab headers containing build details'
it_behaves_like 'an email with X-GitLab headers containing project details' do
let(:project) { build.project }
end
it 'has header indicating build status' do
is_expected.to have_header 'X-GitLab-Build-Status', 'success'
end
it 'has the correct subject' do
should have_subject /Build success for/
end
it 'contains name of project' do
should have_body_text build.project_name
end
end
describe 'build fail' do
before { build.drop }
subject { Notify.build_fail_email(build.id, 'wow@example.com') }
it_behaves_like 'an email with X-GitLab headers containing build details'
it_behaves_like 'an email with X-GitLab headers containing project details' do
let(:project) { build.project }
end
it 'has header indicating build status' do
is_expected.to have_header 'X-GitLab-Build-Status', 'failed'
end
it 'has the correct subject' do
should have_subject /Build failed for/
end
it 'contains name of project' do
should have_body_text build.project_name
end
end
end end
shared_context 'gitlab email notification' do
let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name }
let(:gitlab_sender) { Gitlab.config.gitlab.email_from }
let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to }
let(:recipient) { create(:user, email: 'recipient@example.com') }
let(:project) { create(:project) }
let(:new_user_address) { 'newguy@example.com' }
before do
ActionMailer::Base.deliveries.clear
email = recipient.emails.create(email: "notifications@example.com")
recipient.update_attribute(:notification_email, email.email)
end
end
shared_examples 'a multiple recipients email' do
it 'is sent to the given recipient' do
is_expected.to deliver_to recipient.notification_email
end
end
shared_examples 'an email sent from GitLab' do
it 'is sent from GitLab' do
sender = subject.header[:from].addrs[0]
expect(sender.display_name).to eq(gitlab_sender_display_name)
expect(sender.address).to eq(gitlab_sender)
end
it 'has a Reply-To address' do
reply_to = subject.header[:reply_to].addresses
expect(reply_to).to eq([gitlab_sender_reply_to])
end
end
shared_examples 'an email that contains a header with author username' do
it 'has X-GitLab-Author header containing author\'s username' do
is_expected.to have_header 'X-GitLab-Author', user.username
end
end
shared_examples 'an email with X-GitLab headers containing project details' do
it 'has X-GitLab-Project* headers' do
is_expected.to have_header 'X-GitLab-Project', /#{project.name}/
is_expected.to have_header 'X-GitLab-Project-Id', /#{project.id}/
is_expected.to have_header 'X-GitLab-Project-Path', /#{project.path_with_namespace}/
end
end
shared_examples 'an email starting a new thread' do |message_id_prefix|
include_examples 'an email with X-GitLab headers containing project details'
it 'has a discussion identifier' do
is_expected.to have_header 'Message-ID', /<#{message_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
end
end
shared_examples 'an answer to an existing thread' do |thread_id_prefix|
include_examples 'an email with X-GitLab headers containing project details'
it 'has a subject that begins with Re: ' do
is_expected.to have_subject /^Re: /
end
it 'has headers that reference an existing thread' do
is_expected.to have_header 'Message-ID', /<(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'References', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
end
end
shared_examples 'a new user email' do
it 'is sent to the new user' do
is_expected.to deliver_to new_user_address
end
it 'has the correct subject' do
is_expected.to have_subject /^Account was created for you$/i
end
it 'contains the new user\'s login name' do
is_expected.to have_body_text /#{new_user_address}/
end
end
shared_examples 'it should have Gmail Actions links' do
it { is_expected.to have_body_text /ViewAction/ }
end
shared_examples 'it should not have Gmail Actions links' do
it { is_expected.to_not have_body_text /ViewAction/ }
end
shared_examples 'it should show Gmail Actions View Issue link' do
it_behaves_like 'it should have Gmail Actions links'
it { is_expected.to have_body_text /View Issue/ }
end
shared_examples 'it should show Gmail Actions View Merge request link' do
it_behaves_like 'it should have Gmail Actions links'
it { is_expected.to have_body_text /View Merge request/ }
end
shared_examples 'it should show Gmail Actions View Commit link' do
it_behaves_like 'it should have Gmail Actions links'
it { is_expected.to have_body_text /View Commit/ }
end
shared_examples 'an unsubscribeable thread' do
it { is_expected.to have_body_text /unsubscribe/ }
end
shared_examples "a user cannot unsubscribe through footer link" do
it { is_expected.not_to have_body_text /unsubscribe/ }
end
...@@ -69,27 +69,28 @@ describe Issue, "Issuable" do ...@@ -69,27 +69,28 @@ describe Issue, "Issuable" do
end end
describe "#to_hook_data" do describe "#to_hook_data" do
let(:hook_data) { issue.to_hook_data(user) } let(:data) { issue.to_hook_data(user) }
let(:project) { issue.project }
it "returns correct hook data" do it "returns correct hook data" do
expect(hook_data[:object_kind]).to eq("issue") expect(data[:object_kind]).to eq("issue")
expect(hook_data[:user]).to eq(user.hook_attrs) expect(data[:user]).to eq(user.hook_attrs)
expect(hook_data[:repository][:name]).to eq(issue.project.name) expect(data[:object_attributes]).to eq(issue.hook_attrs)
expect(hook_data[:repository][:url]).to eq(issue.project.url_to_repo) expect(data).to_not have_key(:assignee)
expect(hook_data[:repository][:description]).to eq(issue.project.description)
expect(hook_data[:repository][:homepage]).to eq(issue.project.web_url)
expect(hook_data[:object_attributes]).to eq(issue.hook_attrs)
expect(hook_data).to_not have_key(:assignee)
end end
context "issue is assigned" do context "issue is assigned" do
before { issue.update_attribute(:assignee, user) } before { issue.update_attribute(:assignee, user) }
it "returns correct hook data" do it "returns correct hook data" do
expect(hook_data[:object_attributes]['assignee_id']).to eq(user.id) expect(data[:object_attributes]['assignee_id']).to eq(user.id)
expect(hook_data[:assignee]).to eq(user.hook_attrs) expect(data[:assignee]).to eq(user.hook_attrs)
end end
end end
include_examples 'project hook data'
include_examples 'deprecated repository hook data'
end end
describe '#card_attributes' do describe '#card_attributes' do
......
...@@ -254,13 +254,22 @@ describe MergeRequest, models: true do ...@@ -254,13 +254,22 @@ describe MergeRequest, models: true do
end end
describe "#hook_attrs" do describe "#hook_attrs" do
let(:attrs_hash) { subject.hook_attrs.to_h }
[:source, :target].each do |key|
describe "#{key} key" do
include_examples 'project hook data', project_key: key do
let(:data) { attrs_hash }
let(:project) { subject.send("#{key}_project") }
end
end
end
it "has all the required keys" do it "has all the required keys" do
attrs = subject.hook_attrs expect(attrs_hash).to include(:source)
attrs = attrs.to_h expect(attrs_hash).to include(:target)
expect(attrs).to include(:source) expect(attrs_hash).to include(:last_commit)
expect(attrs).to include(:target) expect(attrs_hash).to include(:work_in_progress)
expect(attrs).to include(:last_commit)
expect(attrs).to include(:work_in_progress)
end end
end end
......
...@@ -355,6 +355,17 @@ describe Repository, models: true do ...@@ -355,6 +355,17 @@ describe Repository, models: true do
end end
end end
describe '#expire_emptiness_caches' do
let(:cache) { repository.send(:cache) }
it 'expires the caches' do
expect(cache).to receive(:expire).with(:empty?)
expect(repository).to receive(:expire_has_visible_content_cache)
repository.expire_emptiness_caches
end
end
describe :skip_merged_commit do describe :skip_merged_commit do
subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", nil, 100, 0, true).map{ |k| k.id } } subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", nil, 100, 0, true).map{ |k| k.id } }
......
...@@ -424,6 +424,21 @@ describe SystemNoteService, services: true do ...@@ -424,6 +424,21 @@ describe SystemNoteService, services: true do
to be_falsey to be_falsey
end end
end end
context 'commit with cross-reference from fork' do
let(:author2) { create(:user) }
let(:forked_project) { Projects::ForkService.new(project, author2).execute }
let(:commit2) { forked_project.commit }
before do
described_class.cross_reference(noteable, commit0, author2)
end
it 'is true when a fork mentions an external issue' do
expect(described_class.cross_reference_exists?(noteable, commit2)).
to be true
end
end
end end
include JiraServiceHelper include JiraServiceHelper
......
RSpec.shared_examples 'project hook data' do |project_key: :project|
it 'contains project data' do
expect(data[project_key][:name]).to eq(project.name)
expect(data[project_key][:description]).to eq(project.description)
expect(data[project_key][:web_url]).to eq(project.web_url)
expect(data[project_key][:avatar_url]).to eq(project.avatar_url)
expect(data[project_key][:git_http_url]).to eq(project.http_url_to_repo)
expect(data[project_key][:git_ssh_url]).to eq(project.ssh_url_to_repo)
expect(data[project_key][:namespace]).to eq(project.namespace.name)
expect(data[project_key][:visibility_level]).to eq(project.visibility_level)
expect(data[project_key][:path_with_namespace]).to eq(project.path_with_namespace)
expect(data[project_key][:default_branch]).to eq(project.default_branch)
expect(data[project_key][:homepage]).to eq(project.web_url)
expect(data[project_key][:url]).to eq(project.url_to_repo)
expect(data[project_key][:ssh_url]).to eq(project.ssh_url_to_repo)
expect(data[project_key][:http_url]).to eq(project.http_url_to_repo)
end
end
RSpec.shared_examples 'deprecated repository hook data' do |project_key: :project|
it 'contains deprecated repository data' do
expect(data[:repository][:name]).to eq(project.name)
expect(data[:repository][:description]).to eq(project.description)
expect(data[:repository][:url]).to eq(project.url_to_repo)
expect(data[:repository][:homepage]).to eq(project.web_url)
end
end
...@@ -19,6 +19,18 @@ describe RepositoryForkWorker do ...@@ -19,6 +19,18 @@ describe RepositoryForkWorker do
fork_project.namespace.path) fork_project.namespace.path)
end end
it 'flushes the empty caches' do
expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).
with(project.path_with_namespace, fork_project.namespace.path).
and_return(true)
expect_any_instance_of(Repository).to receive(:expire_emptiness_caches).
and_call_original
subject.perform(project.id, project.path_with_namespace,
fork_project.namespace.path)
end
it "handles bad fork" do it "handles bad fork" do
expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).and_return(false) expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).and_return(false)
subject.perform( subject.perform(
......
require 'spec_helper'
describe RepositoryImportWorker do
let(:project) { create(:project) }
subject { described_class.new }
describe '#perform' do
it 'imports a project' do
expect_any_instance_of(Projects::ImportService).to receive(:execute).
and_return({ status: :ok })
expect_any_instance_of(Repository).to receive(:expire_emptiness_caches)
expect_any_instance_of(Project).to receive(:import_finish)
subject.perform(project.id)
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment