Commit 097b635b authored by James Lopez's avatar James Lopez

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into fix/gitlab-omniauth-issue

parents 9597920c cb0b7de4
...@@ -34,6 +34,11 @@ v 8.5.0 (unreleased) ...@@ -34,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)
...@@ -49,6 +54,7 @@ v 8.5.0 (unreleased) ...@@ -49,6 +54,7 @@ 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)
...@@ -56,6 +62,8 @@ v 8.5.0 (unreleased) ...@@ -56,6 +62,8 @@ v 8.5.0 (unreleased)
- 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 - Fix broken link to project in build notification emails
- Ability to see and sort on vote count from Issues and MR lists
- Fix builds scheduler when first build in stage was allowed to fail
v 8.4.4 v 8.4.4
- Update omniauth-saml gem to 1.4.2 - Update omniauth-saml gem to 1.4.2
......
...@@ -112,7 +112,7 @@ gem 'diffy', '~> 3.0.3' ...@@ -112,7 +112,7 @@ gem 'diffy', '~> 3.0.3'
# Application server # Application server
group :unicorn do group :unicorn do
gem "unicorn", '~> 4.8.2' gem "unicorn", '~> 4.9.0'
gem 'unicorn-worker-killer', '~> 0.4.2' gem 'unicorn-worker-killer', '~> 0.4.2'
end end
......
...@@ -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)
...@@ -833,7 +833,7 @@ GEM ...@@ -833,7 +833,7 @@ GEM
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.1) unf_ext (0.0.7.1)
unicorn (4.8.3) unicorn (4.9.0)
kgio (~> 2.6) kgio (~> 2.6)
rack rack
raindrops (~> 0.7) raindrops (~> 0.7)
...@@ -1034,7 +1034,7 @@ DEPENDENCIES ...@@ -1034,7 +1034,7 @@ DEPENDENCIES
uglifier (~> 2.7.2) uglifier (~> 2.7.2)
underscore-rails (~> 1.8.0) underscore-rails (~> 1.8.0)
unf (~> 0.1.4) unf (~> 0.1.4)
unicorn (~> 4.8.2) unicorn (~> 4.9.0)
unicorn-worker-killer (~> 0.4.2) unicorn-worker-killer (~> 0.4.2)
version_sorter (~> 2.0.0) version_sorter (~> 2.0.0)
virtus (~> 1.0.1) virtus (~> 1.0.1)
......
...@@ -15,3 +15,5 @@ class @IssuableContext ...@@ -15,3 +15,5 @@ class @IssuableContext
block.find('.selectbox').show() block.find('.selectbox').show()
block.find('.value').hide() block.find('.value').hide()
block.find('.js-select2').select2("open") block.find('.js-select2').select2("open")
$(".right-sidebar").niceScroll()
...@@ -117,4 +117,4 @@ body { ...@@ -117,4 +117,4 @@ body {
&.ui_violet { &.ui_violet {
@include gitlab-theme(#9988CC, $theme-violet, #443366, #332255); @include gitlab-theme(#9988CC, $theme-violet, #443366, #332255);
} }
} }
\ 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;
}
}
} }
} }
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
display: inline-block; display: inline-block;
} }
.issue-no-comments { .issue-no-comments, .issue-no-votes {
opacity: 0.5; opacity: 0.5;
} }
} }
......
...@@ -163,7 +163,7 @@ ...@@ -163,7 +163,7 @@
display: inline-block; display: inline-block;
} }
.merge-request-no-comments { .merge-request-no-comments, .merge-request-no-votes {
opacity: 0.5; opacity: 0.5;
} }
} }
...@@ -236,4 +236,4 @@ ...@@ -236,4 +236,4 @@
} }
} }
} }
} }
\ No newline at end of file
...@@ -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
...@@ -11,7 +11,9 @@ class Projects::RepositoriesController < Projects::ApplicationController ...@@ -11,7 +11,9 @@ class Projects::RepositoriesController < Projects::ApplicationController
end end
def archive def archive
render json: ArchiveRepositoryService.new(@project, params[:ref], params[:format]).execute RepositoryArchiveCacheWorker.perform_async
headers.store(*Gitlab::Workhorse.send_git_archive(@project, params[:ref], params[:format]))
head :ok
rescue => ex rescue => ex
logger.error("#{self.class.name}: #{ex}") logger.error("#{self.class.name}: #{ex}")
return git_not_found! return git_not_found!
......
...@@ -280,76 +280,6 @@ module ApplicationHelper ...@@ -280,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)
......
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"
......
...@@ -11,6 +11,8 @@ module SortingHelper ...@@ -11,6 +11,8 @@ module SortingHelper
sort_value_largest_repo => sort_title_largest_repo, sort_value_largest_repo => sort_title_largest_repo,
sort_value_recently_signin => sort_title_recently_signin, sort_value_recently_signin => sort_title_recently_signin,
sort_value_oldest_signin => sort_title_oldest_signin, sort_value_oldest_signin => sort_title_oldest_signin,
sort_value_downvotes => sort_title_downvotes,
sort_value_upvotes => sort_title_upvotes
} }
end end
...@@ -54,6 +56,14 @@ module SortingHelper ...@@ -54,6 +56,14 @@ module SortingHelper
'Oldest sign in' 'Oldest sign in'
end end
def sort_title_downvotes
'Least popular'
end
def sort_title_upvotes
'Most popular'
end
def sort_value_oldest_updated def sort_value_oldest_updated
'updated_asc' 'updated_asc'
end end
...@@ -93,4 +103,12 @@ module SortingHelper ...@@ -93,4 +103,12 @@ module SortingHelper
def sort_value_oldest_signin def sort_value_oldest_signin
'oldest_sign_in' 'oldest_sign_in'
end end
def sort_value_downvotes
'downvotes_desc'
end
def sort_value_upvotes
'upvotes_desc'
end
end end
...@@ -69,10 +69,35 @@ module Issuable ...@@ -69,10 +69,35 @@ module Issuable
case method.to_s case method.to_s
when 'milestone_due_asc' then order_milestone_due_asc when 'milestone_due_asc' then order_milestone_due_asc
when 'milestone_due_desc' then order_milestone_due_desc when 'milestone_due_desc' then order_milestone_due_desc
when 'downvotes_desc' then order_downvotes_desc
when 'upvotes_desc' then order_upvotes_desc
else else
order_by(method) order_by(method)
end end
end end
def order_downvotes_desc
order_votes_desc('thumbsdown')
end
def order_upvotes_desc
order_votes_desc('thumbsup')
end
def order_votes_desc(award_emoji_name)
issuable_table = self.arel_table
note_table = Note.arel_table
join_clause = issuable_table.join(note_table, Arel::Nodes::OuterJoin).on(
note_table[:noteable_id].eq(issuable_table[:id]).and(
note_table[:noteable_type].eq(self.name).and(
note_table[:is_award].eq(true).and(note_table[:note].eq(award_emoji_name))
)
)
).join_sources
joins(join_clause).group(issuable_table[:id]).reorder("COUNT(notes.id) DESC")
end
end end
def today? def today?
...@@ -129,13 +154,10 @@ module Issuable ...@@ -129,13 +154,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
......
...@@ -137,7 +137,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -137,7 +137,7 @@ class MergeRequest < ActiveRecord::Base
scope :by_milestone, ->(milestone) { where(milestone_id: milestone) } scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) } scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) }
scope :of_projects, ->(ids) { where(target_project_id: ids) } scope :of_projects, ->(ids) { where(target_project_id: ids) }
scope :opened, -> { with_state(:opened) } scope :opened, -> { with_states(:opened, :reopened) }
scope :merged, -> { with_state(:merged) } scope :merged, -> { with_state(:merged) }
scope :closed, -> { with_state(:closed) } scope :closed, -> { with_state(:closed) }
scope :closed_and_merged, -> { with_states(:closed, :merged) } scope :closed_and_merged, -> { with_states(:closed, :merged) }
......
...@@ -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}"
} }
......
...@@ -136,7 +136,7 @@ class ProjectTeam ...@@ -136,7 +136,7 @@ class ProjectTeam
end end
def human_max_access(user_id) def human_max_access(user_id)
Gitlab::Access.options.key max_member_access(user_id) Gitlab::Access.options_with_owner.key(max_member_access(user_id))
end end
# This method assumes project and group members are eager loaded for optimal # This method assumes project and group members are eager loaded for optimal
......
...@@ -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
...@@ -23,13 +23,11 @@ class Repository ...@@ -23,13 +23,11 @@ class Repository
def raw_repository def raw_repository
return nil unless path_with_namespace return nil unless path_with_namespace
@raw_repository ||= begin @raw_repository ||= Gitlab::Git::Repository.new(path_to_repo)
repo = Gitlab::Git::Repository.new(path_to_repo) end
repo.autocrlf = :input
repo def update_autocrlf_option
rescue Gitlab::Git::Repository::NoRepository raw_repository.autocrlf = :input if raw_repository.autocrlf != :input
nil
end
end end
# Return absolute path to repository # Return absolute path to repository
...@@ -40,7 +38,12 @@ class Repository ...@@ -40,7 +38,12 @@ class Repository
end end
def exists? def exists?
raw_repository return false unless raw_repository
raw_repository.rugged
true
rescue Gitlab::Git::Repository::NoRepository
false
end end
def empty? def empty?
...@@ -67,7 +70,7 @@ class Repository ...@@ -67,7 +70,7 @@ class Repository
end end
def commit(id = 'HEAD') def commit(id = 'HEAD')
return nil unless raw_repository return nil unless exists?
commit = Gitlab::Git::Commit.find(raw_repository, id) commit = Gitlab::Git::Commit.find(raw_repository, id)
commit = Commit.new(commit, @project) if commit commit = Commit.new(commit, @project) if commit
commit commit
...@@ -238,6 +241,15 @@ class Repository ...@@ -238,6 +241,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 +270,14 @@ class Repository ...@@ -258,6 +270,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 +631,8 @@ class Repository ...@@ -611,6 +631,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
...@@ -674,6 +696,8 @@ class Repository ...@@ -674,6 +696,8 @@ class Repository
end end
def commit_with_hooks(current_user, branch) def commit_with_hooks(current_user, branch)
update_autocrlf_option
oldrev = Gitlab::Git::BLANK_SHA oldrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
was_empty = empty? was_empty = empty?
......
class ArchiveRepositoryService
attr_reader :project, :ref, :format
def initialize(project, ref, format)
format ||= 'tar.gz'
@project, @ref, @format = project, ref, format.downcase
end
def execute(options = {})
RepositoryArchiveCacheWorker.perform_async
metadata = project.repository.archive_metadata(ref, storage_path, format)
raise "Repository or ref not found" if metadata.empty?
metadata
end
private
def storage_path
Gitlab.config.gitlab.repository_downloads_path
end
end
...@@ -34,6 +34,7 @@ module Ci ...@@ -34,6 +34,7 @@ module Ci
build = commit.builds.create!(build_attrs) build = commit.builds.create!(build_attrs)
build.execute_hooks build.execute_hooks
build
end end
end end
end end
......
class GitPushService class GitPushService < BaseService
attr_accessor :project, :user, :push_data, :push_commits attr_accessor :push_data, :push_commits
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
include Gitlab::Access include Gitlab::Access
# This method will be called after each git update # This method will be called after each git update
# and only if the provided user and project is present in GitLab. # and only if the provided user and project are present in GitLab.
# #
# All callbacks for post receive action should be placed here. # All callbacks for post receive action should be placed here.
# #
...@@ -15,67 +15,67 @@ class GitPushService ...@@ -15,67 +15,67 @@ class GitPushService
# 4. Executes the project's web hooks # 4. Executes the project's web hooks
# 5. Executes the project's services # 5. Executes the project's services
# #
def execute(project, user, oldrev, newrev, ref) def execute
@project, @user = project, user @project.repository.expire_cache(branch_name)
branch_name = Gitlab::Git.ref_name(ref)
project.repository.expire_cache(branch_name)
if push_remove_branch?(ref, newrev)
project.repository.expire_has_visible_content_cache
if push_remove_branch?
@project.repository.expire_has_visible_content_cache
@push_commits = [] @push_commits = []
elsif push_to_new_branch?(ref, oldrev) elsif push_to_new_branch?
project.repository.expire_has_visible_content_cache @project.repository.expire_has_visible_content_cache
# Re-find the pushed commits. # Re-find the pushed commits.
if is_default_branch?(ref) if is_default_branch?
# Initial push to the default branch. Take the full history of that branch as "newly pushed". # Initial push to the default branch. Take the full history of that branch as "newly pushed".
@push_commits = project.repository.commits(newrev) process_default_branch
# Ensure HEAD points to the default branch in case it is not master
project.change_head(branch_name)
# Set protection on the default branch if configured
if (current_application_settings.default_branch_protection != PROTECTION_NONE)
developers_can_push = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? true : false
project.protected_branches.create({ name: project.default_branch, developers_can_push: developers_can_push })
end
else else
# Use the pushed commits that aren't reachable by the default branch # Use the pushed commits that aren't reachable by the default branch
# as a heuristic. This may include more commits than are actually pushed, but # as a heuristic. This may include more commits than are actually pushed, but
# that shouldn't matter because we check for existing cross-references later. # that shouldn't matter because we check for existing cross-references later.
@push_commits = project.repository.commits_between(project.default_branch, newrev) @push_commits = @project.repository.commits_between(@project.default_branch, params[:newrev])
# don't process commits for the initial push to the default branch # don't process commits for the initial push to the default branch
process_commit_messages(ref) process_commit_messages
end end
elsif push_to_existing_branch?(ref, oldrev) elsif push_to_existing_branch?
# Collect data for this git push # Collect data for this git push
@push_commits = project.repository.commits_between(oldrev, newrev) @push_commits = @project.repository.commits_between(params[:oldrev], params[:newrev])
process_commit_messages(ref) process_commit_messages
end end
# Update merge requests that may be affected by this push. A new branch # Update merge requests that may be affected by this push. A new branch
# could cause the last commit of a merge request to change. # could cause the last commit of a merge request to change.
project.update_merge_requests(oldrev, newrev, ref, @user) update_merge_requests
end
@push_data = build_push_data(oldrev, newrev, ref) protected
EventCreateService.new.push(project, user, @push_data) def update_merge_requests
project.execute_hooks(@push_data.dup, :push_hooks) @project.update_merge_requests(params[:oldrev], params[:newrev], params[:ref], current_user)
project.execute_services(@push_data.dup, :push_hooks)
CreateCommitBuildsService.new.execute(project, @user, @push_data) EventCreateService.new.push(@project, current_user, build_push_data)
ProjectCacheWorker.perform_async(project.id) @project.execute_hooks(build_push_data.dup, :push_hooks)
@project.execute_services(build_push_data.dup, :push_hooks)
CreateCommitBuildsService.new.execute(@project, current_user, build_push_data)
ProjectCacheWorker.perform_async(@project.id)
end end
protected def process_default_branch
@push_commits = project.repository.commits(params[:newrev])
# Ensure HEAD points to the default branch in case it is not master
project.change_head(branch_name)
# Set protection on the default branch if configured
if (current_application_settings.default_branch_protection != PROTECTION_NONE)
developers_can_push = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? true : false
@project.protected_branches.create({ name: @project.default_branch, developers_can_push: developers_can_push })
end
end
# Extract any GFM references from the pushed commit messages. If the configured issue-closing regex is matched, # Extract any GFM references from the pushed commit messages. If the configured issue-closing regex is matched,
# close the referenced Issue. Create cross-reference Notes corresponding to any other referenced Mentionables. # close the referenced Issue. Create cross-reference Notes corresponding to any other referenced Mentionables.
def process_commit_messages(ref) def process_commit_messages
is_default_branch = is_default_branch?(ref) is_default_branch = is_default_branch?
authors = Hash.new do |hash, commit| authors = Hash.new do |hash, commit|
email = commit.author_email email = commit.author_email
...@@ -94,7 +94,7 @@ class GitPushService ...@@ -94,7 +94,7 @@ class GitPushService
# Close issues if these commits were pushed to the project's default branch and the commit message matches the # Close issues if these commits were pushed to the project's default branch and the commit message matches the
# closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to # closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to
# a different branch. # a different branch.
closed_issues = commit.closes_issues(user) closed_issues = commit.closes_issues(current_user)
closed_issues.each do |issue| closed_issues.each do |issue|
Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit) Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit)
end end
...@@ -104,34 +104,38 @@ class GitPushService ...@@ -104,34 +104,38 @@ class GitPushService
end end
end end
def build_push_data(oldrev, newrev, ref) def build_push_data
Gitlab::PushDataBuilder. @push_data ||= Gitlab::PushDataBuilder.
build(project, user, oldrev, newrev, ref, push_commits) build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], push_commits)
end end
def push_to_existing_branch?(ref, oldrev) def push_to_existing_branch?
# Return if this is not a push to a branch (e.g. new commits) # Return if this is not a push to a branch (e.g. new commits)
Gitlab::Git.branch_ref?(ref) && !Gitlab::Git.blank_ref?(oldrev) Gitlab::Git.branch_ref?(params[:ref]) && !Gitlab::Git.blank_ref?(params[:oldrev])
end end
def push_to_new_branch?(ref, oldrev) def push_to_new_branch?
Gitlab::Git.branch_ref?(ref) && Gitlab::Git.blank_ref?(oldrev) Gitlab::Git.branch_ref?(params[:ref]) && Gitlab::Git.blank_ref?(params[:oldrev])
end end
def push_remove_branch?(ref, newrev) def push_remove_branch?
Gitlab::Git.branch_ref?(ref) && Gitlab::Git.blank_ref?(newrev) Gitlab::Git.branch_ref?(params[:ref]) && Gitlab::Git.blank_ref?(params[:newrev])
end end
def push_to_branch?(ref) def push_to_branch?
Gitlab::Git.branch_ref?(ref) Gitlab::Git.branch_ref?(params[:ref])
end end
def is_default_branch?(ref) def is_default_branch?
Gitlab::Git.branch_ref?(ref) && Gitlab::Git.branch_ref?(params[:ref]) &&
(Gitlab::Git.ref_name(ref) == project.default_branch || project.default_branch.nil?) (Gitlab::Git.ref_name(params[:ref]) == project.default_branch || project.default_branch.nil?)
end end
def commit_user(commit) def commit_user(commit)
commit.author || user commit.author || current_user
end
def branch_name
@branch_name ||= Gitlab::Git.ref_name(params[:ref])
end end
end end
...@@ -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
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
%br %br
- if current_user.can_create_project? - if current_user.can_create_project?
You can create up to You can create up to
%strong= pluralize(current_user.projects_limit, "project") + "." %strong= pluralize(number_with_delimiter(current_user.projects_limit), "project") + "."
- else - else
If you are added to a project, it will be displayed here. If you are added to a project, it will be displayed here.
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
.dashboard-intro-text .dashboard-intro-text
%p.slead %p.slead
There are There are
%strong= publicish_project_count %strong= number_with_delimiter(publicish_project_count)
public projects on this server. public projects on this server.
%br %br
Public projects are an easy way to allow everyone to have read-only access. Public projects are an easy way to allow everyone to have read-only access.
......
...@@ -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
......
...@@ -119,13 +119,13 @@ ...@@ -119,13 +119,13 @@
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
%p Get recent application code using the following command: %p Get recent application code using the following command:
.radio .radio
= f.label :build_allow_git_fetch do = f.label :build_allow_git_fetch_false do
= f.radio_button :build_allow_git_fetch, 'false' = f.radio_button :build_allow_git_fetch, 'false'
%strong git clone %strong git clone
%br %br
%span.descr Slower but makes sure you have a clean dir before every build %span.descr Slower but makes sure you have a clean dir before every build
.radio .radio
= f.label :build_allow_git_fetch do = f.label :build_allow_git_fetch_true do
= f.radio_button :build_allow_git_fetch, 'true' = f.radio_button :build_allow_git_fetch, 'true'
%strong git fetch %strong git fetch
%br %br
......
...@@ -15,6 +15,25 @@ ...@@ -15,6 +15,25 @@
%li %li
= link_to_member(@project, issue.assignee, name: false, title: "Assigned to :name") = link_to_member(@project, issue.assignee, name: false, title: "Assigned to :name")
- upvotes, downvotes = issue.upvotes, issue.downvotes
- if upvotes > 0 || downvotes > 0
%li
= icon('thumbs-up')
= upvotes
- else
%li{ class: 'issue-no-votes' }
= icon('thumbs-up')
= upvotes
- if upvotes > 0 || downvotes > 0
%li
= icon('thumbs-down')
= downvotes
- else
%li{ class: 'issue-no-votes' }
= icon('thumbs-down')
= downvotes
- note_count = issue.notes.user.count - note_count = issue.notes.user.count
- if note_count > 0 - if note_count > 0
%li %li
......
...@@ -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
...@@ -24,6 +24,25 @@ ...@@ -24,6 +24,25 @@
%li %li
= link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name") = link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name")
- upvotes, downvotes = merge_request.upvotes, merge_request.downvotes
- if upvotes > 0 || downvotes > 0
%li
= icon('thumbs-up')
= upvotes
- else
%li{ class: 'merge-request-no-votes' }
= icon('thumbs-up')
= upvotes
- if upvotes > 0 || downvotes > 0
%li
= icon('thumbs-down')
= downvotes
- else
%li{ class: 'merge-request-no-votes' }
= icon('thumbs-down')
= downvotes
- note_count = merge_request.mr_and_commit_notes.user.count - note_count = merge_request.mr_and_commit_notes.user.count
- if note_count > 0 - if note_count > 0
%li %li
......
$('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();
...@@ -20,3 +20,7 @@ ...@@ -20,3 +20,7 @@
= sort_title_milestone_soon = sort_title_milestone_soon
= link_to page_filter_path(sort: sort_value_milestone_later) do = link_to page_filter_path(sort: sort_value_milestone_later) do
= sort_title_milestone_later = sort_title_milestone_later
= link_to page_filter_path(sort: sort_value_upvotes) do
= sort_title_upvotes
= link_to page_filter_path(sort: sort_value_downvotes) do
= sort_title_downvotes
...@@ -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
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
- ci = false unless local_assigns[:ci] == true - ci = false unless local_assigns[:ci] == true
- skip_namespace = false unless local_assigns[:skip_namespace] == true - skip_namespace = false unless local_assigns[:skip_namespace] == true
- css_class = '' unless local_assigns[:css_class] - css_class = '' unless local_assigns[:css_class]
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true - show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && project.commit
- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description - css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
- ci_commit = project.ci_commit(project.commit.sha) if ci && !project.empty_repo? && project.commit - ci_commit = project.ci_commit(project.commit.sha) if ci && !project.empty_repo? && project.commit
- cache_key = [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.2'] - cache_key = [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.2']
......
...@@ -38,7 +38,7 @@ class PostReceive ...@@ -38,7 +38,7 @@ class PostReceive
if Gitlab::Git.tag_ref?(ref) if Gitlab::Git.tag_ref?(ref)
GitTagPushService.new.execute(project, @user, oldrev, newrev, ref) GitTagPushService.new.execute(project, @user, oldrev, newrev, ref)
else else
GitPushService.new.execute(project, @user, oldrev, newrev, ref) GitPushService.new(project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute
end end
end end
end end
......
...@@ -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
...@@ -6,6 +6,8 @@ I18n.config.enforce_available_locales = false ...@@ -6,6 +6,8 @@ I18n.config.enforce_available_locales = false
Bundler.require(:default, Rails.env) Bundler.require(:default, Rails.env)
module Gitlab module Gitlab
REDIS_CACHE_NAMESPACE = 'cache:gitlab'
class Application < Rails::Application class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here. # Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers # Application configuration should go into files in config/initializers
...@@ -89,7 +91,7 @@ module Gitlab ...@@ -89,7 +91,7 @@ module Gitlab
redis_config_hash[:path] = redis_uri.path redis_config_hash[:path] = redis_uri.path
end end
redis_config_hash[:namespace] = 'cache:gitlab' redis_config_hash[:namespace] = REDIS_CACHE_NAMESPACE
redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever
config.cache_store = :redis_store, redis_config_hash config.cache_store = :redis_store, redis_config_hash
......
...@@ -3,6 +3,6 @@ module Gitlab ...@@ -3,6 +3,6 @@ module Gitlab
Settings Settings
end end
VERSION = File.read(Rails.root.join("VERSION")).strip VERSION = File.read(Rails.root.join("VERSION")).strip.freeze
REVISION = Gitlab::Popen.popen(%W(#{config.git.bin_path} log --pretty=format:%h -n 1)).first.chomp REVISION = Gitlab::Popen.popen(%W(#{config.git.bin_path} log --pretty=format:%h -n 1)).first.chomp.freeze
end end
...@@ -14,6 +14,7 @@ if Rails.env.production? ...@@ -14,6 +14,7 @@ if Rails.env.production?
if sentry_enabled if sentry_enabled
Raven.configure do |config| Raven.configure do |config|
config.dsn = current_application_settings.sentry_dsn config.dsn = current_application_settings.sentry_dsn
config.release = Gitlab::REVISION
end end
end end
end end
-- ---
:concurrency: 5 :concurrency: 5
\ No newline at end of file
...@@ -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 |
......
...@@ -428,8 +428,30 @@ artifacts: ...@@ -428,8 +428,30 @@ artifacts:
- binaries/ - binaries/
``` ```
The artifacts will be send after a successful build success to GitLab, and will You may want to create artifacts only for tagged releases to avoid filling the
be accessible in the GitLab UI to download. build server storage with temporary build artifacts.
Create artifacts only for tags (`default-job` will not create artifacts):
```yaml
default-job:
script:
- mvn test -U
except:
- tags
release-job:
script:
- mvn package -U
artifacts:
paths:
- target/*.war
only:
- tags
```
The artifacts will be sent to GitLab after a successful build and will
be available for download in the GitLab UI.
### cache ### cache
......
...@@ -15,7 +15,7 @@ or inconsistencies and guard for that. Try to make as little assumptions as poss ...@@ -15,7 +15,7 @@ or inconsistencies and guard for that. Try to make as little assumptions as poss
about the state of the database. about the state of the database.
Please don't depend on GitLab specific code since it can change in future versions. Please don't depend on GitLab specific code since it can change in future versions.
If needed copy-paste GitLab code into the migration to make make it forward compatible. If needed copy-paste GitLab code into the migration to make it forward compatible.
## Comments in the migration ## Comments in the migration
......
...@@ -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
...@@ -358,7 +353,7 @@ GitLab Shell is an SSH access and repository management software developed speci ...@@ -358,7 +353,7 @@ GitLab Shell is an SSH access and repository management software developed speci
cd /home/git cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
cd gitlab-workhorse cd gitlab-workhorse
sudo -u git -H git checkout 0.6.4 sudo -u git -H git checkout 0.6.5
sudo -u git -H make sudo -u git -H make
### Initialize Database and Activate Advanced Features ### Initialize Database and Activate Advanced Features
......
# 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"
...@@ -81,27 +81,6 @@ There are new configuration options available for [`gitlab.yml`](config/gitlab.y ...@@ -81,27 +81,6 @@ There are new configuration options available for [`gitlab.yml`](config/gitlab.y
git diff origin/8-3-stable:config/gitlab.yml.example origin/8-4-stable:config/gitlab.yml.example git diff origin/8-3-stable:config/gitlab.yml.example origin/8-4-stable:config/gitlab.yml.example
``` ```
#### Nginx configuration
GitLab 8.3 introduced major changes in the NGINX configuration. Ensure you're
still up-to-date with the latest changes:
```sh
# For HTTPS configurations
git diff origin/8-3-stable:lib/support/nginx/gitlab-ssl origin/8-4-stable:lib/support/nginx/gitlab-ssl
# For HTTP configurations
git diff origin/8-3-stable:lib/support/nginx/gitlab origin/8-4-stable:lib/support/nginx/gitlab
```
If you are using Apache instead of NGINX please see the updated [Apache templates].
Also note that because Apache does not support upstreams behind Unix sockets you
will need to let gitlab-workhorse listen on a TCP port. You can do this
via [/etc/default/gitlab].
[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-4-stable/lib/support/init.d/gitlab.default.example#L34
#### Init script #### Init script
We updated the init script for GitLab in order to set a specific PATH for gitlab-workhorse. We updated the init script for GitLab in order to set a specific PATH for gitlab-workhorse.
......
...@@ -82,6 +82,32 @@ There are new configuration options available for [`gitlab.yml`](config/gitlab.y ...@@ -82,6 +82,32 @@ There are new configuration options available for [`gitlab.yml`](config/gitlab.y
git diff origin/8-4-stable:config/gitlab.yml.example origin/8-5-stable:config/gitlab.yml.example git diff origin/8-4-stable:config/gitlab.yml.example origin/8-5-stable:config/gitlab.yml.example
``` ```
#### Nginx configuration
Ensure you're still up-to-date with the latest NGINX configuration changes:
```sh
# For HTTPS configurations
git diff origin/8-4-stable:lib/support/nginx/gitlab-ssl origin/8-5-stable:lib/support/nginx/gitlab-ssl
# For HTTP configurations
git diff origin/8-4-stable:lib/support/nginx/gitlab origin/8-5-stable:lib/support/nginx/gitlab
```
If you are using Apache instead of NGINX please see the updated [Apache templates].
Also note that because Apache does not support upstreams behind Unix sockets you
will need to let gitlab-workhorse listen on a TCP port. You can do this
via [/etc/default/gitlab].
[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-5-stable/lib/support/init.d/gitlab.default.example#L37
#### Init script
Ensure you're still up-to-date with the latest init script changes:
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
### 8. Start application ### 8. Start application
sudo service gitlab start sudo service gitlab start
......
# Web hooks # Web hooks
_**Note:**
Starting from GitLab 8.5:_
- _the `repository` key is deprecated in favor of the `project` key_
- _the `project.ssh_url` key is deprecated in favor of the `project.git_ssh_url` key_
- _the `project.http_url` key is deprecated in favor of the `project.git_http_url` key_
Project web hooks allow you to trigger an URL if new code is pushed or a new issue is created. Project web hooks allow you to trigger an URL if new code is pushed or a new issue is created.
You can configure web hooks to listen for specific events like pushes, issues or merge requests. GitLab will send a POST request with data to the web hook URL. You can configure web hooks to listen for specific events like pushes, issues or merge requests. GitLab will send a POST request with data to the web hook URL.
...@@ -37,8 +44,25 @@ X-Gitlab-Event: Push Hook ...@@ -37,8 +44,25 @@ X-Gitlab-Event: Push Hook
"user_id": 4, "user_id": 4,
"user_name": "John Smith", "user_name": "John Smith",
"user_email": "john@example.com", "user_email": "john@example.com",
"user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
"project_id": 15, "project_id": 15,
"repository": { "project":{
"name":"Diaspora",
"description":"",
"web_url":"http://example.com/mike/diaspora",
"avatar_url":null,
"git_ssh_url":"git@example.com:mike/diaspora.git",
"git_http_url":"http://example.com/mike/diaspora.git",
"namespace":"Mike",
"visibility_level":0,
"path_with_namespace":"mike/diaspora",
"default_branch":"master",
"homepage":"http://example.com/mike/diaspora",
"url":"git@example.com:mike/diasporadiaspora.git",
"ssh_url":"git@example.com:mike/diaspora.git",
"http_url":"http://example.com/mike/diaspora.git"
},
"repository":{
"name": "Diaspora", "name": "Diaspora",
"url": "git@example.com:mike/diasporadiaspora.git", "url": "git@example.com:mike/diasporadiaspora.git",
"description": "", "description": "",
...@@ -100,8 +124,25 @@ X-Gitlab-Event: Tag Push Hook ...@@ -100,8 +124,25 @@ X-Gitlab-Event: Tag Push Hook
"after": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7", "after": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7",
"user_id": 1, "user_id": 1,
"user_name": "John Smith", "user_name": "John Smith",
"user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
"project_id": 1, "project_id": 1,
"repository": { "project":{
"name":"Example",
"description":"",
"web_url":"http://example.com/jsmith/example",
"avatar_url":null,
"git_ssh_url":"git@example.com:jsmith/example.git",
"git_http_url":"http://example.com/jsmith/example.git",
"namespace":"Jsmith",
"visibility_level":0,
"path_with_namespace":"jsmith/example",
"default_branch":"master",
"homepage":"http://example.com/jsmith/example",
"url":"git@example.com:jsmith/example.git",
"ssh_url":"git@example.com:jsmith/example.git",
"http_url":"http://example.com/jsmith/example.git"
},
"repository":{
"name": "jsmith", "name": "jsmith",
"url": "ssh://git@example.com/jsmith/example.git", "url": "ssh://git@example.com/jsmith/example.git",
"description": "", "description": "",
...@@ -135,7 +176,23 @@ X-Gitlab-Event: Issue Hook ...@@ -135,7 +176,23 @@ X-Gitlab-Event: Issue Hook
"username": "root", "username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
}, },
"repository": { "project":{
"name":"Gitlab Test",
"description":"Aut reprehenderit ut est.",
"web_url":"http://example.com/gitlabhq/gitlab-test",
"avatar_url":null,
"git_ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
"git_http_url":"http://example.com/gitlabhq/gitlab-test.git",
"namespace":"GitlabHQ",
"visibility_level":20,
"path_with_namespace":"gitlabhq/gitlab-test",
"default_branch":"master",
"homepage":"http://example.com/gitlabhq/gitlab-test",
"url":"http://example.com/gitlabhq/gitlab-test.git",
"ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
"http_url":"http://example.com/gitlabhq/gitlab-test.git"
},
"repository":{
"name": "Gitlab Test", "name": "Gitlab Test",
"url": "http://example.com/gitlabhq/gitlab-test.git", "url": "http://example.com/gitlabhq/gitlab-test.git",
"description": "Aut reprehenderit ut est.", "description": "Aut reprehenderit ut est.",
...@@ -197,9 +254,25 @@ X-Gitlab-Event: Note Hook ...@@ -197,9 +254,25 @@ X-Gitlab-Event: Note Hook
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
}, },
"project_id": 5, "project_id": 5,
"repository": { "project":{
"name":"Gitlab Test",
"description":"Aut reprehenderit ut est.",
"web_url":"http://example.com/gitlabhq/gitlab-test",
"avatar_url":null,
"git_ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
"git_http_url":"http://example.com/gitlabhq/gitlab-test.git",
"namespace":"GitlabHQ",
"visibility_level":20,
"path_with_namespace":"gitlabhq/gitlab-test",
"default_branch":"master",
"homepage":"http://example.com/gitlabhq/gitlab-test",
"url":"http://example.com/gitlabhq/gitlab-test.git",
"ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
"http_url":"http://example.com/gitlabhq/gitlab-test.git"
},
"repository":{
"name": "Gitlab Test", "name": "Gitlab Test",
"url": "http://localhost/gitlab-org/gitlab-test.git", "url": "http://example.com/gitlab-org/gitlab-test.git",
"description": "Aut reprehenderit ut est.", "description": "Aut reprehenderit ut est.",
"homepage": "http://example.com/gitlab-org/gitlab-test" "homepage": "http://example.com/gitlab-org/gitlab-test"
}, },
...@@ -260,9 +333,25 @@ X-Gitlab-Event: Note Hook ...@@ -260,9 +333,25 @@ X-Gitlab-Event: Note Hook
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
}, },
"project_id": 5, "project_id": 5,
"repository": { "project":{
"name":"Gitlab Test",
"description":"Aut reprehenderit ut est.",
"web_url":"http://example.com/gitlab-org/gitlab-test",
"avatar_url":null,
"git_ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
"git_http_url":"http://example.com/gitlab-org/gitlab-test.git",
"namespace":"Gitlab Org",
"visibility_level":10,
"path_with_namespace":"gitlab-org/gitlab-test",
"default_branch":"master",
"homepage":"http://example.com/gitlab-org/gitlab-test",
"url":"http://example.com/gitlab-org/gitlab-test.git",
"ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
"http_url":"http://example.com/gitlab-org/gitlab-test.git"
},
"repository":{
"name": "Gitlab Test", "name": "Gitlab Test",
"url": "http://example.com/gitlab-org/gitlab-test.git", "url": "http://localhost/gitlab-org/gitlab-test.git",
"description": "Aut reprehenderit ut est.", "description": "Aut reprehenderit ut est.",
"homepage": "http://example.com/gitlab-org/gitlab-test" "homepage": "http://example.com/gitlab-org/gitlab-test"
}, },
...@@ -300,21 +389,37 @@ X-Gitlab-Event: Note Hook ...@@ -300,21 +389,37 @@ X-Gitlab-Event: Note Hook
"description": "Et voluptas corrupti assumenda temporibus. Architecto cum animi eveniet amet asperiores. Vitae numquam voluptate est natus sit et ad id.", "description": "Et voluptas corrupti assumenda temporibus. Architecto cum animi eveniet amet asperiores. Vitae numquam voluptate est natus sit et ad id.",
"position": 0, "position": 0,
"locked_at": null, "locked_at": null,
"source": { "source":{
"name": "Gitlab Test", "name":"Gitlab Test",
"ssh_url": "git@example.com:gitlab-org/gitlab-test.git", "description":"Aut reprehenderit ut est.",
"http_url": "http://example.com/gitlab-org/gitlab-test.git", "web_url":"http://example.com/gitlab-org/gitlab-test",
"web_url": "http://example.com/gitlab-org/gitlab-test", "avatar_url":null,
"namespace": "Gitlab Org", "git_ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
"visibility_level": 10 "git_http_url":"http://example.com/gitlab-org/gitlab-test.git",
"namespace":"Gitlab Org",
"visibility_level":10,
"path_with_namespace":"gitlab-org/gitlab-test",
"default_branch":"master",
"homepage":"http://example.com/gitlab-org/gitlab-test",
"url":"http://example.com/gitlab-org/gitlab-test.git",
"ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
"http_url":"http://example.com/gitlab-org/gitlab-test.git"
}, },
"target": { "target": {
"name": "Gitlab Test", "name":"Gitlab Test",
"ssh_url": "git@example.com:gitlab-org/gitlab-test.git", "description":"Aut reprehenderit ut est.",
"http_url": "http://example.com/gitlab-org/gitlab-test.git", "web_url":"http://example.com/gitlab-org/gitlab-test",
"web_url": "http://example.com/gitlab-org/gitlab-test", "avatar_url":null,
"namespace": "Gitlab Org", "git_ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
"visibility_level": 10 "git_http_url":"http://example.com/gitlab-org/gitlab-test.git",
"namespace":"Gitlab Org",
"visibility_level":10,
"path_with_namespace":"gitlab-org/gitlab-test",
"default_branch":"master",
"homepage":"http://example.com/gitlab-org/gitlab-test",
"url":"http://example.com/gitlab-org/gitlab-test.git",
"ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
"http_url":"http://example.com/gitlab-org/gitlab-test.git"
}, },
"last_commit": { "last_commit": {
"id": "562e173be03b8ff2efb05345d12df18815438a4b", "id": "562e173be03b8ff2efb05345d12df18815438a4b",
...@@ -355,11 +460,27 @@ X-Gitlab-Event: Note Hook ...@@ -355,11 +460,27 @@ X-Gitlab-Event: Note Hook
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
}, },
"project_id": 5, "project_id": 5,
"repository": { "project":{
"name": "Gitlab Test", "name":"Gitlab Test",
"url": "http://example.com/gitlab-org/gitlab-test.git", "description":"Aut reprehenderit ut est.",
"description": "Aut reprehenderit ut est.", "web_url":"http://example.com/gitlab-org/gitlab-test",
"homepage": "http://example.com/gitlab-org/gitlab-test" "avatar_url":null,
"git_ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
"git_http_url":"http://example.com/gitlab-org/gitlab-test.git",
"namespace":"Gitlab Org",
"visibility_level":10,
"path_with_namespace":"gitlab-org/gitlab-test",
"default_branch":"master",
"homepage":"http://example.com/gitlab-org/gitlab-test",
"url":"http://example.com/gitlab-org/gitlab-test.git",
"ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
"http_url":"http://example.com/gitlab-org/gitlab-test.git"
},
"repository":{
"name":"diaspora",
"url":"git@example.com:mike/diasporadiaspora.git",
"description":"",
"homepage":"http://example.com/mike/diaspora"
}, },
"object_attributes": { "object_attributes": {
"id": 1241, "id": 1241,
...@@ -397,7 +518,6 @@ X-Gitlab-Event: Note Hook ...@@ -397,7 +518,6 @@ X-Gitlab-Event: Note Hook
### Comment on code snippet ### Comment on code snippet
**Request header**: **Request header**:
``` ```
...@@ -415,11 +535,27 @@ X-Gitlab-Event: Note Hook ...@@ -415,11 +535,27 @@ X-Gitlab-Event: Note Hook
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
}, },
"project_id": 5, "project_id": 5,
"repository": { "project":{
"name": "Gitlab Test", "name":"Gitlab Test",
"url": "http://example.com/gitlab-org/gitlab-test.git", "description":"Aut reprehenderit ut est.",
"description": "Aut reprehenderit ut est.", "web_url":"http://example.com/gitlab-org/gitlab-test",
"homepage": "http://example.com/gitlab-org/gitlab-test" "avatar_url":null,
"git_ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
"git_http_url":"http://example.com/gitlab-org/gitlab-test.git",
"namespace":"Gitlab Org",
"visibility_level":10,
"path_with_namespace":"gitlab-org/gitlab-test",
"default_branch":"master",
"homepage":"http://example.com/gitlab-org/gitlab-test",
"url":"http://example.com/gitlab-org/gitlab-test.git",
"ssh_url":"git@example.com:gitlab-org/gitlab-test.git",
"http_url":"http://example.com/gitlab-org/gitlab-test.git"
},
"repository":{
"name":"Gitlab Test",
"url":"http://example.com/gitlab-org/gitlab-test.git",
"description":"Aut reprehenderit ut est.",
"homepage":"http://example.com/gitlab-org/gitlab-test"
}, },
"object_attributes": { "object_attributes": {
"id": 1245, "id": 1245,
...@@ -491,21 +627,37 @@ X-Gitlab-Event: Merge Request Hook ...@@ -491,21 +627,37 @@ X-Gitlab-Event: Merge Request Hook
"target_project_id": 14, "target_project_id": 14,
"iid": 1, "iid": 1,
"description": "", "description": "",
"source": { "source":{
"name": "awesome_project", "name":"Awesome Project",
"ssh_url": "ssh://git@example.com/awesome_space/awesome_project.git", "description":"Aut reprehenderit ut est.",
"http_url": "http://example.com/awesome_space/awesome_project.git", "web_url":"http://example.com/awesome_space/awesome_project",
"web_url": "http://example.com/awesome_space/awesome_project", "avatar_url":null,
"visibility_level": 20, "git_ssh_url":"git@example.com:awesome_space/awesome_project.git",
"namespace": "awesome_space" "git_http_url":"http://example.com/awesome_space/awesome_project.git",
"namespace":"Awesome Space",
"visibility_level":20,
"path_with_namespace":"awesome_space/awesome_project",
"default_branch":"master",
"homepage":"http://example.com/awesome_space/awesome_project",
"url":"http://example.com/awesome_space/awesome_project.git",
"ssh_url":"git@example.com:awesome_space/awesome_project.git",
"http_url":"http://example.com/awesome_space/awesome_project.git"
}, },
"target": { "target": {
"name": "awesome_project", "name":"Awesome Project",
"ssh_url": "ssh://git@example.com/awesome_space/awesome_project.git", "description":"Aut reprehenderit ut est.",
"http_url": "http://example.com/awesome_space/awesome_project.git", "web_url":"http://example.com/awesome_space/awesome_project",
"web_url": "http://example.com/awesome_space/awesome_project", "avatar_url":null,
"visibility_level": 20, "git_ssh_url":"git@example.com:awesome_space/awesome_project.git",
"namespace": "awesome_space" "git_http_url":"http://example.com/awesome_space/awesome_project.git",
"namespace":"Awesome Space",
"visibility_level":20,
"path_with_namespace":"awesome_space/awesome_project",
"default_branch":"master",
"homepage":"http://example.com/awesome_space/awesome_project",
"url":"http://example.com/awesome_space/awesome_project.git",
"ssh_url":"git@example.com:awesome_space/awesome_project.git",
"http_url":"http://example.com/awesome_space/awesome_project.git"
}, },
"last_commit": { "last_commit": {
"id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
......
...@@ -32,6 +32,13 @@ Feature: Project Fork ...@@ -32,6 +32,13 @@ Feature: Project Fork
And I visit the forks page of the "Shop" project And I visit the forks page of the "Shop" project
Then I should see my fork on the list Then I should see my fork on the list
Scenario: Viewing forks of a Project that has no repo
Given I click link "Fork"
When I fork to my namespace
And I make forked repo invalid
And I visit the forks page of the "Shop" project
Then I should see my fork on the list
Scenario: Viewing private forks of a Project Scenario: Viewing private forks of a Project
Given There is an existent fork of the "Shop" project Given There is an existent fork of the "Shop" project
And I click link "Fork" And I click link "Fork"
......
...@@ -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
...@@ -81,6 +88,16 @@ Feature: Project Issues ...@@ -81,6 +88,16 @@ Feature: Project Issues
And I visit dashboard merge requests page And I visit dashboard merge requests page
Then The list should be sorted by "Oldest updated" Then The list should be sorted by "Oldest updated"
@javascript
Scenario: Sort issues by upvotes/downvotes
Given project "Shop" have "Bugfix" open issue
And issue "Release 0.4" have 2 upvotes and 1 downvote
And issue "Tweet control" have 1 upvote and 2 downvotes
And I sort the list by "Most popular"
Then The list should be sorted by "Most popular"
And I sort the list by "Least popular"
Then The list should be sorted by "Least popular"
@javascript @javascript
Scenario: I search issue Scenario: I search issue
Given I fill in issue search with "Re" Given I fill in issue search with "Re"
......
...@@ -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"
...@@ -106,6 +107,17 @@ Feature: Project Merge Requests ...@@ -106,6 +107,17 @@ Feature: Project Merge Requests
And I visit dashboard merge requests page And I visit dashboard merge requests page
Then The list should be sorted by "Oldest updated" Then The list should be sorted by "Oldest updated"
@javascript
Scenario: Sort merge requests by upvotes/downvotes
Given project "Shop" have "Bug NS-05" open merge request with diffs inside
And project "Shop" have "Bug NS-06" open merge request
And merge request "Bug NS-04" have 2 upvotes and 1 downvote
And merge request "Bug NS-06" have 1 upvote and 2 downvotes
And I sort the list by "Most popular"
Then The list should be sorted by "Most popular"
And I sort the list by "Least popular"
Then The list should be sorted by "Least popular"
@javascript @javascript
Scenario: Visiting Merge Requests after commenting on diffs Scenario: Visiting Merge Requests after commenting on diffs
Given project "Shop" have "Bug NS-05" open merge request with diffs inside Given project "Shop" have "Bug NS-05" open merge request with diffs inside
......
...@@ -62,6 +62,12 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps ...@@ -62,6 +62,12 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
end end
end end
step 'I make forked repo invalid' do
project = @user.fork_of(@project)
project.path = 'test-crappy-path'
project.save!
end
step 'There is an existent fork of the "Shop" project' do step 'There is an existent fork of the "Shop" project' do
user = create(:user, name: 'Mike') user = create(:user, name: 'Mike')
@forked_project = Projects::ForkService.new(@project, user).execute @forked_project = Projects::ForkService.new(@project, user).execute
......
...@@ -46,6 +46,8 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps ...@@ -46,6 +46,8 @@ 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'
......
...@@ -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
...@@ -170,6 +174,13 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps ...@@ -170,6 +174,13 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
author: project.users.first) author: project.users.first)
end end
step 'project "Shop" have "Bugfix" open issue' do
create(:issue,
title: "Bugfix",
project: project,
author: project.users.first)
end
step 'project "Shop" have "Release 0.3" closed issue' do step 'project "Shop" have "Release 0.3" closed issue' do
create(:closed_issue, create(:closed_issue,
title: "Release 0.3", title: "Release 0.3",
...@@ -177,6 +188,56 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps ...@@ -177,6 +188,56 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
author: project.users.first) author: project.users.first)
end end
step 'issue "Release 0.4" have 2 upvotes and 1 downvote' do
issue = Issue.find_by(title: 'Release 0.4')
create_list(:upvote_note, 2, project: project, noteable: issue)
create(:downvote_note, project: project, noteable: issue)
end
step 'issue "Tweet control" have 1 upvote and 2 downvotes' do
issue = Issue.find_by(title: 'Tweet control')
create(:upvote_note, project: project, noteable: issue)
create_list(:downvote_note, 2, project: project, noteable: issue)
end
step 'The list should be sorted by "Least popular"' do
page.within '.issues-list' do
page.within 'li.issue:nth-child(1)' do
expect(page).to have_content 'Tweet control'
expect(page).to have_content '1 2'
end
page.within 'li.issue:nth-child(2)' do
expect(page).to have_content 'Release 0.4'
expect(page).to have_content '2 1'
end
page.within 'li.issue:nth-child(3)' do
expect(page).to have_content 'Bugfix'
expect(page).to have_content '0 0'
end
end
end
step 'The list should be sorted by "Most popular"' do
page.within '.issues-list' do
page.within 'li.issue:nth-child(1)' do
expect(page).to have_content 'Release 0.4'
expect(page).to have_content '2 1'
end
page.within 'li.issue:nth-child(2)' do
expect(page).to have_content 'Tweet control'
expect(page).to have_content '1 2'
end
page.within 'li.issue:nth-child(3)' do
expect(page).to have_content 'Bugfix'
expect(page).to have_content '0 0'
end
end
end
step 'empty project "Empty Project"' do step 'empty project "Empty Project"' do
create :empty_project, name: 'Empty Project', namespace: @user.namespace create :empty_project, name: 'Empty Project', namespace: @user.namespace
end end
...@@ -301,4 +362,5 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps ...@@ -301,4 +362,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
...@@ -138,6 +138,56 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -138,6 +138,56 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
author: project.users.first) author: project.users.first)
end end
step 'merge request "Bug NS-04" have 2 upvotes and 1 downvote' do
merge_request = MergeRequest.find_by(title: 'Bug NS-04')
create_list(:upvote_note, 2, project: project, noteable: merge_request)
create(:downvote_note, project: project, noteable: merge_request)
end
step 'merge request "Bug NS-06" have 1 upvote and 2 downvotes' do
merge_request = MergeRequest.find_by(title: 'Bug NS-06')
create(:upvote_note, project: project, noteable: merge_request)
create_list(:downvote_note, 2, project: project, noteable: merge_request)
end
step 'The list should be sorted by "Least popular"' do
page.within '.mr-list' do
page.within 'li.merge-request:nth-child(1)' do
expect(page).to have_content 'Bug NS-06'
expect(page).to have_content '1 2'
end
page.within 'li.merge-request:nth-child(2)' do
expect(page).to have_content 'Bug NS-04'
expect(page).to have_content '2 1'
end
page.within 'li.merge-request:nth-child(3)' do
expect(page).to have_content 'Bug NS-05'
expect(page).to have_content '0 0'
end
end
end
step 'The list should be sorted by "Most popular"' do
page.within '.mr-list' do
page.within 'li.merge-request:nth-child(1)' do
expect(page).to have_content 'Bug NS-04'
expect(page).to have_content '2 1'
end
page.within 'li.merge-request:nth-child(2)' do
expect(page).to have_content 'Bug NS-06'
expect(page).to have_content '1 2'
end
page.within 'li.merge-request:nth-child(3)' do
expect(page).to have_content 'Bug NS-05'
expect(page).to have_content '0 0'
end
end
end
step 'I click on the Changes tab' do step 'I click on the Changes tab' do
page.within '.merge-request-tabs' do page.within '.merge-request-tabs' do
click_link 'Changes' click_link 'Changes'
......
...@@ -113,12 +113,46 @@ module SharedIssuable ...@@ -113,12 +113,46 @@ module SharedIssuable
end end
end end
step 'I sort the list by "Least popular"' do
find('button.dropdown-toggle.btn').click
page.within('ul.dropdown-menu.dropdown-menu-align-right li') do
click_link 'Least popular'
end
end
step 'I sort the list by "Most popular"' do
find('button.dropdown-toggle.btn').click
page.within('ul.dropdown-menu.dropdown-menu-align-right li') do
click_link 'Most popular'
end
end
step 'The list should be sorted by "Oldest updated"' do step 'The list should be sorted by "Oldest updated"' do
page.within('div.dropdown.inline.prepend-left-10') do page.within('div.dropdown.inline.prepend-left-10') do
expect(page.find('button.dropdown-toggle.btn')).to have_content('Oldest updated') expect(page.find('button.dropdown-toggle.btn')).to have_content('Oldest updated')
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 +193,10 @@ module SharedIssuable ...@@ -159,4 +193,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
...@@ -98,11 +98,8 @@ module API ...@@ -98,11 +98,8 @@ module API
authorize! :download_code, user_project authorize! :download_code, user_project
begin begin
ArchiveRepositoryService.new( RepositoryArchiveCacheWorker.perform_async
user_project, header *Gitlab::Workhorse.send_git_archive(user_project, params[:sha], params[:format])
params[:sha],
params[:format]
).execute
rescue rescue
not_found!('File') not_found!('File')
end end
......
module Ci module Ci
class Status class Status
def self.get_status(statuses) def self.get_status(statuses)
statuses.reject! { |status| status.try(&:allow_failure?) }
if statuses.none? if statuses.none?
'skipped' 'skipped'
elsif statuses.all?(&:success?) elsif statuses.all? { |status| status.success? || status.ignored? }
'success' 'success'
elsif statuses.all?(&:pending?) elsif statuses.all?(&:pending?)
'pending' 'pending'
......
...@@ -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] =
......
...@@ -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
......
...@@ -3,19 +3,38 @@ require 'json' ...@@ -3,19 +3,38 @@ require 'json'
module Gitlab module Gitlab
class Workhorse class Workhorse
SEND_DATA_HEADER = 'Gitlab-Workhorse-Send-Data'
class << self class << self
def send_git_blob(repository, blob) def send_git_blob(repository, blob)
params_hash = { params = {
'RepoPath' => repository.path_to_repo, 'RepoPath' => repository.path_to_repo,
'BlobId' => blob.id, 'BlobId' => blob.id,
} }
params = Base64.urlsafe_encode64(JSON.dump(params_hash))
[ [
'Gitlab-Workhorse-Send-Data', SEND_DATA_HEADER,
"git-blob:#{params}", "git-blob:#{encode(params)}",
] ]
end end
def send_git_archive(project, ref, format)
format ||= 'tar.gz'
format.downcase!
params = project.repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format)
raise "Repository or ref not found" if params.empty?
[
SEND_DATA_HEADER,
"git-archive:#{encode(params)}",
]
end
protected
def encode(hash)
Base64.urlsafe_encode64(JSON.dump(hash))
end
end end
end end
end end
namespace :cache do namespace :cache do
CLEAR_BATCH_SIZE = 1000
REDIS_SCAN_START_STOP = '0' # Magic value, see http://redis.io/commands/scan
desc "GitLab | Clear redis cache" desc "GitLab | Clear redis cache"
task :clear => :environment do task :clear => :environment do
# Hack into Rails.cache until https://github.com/redis-store/redis-store/pull/225
# is accepted (I hope) and we can update the redis-store gem.
redis_store = Rails.cache.instance_variable_get(:@data) redis_store = Rails.cache.instance_variable_get(:@data)
redis_store.keys.each_slice(1000) do |key_slice| cursor = [REDIS_SCAN_START_STOP, []]
redis_store.del(*key_slice) loop do
cursor = redis_store.scan(
cursor.first,
match: "#{Gitlab::REDIS_CACHE_NAMESPACE}*",
count: CLEAR_BATCH_SIZE
)
keys = cursor.last
redis_store.del(*keys) if keys.any?
break if cursor.first == REDIS_SCAN_START_STOP
end end
end end
end end
...@@ -16,7 +16,6 @@ namespace :gitlab do ...@@ -16,7 +16,6 @@ namespace :gitlab do
check_git_config check_git_config
check_database_config_exists check_database_config_exists
check_database_is_not_sqlite
check_migrations_are_up check_migrations_are_up
check_orphaned_group_members check_orphaned_group_members
check_gitlab_config_exists check_gitlab_config_exists
......
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
...@@ -123,6 +123,40 @@ describe Projects::MergeRequestsController do ...@@ -123,6 +123,40 @@ describe Projects::MergeRequestsController do
end end
end end
describe 'GET #index' do
def get_merge_requests
get :index,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
state: 'opened'
end
context 'when filtering by opened state' do
context 'with opened merge requests' do
it 'should list those merge requests' do
get_merge_requests
expect(assigns(:merge_requests)).to include(merge_request)
end
end
context 'with reopened merge requests' do
before do
merge_request.close!
merge_request.reopen!
end
it 'should list those merge requests' do
get_merge_requests
expect(assigns(:merge_requests)).to include(merge_request)
end
end
end
end
describe 'GET diffs' do describe 'GET diffs' do
def go(format: 'html') def go(format: 'html')
get :diffs, get :diffs,
......
...@@ -8,15 +8,10 @@ describe Projects::RepositoriesController do ...@@ -8,15 +8,10 @@ describe Projects::RepositoriesController do
before do before do
sign_in(user) sign_in(user)
project.team << [user, :developer] project.team << [user, :developer]
allow(ArchiveRepositoryService).to receive(:new).and_return(service)
end end
let(:service) { ArchiveRepositoryService.new(project, "master", "zip") } it "uses Gitlab::Workhorse" do
expect(Gitlab::Workhorse).to receive(:send_git_archive).with(project, "master", "zip")
it "executes ArchiveRepositoryService" do
expect(ArchiveRepositoryService).to receive(:new).with(project, "master", "zip")
expect(service).to receive(:execute)
get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip" get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
end end
...@@ -24,7 +19,7 @@ describe Projects::RepositoriesController do ...@@ -24,7 +19,7 @@ describe Projects::RepositoriesController do
context "when the service raises an error" do context "when the service raises an error" do
before do before do
allow(service).to receive(:execute).and_raise("Archive failed") allow(Gitlab::Workhorse).to receive(:send_git_archive).and_raise("Archive failed")
end end
it "renders Not Found" do it "renders Not Found" do
......
...@@ -16,10 +16,30 @@ FactoryGirl.define do ...@@ -16,10 +16,30 @@ FactoryGirl.define do
commit factory: :ci_commit commit factory: :ci_commit
trait :success do
status 'success'
end
trait :failed do
status 'failed'
end
trait :canceled do trait :canceled do
status 'canceled' status 'canceled'
end end
trait :running do
status 'running'
end
trait :pending do
status 'pending'
end
trait :allowed_to_fail do
allow_failure true
end
after(:build) do |build, evaluator| after(:build) do |build, evaluator|
build.project = build.commit.project build.project = build.commit.project
end end
......
...@@ -34,6 +34,8 @@ FactoryGirl.define do ...@@ -34,6 +34,8 @@ FactoryGirl.define do
factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff] factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff]
factory :note_on_project_snippet, traits: [:on_project_snippet] factory :note_on_project_snippet, traits: [:on_project_snippet]
factory :system_note, traits: [:system] factory :system_note, traits: [:system]
factory :downvote_note, traits: [:award, :downvote]
factory :upvote_note, traits: [:award, :upvote]
trait :on_commit do trait :on_commit do
project project
...@@ -65,6 +67,18 @@ FactoryGirl.define do ...@@ -65,6 +67,18 @@ FactoryGirl.define do
system true system true
end end
trait :award do
is_award true
end
trait :downvote do
note "thumbsdown"
end
trait :upvote do
note "thumbsup"
end
trait :with_attachment do trait :with_attachment do
attachment { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") } attachment { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") }
end end
......
require 'spec_helper'
describe Ci::Status do
describe '.get_status' do
subject { described_class.get_status(builds) }
context 'all builds successful' do
let(:builds) { Array.new(2) { create(:ci_build, :success) } }
it { is_expected.to eq 'success' }
end
context 'at least one build failed' do
let(:builds) { [create(:ci_build, :success), create(:ci_build, :failed)] }
it { is_expected.to eq 'failed' }
end
context 'at least one running' do
let(:builds) { [create(:ci_build, :success), create(:ci_build, :running)] }
it { is_expected.to eq 'running' }
end
context 'at least one pending' do
let(:builds) { [create(:ci_build, :success), create(:ci_build, :pending)] }
it { is_expected.to eq 'running' }
end
context 'build success and failed but allowed to fail' do
let(:builds) { [create(:ci_build, :success), create(:ci_build, :failed, :allowed_to_fail)] }
it { is_expected.to eq 'success' }
end
context 'one build failed but allowed to fail' do
let(:builds) { [create(:ci_build, :failed, :allowed_to_fail)] }
it { is_expected.to eq 'success' }
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 'spec_helper'
describe ArchiveRepositoryService, services: true do describe Gitlab::Workhorse, lib: true do
let(:project) { create(:project) } let(:project) { create(:project) }
subject { ArchiveRepositoryService.new(project, "master", "zip") } let(:subject) { Gitlab::Workhorse }
describe "#execute" do
it "cleans old archives" do
expect(RepositoryArchiveCacheWorker).to receive(:perform_async)
subject.execute(timeout: 0.0)
end
describe "#send_git_archive" do
context "when the repository doesn't have an archive file path" do context "when the repository doesn't have an archive file path" do
before do before do
allow(project.repository).to receive(:archive_metadata).and_return(Hash.new) allow(project.repository).to receive(:archive_metadata).and_return(Hash.new)
end end
it "raises an error" do it "raises an error" do
expect { subject.execute(timeout: 0.0) }.to raise_error(RuntimeError) expect { subject.send_git_archive(project, "master", "zip") }.to raise_error(RuntimeError)
end end
end end
end end
end end
...@@ -247,6 +247,35 @@ describe Ci::Commit, models: true do ...@@ -247,6 +247,35 @@ describe Ci::Commit, models: true do
end end
end end
context 'custom stage with first job allowed to fail' do
let(:yaml) do
{
stages: ['clean', 'test'],
clean_job: {
stage: 'clean',
allow_failure: true,
script: 'BUILD',
},
test_job: {
stage: 'test',
script: 'TEST',
},
}
end
before do
stub_ci_commit_yaml_file(YAML.dump(yaml))
create_builds
end
it 'properly schedules builds' do
expect(commit.builds.pluck(:status)).to contain_exactly('pending')
commit.builds.running_or_pending.each(&:drop)
expect(commit.builds.pluck(:status)).to contain_exactly('pending', 'failed')
end
end
context 'properly creates builds when "when" is defined' do context 'properly creates builds when "when" is defined' do
let(:yaml) do let(:yaml) do
{ {
......
...@@ -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
......
...@@ -68,14 +68,24 @@ describe ProjectTeam, models: true do ...@@ -68,14 +68,24 @@ describe ProjectTeam, models: true do
end end
describe "#human_max_access" do describe "#human_max_access" do
it "return master role" do it 'returns Master role' do
user = create :user user = create(:user)
group = create :group group = create(:group)
group.add_users([user.id], GroupMember::MASTER) group.add_master(user)
project = create(:project, namespace: group)
project.team << [user, :guest] project = build_stubbed(:empty_project, namespace: group)
expect(project.team.human_max_access(user.id)).to eq("Master") expect(project.team.human_max_access(user.id)).to eq 'Master'
end
it 'returns Owner role' do
user = create(:user)
group = create(:group)
group.add_owner(user)
project = build_stubbed(:empty_project, namespace: group)
expect(project.team.human_max_access(user.id)).to eq 'Owner'
end end
end end
end end
...@@ -200,13 +200,22 @@ describe Repository, models: true do ...@@ -200,13 +200,22 @@ describe Repository, models: true do
describe :commit_with_hooks do describe :commit_with_hooks do
context 'when pre hooks were successful' do context 'when pre hooks were successful' do
it 'should run without errors' do before do
expect_any_instance_of(GitHooksService).to receive(:execute).and_return(true) expect_any_instance_of(GitHooksService).to receive(:execute).
and_return(true)
end
it 'should run without errors' do
expect do expect do
repository.commit_with_hooks(user, 'feature') { sample_commit.id } repository.commit_with_hooks(user, 'feature') { sample_commit.id }
end.not_to raise_error end.not_to raise_error
end end
it 'should ensure the autocrlf Git option is set to :input' do
expect(repository).to receive(:update_autocrlf_option)
repository.commit_with_hooks(user, 'feature') { sample_commit.id }
end
end end
context 'when pre hooks failed' do context 'when pre hooks failed' do
...@@ -220,6 +229,25 @@ describe Repository, models: true do ...@@ -220,6 +229,25 @@ describe Repository, models: true do
end end
end end
describe '#exists?' do
it 'returns true when a repository exists' do
expect(repository.exists?).to eq(true)
end
it 'returns false when a repository does not exist' do
expect(repository.raw_repository).to receive(:rugged).
and_raise(Gitlab::Git::Repository::NoRepository)
expect(repository.exists?).to eq(false)
end
it 'returns false when there is no namespace' do
allow(repository).to receive(:path_with_namespace).and_return(nil)
expect(repository.exists?).to eq(false)
end
end
describe '#has_visible_content?' do describe '#has_visible_content?' do
subject { repository.has_visible_content? } subject { repository.has_visible_content? }
...@@ -249,6 +277,33 @@ describe Repository, models: true do ...@@ -249,6 +277,33 @@ describe Repository, models: true do
end end
end end
describe '#update_autocrlf_option' do
describe 'when autocrlf is not already set to :input' do
before do
repository.raw_repository.autocrlf = true
end
it 'sets autocrlf to :input' do
repository.update_autocrlf_option
expect(repository.raw_repository.autocrlf).to eq(:input)
end
end
describe 'when autocrlf is already set to :input' do
before do
repository.raw_repository.autocrlf = :input
end
it 'does nothing' do
expect(repository.raw_repository).to_not receive(:autocrlf=).
with(:input)
repository.update_autocrlf_option
end
end
end
describe '#empty?' do describe '#empty?' do
let(:empty_repository) { create(:project_empty_repo).repository } let(:empty_repository) { create(:project_empty_repo).repository }
...@@ -355,6 +410,17 @@ describe Repository, models: true do ...@@ -355,6 +410,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 } }
......
...@@ -4,6 +4,7 @@ require 'mime/types' ...@@ -4,6 +4,7 @@ require 'mime/types'
describe API::API, api: true do describe API::API, api: true do
include ApiHelpers include ApiHelpers
include RepoHelpers include RepoHelpers
include WorkhorseHelpers
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user2) { create(:user) } let(:user2) { create(:user) }
...@@ -91,21 +92,27 @@ describe API::API, api: true do ...@@ -91,21 +92,27 @@ describe API::API, api: true do
get api("/projects/#{project.id}/repository/archive", user) get api("/projects/#{project.id}/repository/archive", user)
repo_name = project.repository.name.gsub("\.git", "") repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.gz/) type, params = workhorse_send_data
expect(type).to eq('git-archive')
expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.gz/)
end end
it "should get the archive.zip" do it "should get the archive.zip" do
get api("/projects/#{project.id}/repository/archive.zip", user) get api("/projects/#{project.id}/repository/archive.zip", user)
repo_name = project.repository.name.gsub("\.git", "") repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.zip/) type, params = workhorse_send_data
expect(type).to eq('git-archive')
expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.zip/)
end end
it "should get the archive.tar.bz2" do it "should get the archive.tar.bz2" do
get api("/projects/#{project.id}/repository/archive.tar.bz2", user) get api("/projects/#{project.id}/repository/archive.tar.bz2", user)
repo_name = project.repository.name.gsub("\.git", "") repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.bz2/) type, params = workhorse_send_data
expect(type).to eq('git-archive')
expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.bz2/)
end end
it "should return 404 for invalid sha" do it "should return 404 for invalid sha" do
......
require 'spec_helper'
describe Ci::CreateBuildsService, services: true do
let(:commit) { create(:ci_commit) }
let(:user) { create(:user) }
describe '#execute' do
# Using stubbed .gitlab-ci.yml created in commit factory
#
subject do
described_class.new.execute(commit, 'test', 'master', nil, user, nil, status)
end
context 'next builds available' do
let(:status) { 'success' }
it { is_expected.to be_an_instance_of Array }
it { is_expected.to all(be_an_instance_of Ci::Build) }
end
context 'builds skipped' do
let(:status) { 'skipped' }
it { is_expected.to be_empty }
end
end
end
...@@ -5,7 +5,6 @@ describe GitPushService, services: true do ...@@ -5,7 +5,6 @@ describe GitPushService, services: true do
let(:user) { create :user } let(:user) { create :user }
let(:project) { create :project } let(:project) { create :project }
let(:service) { GitPushService.new }
before do before do
@blankrev = Gitlab::Git::BLANK_SHA @blankrev = Gitlab::Git::BLANK_SHA
...@@ -15,10 +14,17 @@ describe GitPushService, services: true do ...@@ -15,10 +14,17 @@ describe GitPushService, services: true do
end end
describe 'Push branches' do describe 'Push branches' do
let(:oldrev) { @oldrev }
let(:newrev) { @newrev }
subject do
execute_service(project, user, oldrev, newrev, @ref )
end
context 'new branch' do context 'new branch' do
subject do
service.execute(project, user, @blankrev, @newrev, @ref) let(:oldrev) { @blankrev }
end
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
...@@ -36,9 +42,6 @@ describe GitPushService, services: true do ...@@ -36,9 +42,6 @@ describe GitPushService, services: true do
end end
context 'existing branch' do context 'existing branch' do
subject do
service.execute(project, user, @oldrev, @newrev, @ref)
end
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
...@@ -50,9 +53,8 @@ describe GitPushService, services: true do ...@@ -50,9 +53,8 @@ describe GitPushService, services: true do
end end
context 'rm branch' do context 'rm branch' do
subject do
service.execute(project, user, @oldrev, @blankrev, @ref) let(:newrev) { @blankrev }
end
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
...@@ -72,7 +74,7 @@ describe GitPushService, services: true do ...@@ -72,7 +74,7 @@ describe GitPushService, services: true do
describe "Git Push Data" do describe "Git Push Data" do
before do before do
service.execute(project, user, @oldrev, @newrev, @ref) service = execute_service(project, user, @oldrev, @newrev, @ref )
@push_data = service.push_data @push_data = service.push_data
@commit = project.commit(@newrev) @commit = project.commit(@newrev)
end end
...@@ -134,20 +136,21 @@ describe GitPushService, services: true do ...@@ -134,20 +136,21 @@ describe GitPushService, services: true do
describe "Push Event" do describe "Push Event" do
before do before do
service.execute(project, user, @oldrev, @newrev, @ref) service = execute_service(project, user, @oldrev, @newrev, @ref )
@event = Event.last @event = Event.last
@push_data = service.push_data
end end
it { expect(@event).not_to be_nil } it { expect(@event).not_to be_nil }
it { expect(@event.project).to eq(project) } it { expect(@event.project).to eq(project) }
it { expect(@event.action).to eq(Event::PUSHED) } it { expect(@event.action).to eq(Event::PUSHED) }
it { expect(@event.data).to eq(service.push_data) } it { expect(@event.data).to eq(@push_data) }
context "Updates merge requests" do context "Updates merge requests" do
it "when pushing a new branch for the first time" do it "when pushing a new branch for the first time" do
expect(project).to receive(:update_merge_requests). expect(project).to receive(:update_merge_requests).
with(@blankrev, 'newrev', 'refs/heads/master', user) with(@blankrev, 'newrev', 'refs/heads/master', user)
service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master') execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
end end
end end
end end
...@@ -158,7 +161,7 @@ describe GitPushService, services: true do ...@@ -158,7 +161,7 @@ describe GitPushService, services: true do
expect(project).to receive(:execute_hooks) expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master") expect(project.default_branch).to eq("master")
expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: false }) expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: false })
service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master') execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
end end
it "when pushing a branch for the first time with default branch protection disabled" do it "when pushing a branch for the first time with default branch protection disabled" do
...@@ -167,7 +170,7 @@ describe GitPushService, services: true do ...@@ -167,7 +170,7 @@ describe GitPushService, services: true do
expect(project).to receive(:execute_hooks) expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master") expect(project.default_branch).to eq("master")
expect(project.protected_branches).not_to receive(:create) expect(project.protected_branches).not_to receive(:create)
service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master') execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
end end
it "when pushing a branch for the first time with default branch protection set to 'developers can push'" do it "when pushing a branch for the first time with default branch protection set to 'developers can push'" do
...@@ -176,12 +179,12 @@ describe GitPushService, services: true do ...@@ -176,12 +179,12 @@ describe GitPushService, services: true do
expect(project).to receive(:execute_hooks) expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master") expect(project.default_branch).to eq("master")
expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: true }) expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: true })
service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master') execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
end end
it "when pushing new commits to existing branch" do it "when pushing new commits to existing branch" do
expect(project).to receive(:execute_hooks) expect(project).to receive(:execute_hooks)
service.execute(project, user, 'oldrev', 'newrev', 'refs/heads/master') execute_service(project, user, 'oldrev', 'newrev', 'refs/heads/master' )
end end
end end
end end
...@@ -204,7 +207,7 @@ describe GitPushService, services: true do ...@@ -204,7 +207,7 @@ describe GitPushService, services: true do
it "creates a note if a pushed commit mentions an issue" do it "creates a note if a pushed commit mentions an issue" do
expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, commit_author) expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, commit_author)
service.execute(project, user, @oldrev, @newrev, @ref) execute_service(project, user, @oldrev, @newrev, @ref )
end end
it "only creates a cross-reference note if one doesn't already exist" do it "only creates a cross-reference note if one doesn't already exist" do
...@@ -212,7 +215,7 @@ describe GitPushService, services: true do ...@@ -212,7 +215,7 @@ describe GitPushService, services: true do
expect(SystemNoteService).not_to receive(:cross_reference).with(issue, commit, commit_author) expect(SystemNoteService).not_to receive(:cross_reference).with(issue, commit, commit_author)
service.execute(project, user, @oldrev, @newrev, @ref) execute_service(project, user, @oldrev, @newrev, @ref )
end end
it "defaults to the pushing user if the commit's author is not known" do it "defaults to the pushing user if the commit's author is not known" do
...@@ -222,7 +225,7 @@ describe GitPushService, services: true do ...@@ -222,7 +225,7 @@ describe GitPushService, services: true do
) )
expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, user) expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, user)
service.execute(project, user, @oldrev, @newrev, @ref) execute_service(project, user, @oldrev, @newrev, @ref )
end end
it "finds references in the first push to a non-default branch" do it "finds references in the first push to a non-default branch" do
...@@ -231,7 +234,7 @@ describe GitPushService, services: true do ...@@ -231,7 +234,7 @@ describe GitPushService, services: true do
expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, commit_author) expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, commit_author)
service.execute(project, user, @blankrev, @newrev, 'refs/heads/other') execute_service(project, user, @blankrev, @newrev, 'refs/heads/other' )
end end
end end
...@@ -255,18 +258,18 @@ describe GitPushService, services: true do ...@@ -255,18 +258,18 @@ describe GitPushService, services: true do
context "to default branches" do context "to default branches" do
it "closes issues" do it "closes issues" do
service.execute(project, user, @oldrev, @newrev, @ref) execute_service(project, user, @oldrev, @newrev, @ref )
expect(Issue.find(issue.id)).to be_closed expect(Issue.find(issue.id)).to be_closed
end end
it "adds a note indicating that the issue is now closed" do it "adds a note indicating that the issue is now closed" do
expect(SystemNoteService).to receive(:change_status).with(issue, project, commit_author, "closed", closing_commit) expect(SystemNoteService).to receive(:change_status).with(issue, project, commit_author, "closed", closing_commit)
service.execute(project, user, @oldrev, @newrev, @ref) execute_service(project, user, @oldrev, @newrev, @ref )
end end
it "doesn't create additional cross-reference notes" do it "doesn't create additional cross-reference notes" do
expect(SystemNoteService).not_to receive(:cross_reference) expect(SystemNoteService).not_to receive(:cross_reference)
service.execute(project, user, @oldrev, @newrev, @ref) execute_service(project, user, @oldrev, @newrev, @ref )
end end
it "doesn't close issues when external issue tracker is in use" do it "doesn't close issues when external issue tracker is in use" do
...@@ -274,7 +277,7 @@ describe GitPushService, services: true do ...@@ -274,7 +277,7 @@ describe GitPushService, services: true do
# The push still shouldn't create cross-reference notes. # The push still shouldn't create cross-reference notes.
expect do expect do
service.execute(project, user, @oldrev, @newrev, 'refs/heads/hurf') execute_service(project, user, @oldrev, @newrev, 'refs/heads/hurf' )
end.not_to change { Note.where(project_id: project.id, system: true).count } end.not_to change { Note.where(project_id: project.id, system: true).count }
end end
end end
...@@ -287,11 +290,11 @@ describe GitPushService, services: true do ...@@ -287,11 +290,11 @@ describe GitPushService, services: true do
it "creates cross-reference notes" do it "creates cross-reference notes" do
expect(SystemNoteService).to receive(:cross_reference).with(issue, closing_commit, commit_author) expect(SystemNoteService).to receive(:cross_reference).with(issue, closing_commit, commit_author)
service.execute(project, user, @oldrev, @newrev, @ref) execute_service(project, user, @oldrev, @newrev, @ref )
end end
it "doesn't close issues" do it "doesn't close issues" do
service.execute(project, user, @oldrev, @newrev, @ref) execute_service(project, user, @oldrev, @newrev, @ref )
expect(Issue.find(issue.id)).to be_opened expect(Issue.find(issue.id)).to be_opened
end end
end end
...@@ -328,7 +331,7 @@ describe GitPushService, services: true do ...@@ -328,7 +331,7 @@ describe GitPushService, services: true do
let(:message) { "this is some work.\n\nrelated to JIRA-1" } let(:message) { "this is some work.\n\nrelated to JIRA-1" }
it "should initiate one api call to jira server to mention the issue" do it "should initiate one api call to jira server to mention the issue" do
service.execute(project, user, @oldrev, @newrev, @ref) execute_service(project, user, @oldrev, @newrev, @ref )
expect(WebMock).to have_requested(:post, jira_api_comment_url).with( expect(WebMock).to have_requested(:post, jira_api_comment_url).with(
body: /mentioned this issue in/ body: /mentioned this issue in/
...@@ -346,7 +349,7 @@ describe GitPushService, services: true do ...@@ -346,7 +349,7 @@ describe GitPushService, services: true do
} }
}.to_json }.to_json
service.execute(project, user, @oldrev, @newrev, @ref) execute_service(project, user, @oldrev, @newrev, @ref )
expect(WebMock).to have_requested(:post, jira_api_transition_url).with( expect(WebMock).to have_requested(:post, jira_api_transition_url).with(
body: transition_body body: transition_body
).once ).once
...@@ -357,7 +360,7 @@ describe GitPushService, services: true do ...@@ -357,7 +360,7 @@ describe GitPushService, services: true do
body: "Issue solved with [#{closing_commit.id}|http://localhost/#{project.path_with_namespace}/commit/#{closing_commit.id}]." body: "Issue solved with [#{closing_commit.id}|http://localhost/#{project.path_with_namespace}/commit/#{closing_commit.id}]."
}.to_json }.to_json
service.execute(project, user, @oldrev, @newrev, @ref) execute_service(project, user, @oldrev, @newrev, @ref )
expect(WebMock).to have_requested(:post, jira_api_comment_url).with( expect(WebMock).to have_requested(:post, jira_api_comment_url).with(
body: comment_body body: comment_body
).once ).once
...@@ -376,7 +379,13 @@ describe GitPushService, services: true do ...@@ -376,7 +379,13 @@ describe GitPushService, services: true do
end end
it 'push to first branch updates HEAD' do it 'push to first branch updates HEAD' do
service.execute(project, user, @blankrev, @newrev, new_ref) execute_service(project, user, @blankrev, @newrev, new_ref )
end end
end end
def execute_service(project, user, oldrev, newrev, ref)
service = described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref )
service.execute
service
end
end end
...@@ -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
module WorkhorseHelpers
extend self
def workhorse_send_data
@_workhorse_send_data ||= begin
header = response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]
split_header = header.split(':')
type = split_header.shift
header = split_header.join(':')
[
type,
JSON.parse(Base64.urlsafe_decode64(header)),
]
end
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