Commit 64f47845 authored by Rémy Coutable's avatar Rémy Coutable

Merge remote-tracking branch 'origin/master' into 8-5-stable

parents c348da5b a4ffdf92
......@@ -5,6 +5,11 @@ services:
- postgres:latest
- redis:latest
cache:
key: "ruby22"
paths:
- vendor
variables:
MYSQL_ALLOW_EMPTY_PASSWORD: "1"
......@@ -144,6 +149,10 @@ spec:feature:ruby21:
script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature
cache:
key: "ruby21"
paths:
- vendor
tags:
- ruby
- mysql
......@@ -154,6 +163,10 @@ spec:api:ruby21:
- master
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api
cache:
key: "ruby21"
paths:
- vendor
tags:
- ruby
- mysql
......@@ -164,6 +177,10 @@ spec:models:ruby21:
- master
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models
cache:
key: "ruby21"
paths:
- vendor
tags:
- ruby
- mysql
......@@ -174,6 +191,10 @@ spec:lib:ruby21:
- master
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib
cache:
key: "ruby21"
paths:
- vendor
tags:
- ruby
- mysql
......@@ -184,6 +205,10 @@ spec:services:ruby21:
- master
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services
cache:
key: "ruby21"
paths:
- vendor
tags:
- ruby
- mysql
......@@ -194,6 +219,10 @@ spec:benchmark:ruby21:
- master
script:
- RAILS_ENV=test bundle exec rake spec:benchmark
cache:
key: "ruby21"
paths:
- vendor
tags:
- ruby
- mysql
......@@ -205,6 +234,10 @@ spec:other:ruby21:
- master
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other
cache:
key: "ruby21"
paths:
- vendor
tags:
- ruby
- mysql
......@@ -215,6 +248,10 @@ spinach:project:half:ruby21:
- master
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
cache:
key: "ruby21"
paths:
- vendor
tags:
- ruby
- mysql
......@@ -225,6 +262,10 @@ spinach:project:rest:ruby21:
- master
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
cache:
key: "ruby21"
paths:
- vendor
tags:
- ruby
- mysql
......@@ -235,6 +276,11 @@ spinach:other:ruby21:
- master
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
cache:
key: "ruby21"
paths:
- vendor
tags:
- ruby
- mysql
Please view this file on the master branch, on stable branches it's out of date.
v 8.5.0 (unreleased)
- Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu)
- Cache various Repository methods to improve performance (Yorick Peterse)
- Fix duplicated branch creation/deletion Web hooks/service notifications when using Web UI (Stan Hu)
- Ensure rake tasks that don't need a DB connection can be run without one
- Update New Relic gem to 3.14.1.311 (Stan Hu)
- Add "visibility" flag to GET /projects api endpoint
......@@ -14,11 +16,14 @@ v 8.5.0 (unreleased)
- New UI for pagination
- Don't prevent sign out when 2FA enforcement is enabled and user hasn't yet
set it up
- API: Added "merge_requests/:merge_request_id/closes_issues" (Gal Schlezinger)
- Fix diff comments loaded by AJAX to load comment with diff in discussion tab
- Fix relative links in other markup formats (Ben Boeckel)
- Whitelist raw "abbr" elements when parsing Markdown (Benedict Etzel)
- Fix label links for a merge request pointing to issues list
- Don't vendor minified JS
- Increase project import timeout to 15 minutes
- Be more permissive with email address validation: it only has to contain a single '@'
- Display 404 error on group not found
- Track project import failure
- Support Two-factor Authentication for LDAP users
......@@ -29,28 +34,40 @@ v 8.5.0 (unreleased)
- Update the ExternalIssue regex pattern (Blake Hitchcock)
- 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
- 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)
- Revert "Add IP check against DNSBLs at account sign-up"
- Actually use the `skip_merges` option in Repository#commits (Tony Chu)
- Fix API to keep request parameters in Link header (Michael Potthoff)
- Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead
- Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead
- Prevent parse error when name of project ends with .atom and prevent path issues
- Mark inline difference between old and new paths when a file is renamed
- Support Akismet spam checking for creation of issues via API (Stan Hu)
- API: Allow to set or update a merge-request's milestone (Kirill Skachkov)
- Improve UI consistency between projects and groups lists
- Add sort dropdown to dashboard projects page
- 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)
- 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
- Faster snippet search
- Title for milestones should be unique (Zeger-Jan van de Weg)
- Validate correctness of maximum attachment size application setting
- 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 broken link to project in build notification emails
v 8.4.4
- Update omniauth-saml gem to 1.4.2
- Prevent long-running backup tasks from timing out the database connection
- Add a Project setting to allow guests to view build logs (defaults to true)
- Sort project milestones by due date including issue editor (Oliver Rogers / Orih)
v 8.4.3
- Increase lfs_objects size column to 8-byte integer to allow files larger
......@@ -386,6 +403,7 @@ v 8.1.0
- Improved performance of the trending projects page
- Remove CI migration task
- Improved performance of finding projects by their namespace
- Add assignee data to Issuables' hook_data (Bram Daams)
- Fix bug where transferring a project would result in stale commit links (Stan Hu)
- Fix build trace updating
- Include full path of source and target branch names in New Merge Request page (Stan Hu)
......
......@@ -364,6 +364,7 @@ corresponding merge request should be updated to have the following:
This makes it easier for release managers to keep track of what still has to be
merged and where changes have to be merged into.
Like all merge requests the target should be master so all bugfixes are in master.
## Definition of done
......
......@@ -21,7 +21,7 @@ gem "pg", '~> 0.18.2', group: :postgres
gem 'devise', '~> 3.5.4'
gem 'devise-async', '~> 0.9.0'
gem 'doorkeeper', '~> 2.2.0'
gem 'omniauth', '~> 1.2.2'
gem 'omniauth', '~> 1.3.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6'
gem 'omniauth-bitbucket', '~> 0.0.2'
gem 'omniauth-cas3', '~> 1.1.2'
......@@ -105,7 +105,7 @@ gem 'rouge', '~> 1.10.1'
# See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s
# and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM
gem 'nokogiri', '1.6.7.2'
gem 'nokogiri', '~> 1.6.7', '>= 1.6.7.2'
# Diffs
gem 'diffy', '~> 3.0.3'
......
......@@ -50,7 +50,7 @@ GEM
after_commit_queue (1.3.0)
activerecord (>= 3.0)
akismet (2.0.0)
allocations (1.0.3)
allocations (1.0.4)
annotate (2.6.10)
activerecord (>= 3.2, <= 4.3)
rake (~> 10.4)
......@@ -491,9 +491,9 @@ GEM
rack (~> 1.2)
octokit (3.8.0)
sawyer (~> 0.6.0, >= 0.5.3)
omniauth (1.2.2)
omniauth (1.3.1)
hashie (>= 1.2, < 4)
rack (~> 1.0)
rack (>= 1.0, < 3)
omniauth-azure-oauth2 (0.0.6)
jwt (~> 1.0)
omniauth (~> 1.0)
......@@ -959,11 +959,11 @@ DEPENDENCIES
mysql2 (~> 0.3.16)
nested_form (~> 0.3.2)
net-ssh (~> 3.0.1)
nokogiri (= 1.6.7.2)
nokogiri (~> 1.6.7, >= 1.6.7.2)
nprogress-rails (~> 0.1.6.7)
oauth2 (~> 1.0.0)
octokit (~> 3.8.0)
omniauth (~> 1.2.2)
omniauth (~> 1.3.1)
omniauth-azure-oauth2 (~> 0.0.6)
omniauth-bitbucket (~> 0.0.2)
omniauth-cas3 (~> 1.1.2)
......
......@@ -49,10 +49,11 @@ class @AwardsHandler
counter.text(parseInt(counter.text()) - 1)
emojiIcon.removeClass("active")
@removeMeFromAuthorList(emoji)
else if emoji =="thumbsup" || emoji == "thumbsdown"
else if emoji == "thumbsup" || emoji == "thumbsdown"
emojiIcon.tooltip("destroy")
counter.text(0)
emojiIcon.removeClass("active")
@removeMeFromAuthorList(emoji)
else
emojiIcon.tooltip("destroy")
emojiIcon.remove()
......
@Dashboard =
init: ->
$(".projects-list-filter").off('keyup')
this.initSearch()
initSearch: ->
@timer = null
$("#project-filter-form-field").on('keyup', ->
$(".projects-list-filter").on('keyup', ->
clearTimeout(@timer)
@timer = setTimeout(Dashboard.filterResults, 500)
)
......@@ -13,8 +14,8 @@
$('.projects-list-holder').fadeTo(250, 0.5)
form = null
form = $("#project-filter-form")
search = $("#project-filter-form-field").val()
form = $("form#project-filter-form")
search = $(".projects-list-filter").val()
project_filter_url = form.attr('action') + '?' + form.serialize()
$.ajax
......@@ -24,7 +25,7 @@
complete: ->
$('.projects-list-holder').fadeTo(250, 1)
success: (data) ->
$('div.projects-list-holder').replaceWith(data.html)
$('.projects-list-holder').replaceWith(data.html)
# Change url so if user reload a page - search results are saved
history.replaceState {page: project_filter_url}, document.title, project_filter_url
dataType: "json"
......@@ -16,6 +16,8 @@ class Dispatcher
shortcut_handler = null
switch page
when 'explore:projects:index', 'explore:projects:starred', 'explore:projects:trending'
Dashboard.init()
when 'projects:issues:index'
Issues.init()
shortcut_handler = new ShortcutsNavigation()
......
......@@ -44,6 +44,7 @@ $(document).on('page:fetch', start)
$(document).on('page:change', stop)
$ ->
# Make logo clickable
# Make logo clickable as part of a workaround for Safari visited
# link behaviour (See !2690).
$('#logo').on 'click', ->
$('#js-shortcuts-home').get(0).click()
......@@ -3,22 +3,24 @@ class @ProjectsList
$(".projects-list .js-expand").on 'click', (e) ->
e.preventDefault()
list = $(this).closest('.projects-list')
list.find("li").show()
list.find("li.bottom").hide()
$(".projects-list-filter").keyup ->
terms = $(this).val()
uiBox = $('div.projects-list-holder')
filterSelector = $(this).data('filter-selector') || 'span.filter-title'
$("#filter_projects").on 'keyup', ->
ProjectsList.filter_results($("#filter_projects"))
if terms == "" || terms == undefined
uiBox.find("ul.projects-list li").show()
@filter_results: ($element) ->
terms = $element.val()
filterSelector = $element.data('filter-selector') || 'span.filter-title'
if not terms
$(".projects-list li").show()
$('.gl-pagination').show()
else
uiBox.find("ul.projects-list li").each (index) ->
name = $(this).find(filterSelector).text()
$(".projects-list li").each (index) ->
$this = $(this)
name = $this.find(filterSelector).text()
if name.toLowerCase().search(terms.toLowerCase()) == -1
$(this).hide()
if name.toLowerCase().indexOf(terms.toLowerCase()) == -1
$this.hide()
else
$(this).show()
uiBox.find("ul.projects-list li.bottom").hide()
$this.show()
$('.gl-pagination').hide()
......@@ -118,3 +118,19 @@ body {
@include gitlab-theme(#9988CC, $theme-violet, #443366, #332255);
}
}
::-webkit-scrollbar{
width: 3px;
}
::-webkit-scrollbar-thumb{
background-color:$theme-charcoal; border-radius: 0;
}
::-webkit-scrollbar-thumb:hover{
background-color:$theme-charcoal;
}
::-webkit-scrollbar-track{
background-color:#FFF;
}
\ No newline at end of file
......@@ -12,6 +12,14 @@
.identifier {
color: #5c5d5e;
}
.issue_created_ago, .author_link {
white-space: nowrap;
}
.issue-meta {
margin-left: 65px
}
}
.detail-page-description {
......
......@@ -80,6 +80,10 @@
display: inline-block;
}
.select2-container span {
margin-top: 0;
}
.issuable-count {
}
......@@ -88,6 +92,10 @@
margin-left: 20px;
border-left: 1px solid $border-gray-light;
padding-left: 10px;
&:hover {
color: $gray-darkest;
}
}
}
......@@ -192,6 +200,10 @@
.btn {
background: $gray-normal;
border: 1px solid $border-gray-normal;
&:hover {
background: $gray-dark;
border: 1px solid $border-gray-dark;
}
}
&.right-sidebar-collapsed {
......@@ -223,6 +235,19 @@
display: block;
margin-top: 0;
}
.btn-clipboard {
border: none;
&:hover {
background: transparent;
}
i {
color: #999999;
}
}
}
}
......
......@@ -164,7 +164,7 @@ class ApplicationController < ActionController::Base
end
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
def method_missing(method_sym, *arguments, &block)
......
......@@ -12,9 +12,13 @@ module Ci
# Project status badge
# Image with build status for sha or ref
#
# This action in DEPRECATED, this is here only for backwards compatibility
# with projects migrated from GitLab CI.
#
def badge
return render_404 unless @project
image = Ci::ImageForBuildService.new.execute(@project, params)
send_file image.path, filename: image.name, disposition: 'inline', type:"image/svg+xml"
end
......
......@@ -12,7 +12,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = @projects.search(terms)
end
@projects = @projects.page(params[:page]).per(PER_PAGE)
@projects = @projects.page(params[:page]).per(PER_PAGE) if terms.blank?
@last_push = current_user.recent_push
respond_to do |format|
......@@ -41,7 +41,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = @projects.search(terms)
end
@projects = @projects.page(params[:page]).per(PER_PAGE)
@projects = @projects.page(params[:page]).per(PER_PAGE) if terms.blank?
@last_push = current_user.recent_push
@groups = []
......
......@@ -6,19 +6,49 @@ class Explore::ProjectsController < Explore::ApplicationController
@projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
@projects = @projects.non_archived
@projects = @projects.search(params[:search]) if params[:search].present?
@projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).page(params[:page]).per(PER_PAGE)
@projects = @projects.includes(:namespace).page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
respond_to do |format|
format.html
format.json do
render json: {
html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
}
end
end
end
def trending
@projects = TrendingProjectsFinder.new.execute(current_user)
@projects = @projects.non_archived
@projects = @projects.page(params[:page]).per(PER_PAGE)
@projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
@projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
respond_to do |format|
format.html
format.json do
render json: {
html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
}
end
end
end
def starred
@projects = ProjectsFinder.new.execute(current_user)
@projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
@projects = @projects.reorder('star_count DESC')
@projects = @projects.page(params[:page]).per(PER_PAGE)
@projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
respond_to do |format|
format.html
format.json do
render json: {
html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
}
end
end
end
end
......@@ -14,7 +14,7 @@ class GroupsController < Groups::ApplicationController
# Load group projects
before_action :load_projects, except: [:index, :new, :create, :projects, :edit, :update, :autocomplete]
before_action :event_filter, only: :show
before_action :event_filter, only: [:show, :events]
layout :determine_layout
......@@ -41,14 +41,16 @@ class GroupsController < Groups::ApplicationController
def show
@last_push = current_user.recent_push if current_user
@projects = @projects.includes(:namespace)
@projects = @projects.page(params[:page]).per(PER_PAGE)
@projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
@projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
respond_to do |format|
format.html
format.json do
load_events
pager_json("events/_events", @events.count)
render json: {
html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
}
end
format.atom do
......@@ -58,6 +60,15 @@ class GroupsController < Groups::ApplicationController
end
end
def events
respond_to do |format|
format.json do
load_events
pager_json("events/_events", @events.count)
end
end
end
def edit
end
......
class Projects::BadgesController < Projects::ApplicationController
def build
respond_to do |format|
format.html { render_404 }
format.svg do
image = Ci::ImageForBuildService.new.execute(project, ref: params[:ref])
send_file(image.path, filename: image.name, disposition: 'inline', type: 'image/svg+xml')
end
end
end
end
class Projects::BuildsController < Projects::ApplicationController
before_action :build, except: [:index, :cancel_all]
before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry]
before_action :authorize_update_build!, except: [:index, :show, :status]
layout "project"
layout 'project'
def index
@scope = params[:scope]
......@@ -24,7 +22,6 @@ class Projects::BuildsController < Projects::ApplicationController
def cancel_all
@project.builds.running_or_pending.each(&:cancel)
redirect_to namespace_project_builds_path(project.namespace, project)
end
......@@ -47,20 +44,18 @@ class Projects::BuildsController < Projects::ApplicationController
end
build = Ci::Build.retry(@build)
redirect_to build_path(build)
end
def status
render json: @build.to_json(only: [:status, :id, :sha, :coverage], methods: :sha)
end
def cancel
@build.cancel
redirect_to build_path(@build)
end
def status
render json: @build.to_json(only: [:status, :id, :sha, :coverage], methods: :sha)
end
private
def build
......
......@@ -11,8 +11,6 @@ class Projects::CommitController < Projects::ApplicationController
before_action :define_show_vars, only: [:show, :builds]
def show
return git_not_found! unless @commit
apply_diff_view_cookie!
@line_notes = commit.notes.inline
......@@ -68,6 +66,8 @@ class Projects::CommitController < Projects::ApplicationController
end
def define_show_vars
return git_not_found! unless commit
if params[:w].to_i == 1
@diffs = commit.diffs({ ignore_whitespace_change: true })
else
......
......@@ -21,6 +21,9 @@ class Projects::CommitsController < Projects::ApplicationController
@note_counts = project.notes.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
@merge_request = @project.merge_requests.opened.
find_by(source_project: @project, source_branch: @ref, target_branch: @repository.root_ref)
respond_to do |format|
format.html
format.json { pager_json("projects/commits/_commits", @commits.size) }
......
......@@ -4,24 +4,23 @@ class Projects::CompareController < Projects::ApplicationController
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!
before_action :assign_ref_vars, only: [:index, :show]
before_action :merge_request, only: [:index, :show]
def index
@ref = Addressable::URI.unescape(params[:to])
end
def show
base_ref = Addressable::URI.unescape(params[:from])
@ref = head_ref = Addressable::URI.unescape(params[:to])
diff_options = { ignore_whitespace_change: true } if params[:w] == '1'
compare_result = CompareService.new.
execute(@project, head_ref, @project, base_ref, diff_options)
execute(@project, @head_ref, @project, @base_ref, diff_options)
if compare_result
@commits = Commit.decorate(compare_result.commits, @project)
@diffs = compare_result.diffs
@commit = @project.commit(head_ref)
@base_commit = @project.merge_base_commit(base_ref, head_ref)
@commit = @project.commit(@head_ref)
@base_commit = @project.merge_base_commit(@base_ref, @head_ref)
@diff_refs = [@base_commit, @commit]
@line_notes = []
end
......@@ -31,4 +30,16 @@ class Projects::CompareController < Projects::ApplicationController
redirect_to namespace_project_compare_path(@project.namespace, @project,
params[:from], params[:to])
end
private
def assign_ref_vars
@base_ref = Addressable::URI.unescape(params[:from])
@ref = @head_ref = Addressable::URI.unescape(params[:to])
end
def merge_request
@merge_request ||= @project.merge_requests.opened.
find_by(source_project: @project, source_branch: @head_ref, target_branch: @base_ref)
end
end
......@@ -3,6 +3,7 @@ class Projects::ImportsController < Projects::ApplicationController
before_action :authorize_admin_project!
before_action :require_no_repo, only: [:new, :create]
before_action :redirect_if_progress, only: [:new, :create]
before_action :redirect_if_no_import, only: :show
def new
end
......@@ -63,14 +64,19 @@ class Projects::ImportsController < Projects::ApplicationController
def require_no_repo
if @project.repository_exists?
redirect_to(namespace_project_path(@project.namespace, @project))
redirect_to namespace_project_path(@project.namespace, @project)
end
end
def redirect_if_progress
if @project.import_in_progress?
redirect_to namespace_project_import_path(@project.namespace, @project) &&
return
redirect_to namespace_project_import_path(@project.namespace, @project)
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
......@@ -11,10 +11,11 @@ class Projects::MilestonesController < Projects::ApplicationController
respond_to :html
def index
@milestones = case params[:state]
when 'all'; @project.milestones.order("state, due_date DESC")
when 'closed'; @project.milestones.closed.order("due_date DESC")
else @project.milestones.active.order("due_date ASC")
@milestones =
case params[:state]
when 'all' then @project.milestones.reorder(due_date: :desc, title: :asc)
when 'closed' then @project.milestones.closed.reorder(due_date: :desc, title: :asc)
else @project.milestones.active.reorder(due_date: :asc, title: :asc)
end
@milestones = @milestones.includes(:project)
......
......@@ -212,8 +212,7 @@ module ApplicationHelper
file_content
end
else
GitHub::Markup.render(file_name, file_content).
force_encoding(file_content.encoding).html_safe
other_markup(file_name, file_content)
end
rescue RuntimeError
simple_format(file_content)
......@@ -281,76 +280,6 @@ module ApplicationHelper
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)
titles = {
opened: "Open"
......
......@@ -69,7 +69,7 @@ module DiffHelper
end
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
def organize_comments(type_left, type_right, line_code_left, line_code_right)
......
......@@ -78,6 +78,21 @@ module GitlabMarkdownHelper
)
end
def other_markup(file_name, text)
Gitlab::OtherMarkup.render(
file_name,
text,
project: @project,
current_user: (current_user if defined?(current_user)),
# RelativeLinkFilter
project_wiki: @project_wiki,
requested_path: @path,
ref: @ref,
commit: @commit
)
end
# Return the first line of +text+, up to +max_chars+, after parsing the line
# as Markdown. HTML tags in the parsed output are not counted toward the
# +max_chars+ limit. If the length limit falls within a tag's contents, then
......
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
......@@ -44,14 +44,14 @@ module IssuesHelper
end
def bulk_update_milestone_options
milestones = project_active_milestones.to_a
milestones = @project.milestones.active.reorder(due_date: :asc, title: :asc).to_a
milestones.unshift(Milestone::None)
options_from_collection_for_select(milestones, 'id', 'title', params[:milestone_id])
end
def milestone_options(object)
milestones = object.project.milestones.active.to_a
milestones = object.project.milestones.active.reorder(due_date: :asc, title: :asc).to_a
milestones.unshift(Milestone::None)
options_from_collection_for_select(milestones, 'id', 'title', object.milestone_id)
......
......@@ -3,18 +3,6 @@ module NavHelper
cookies[:collapsed_nav] == 'true'
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
if nav_menu_collapsed?
"sidebar-collapsed"
......
......@@ -98,10 +98,6 @@ module ProjectsHelper
project_nav_tabs.include? name
end
def project_active_milestones
@project.milestones.active.order("due_date, title ASC")
end
def project_for_deploy_key(deploy_key)
if deploy_key.projects.include?(@project)
@project
......
......@@ -3,26 +3,27 @@ module Emails
def build_fail_email(build_id, to)
@build = Ci::Build.find(build_id)
@project = @build.project
add_project_headers
add_build_headers
headers['X-GitLab-Build-Status'] = "failed"
add_build_headers('failed')
mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha))
end
def build_success_email(build_id, to)
@build = Ci::Build.find(build_id)
@project = @build.project
add_project_headers
add_build_headers
headers['X-GitLab-Build-Status'] = "success"
add_build_headers('success')
mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha))
end
private
def add_build_headers
def add_build_headers(status)
headers['X-GitLab-Build-Id'] = @build.id
headers['X-GitLab-Build-Ref'] = @build.ref
headers['X-GitLab-Build-Status'] = status.to_s
end
end
end
......@@ -71,8 +71,8 @@ class ApplicationSetting < ActiveRecord::Base
url: true
validates :admin_notification_email,
allow_blank: true,
email: true
email: true,
allow_blank: true
validates :two_factor_grace_period,
numericality: { greater_than_or_equal_to: 0 }
......
......@@ -126,17 +126,17 @@ module Issuable
end
def to_hook_data(user)
{
hook_data = {
object_kind: self.class.name.underscore,
user: user.hook_attrs,
repository: {
name: project.name,
url: project.url_to_repo,
description: project.description,
homepage: project.web_url
},
object_attributes: hook_attrs
project: project.hook_attrs,
object_attributes: hook_attrs,
# DEPRECATED
repository: project.hook_attrs.slice(:name, :url, :description, :homepage)
}
hook_data.merge!(assignee: assignee.hook_attrs) if assignee
hook_data
end
def label_names
......
......@@ -15,7 +15,7 @@ class Email < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
validates :email, presence: true, email: { strict_mode: true }, uniqueness: true
validates :email, presence: true, uniqueness: true, email: true
validate :unique_email, if: ->(email) { email.email_changed? }
before_validation :cleanup_email
......
......@@ -39,7 +39,6 @@ class Member < ActiveRecord::Base
if: :invite?
},
email: {
strict_mode: true,
allow_nil: true
},
uniqueness: {
......
......@@ -137,6 +137,7 @@ class MergeRequest < ActiveRecord::Base
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 :of_projects, ->(ids) { where(target_project_id: ids) }
scope :opened, -> { with_state(:opened) }
scope :merged, -> { with_state(:merged) }
scope :closed, -> { with_state(:closed) }
scope :closed_and_merged, -> { with_states(:closed, :merged) }
......@@ -240,7 +241,7 @@ class MergeRequest < ActiveRecord::Base
return unless unchecked?
can_be_merged =
project.repository.can_be_merged?(source_sha, target_branch)
!broken? && project.repository.can_be_merged?(source_sha, target_branch)
if can_be_merged
mark_as_mergeable
......
......@@ -342,7 +342,7 @@ class Project < ActiveRecord::Base
end
def repository
@repository ||= Repository.new(path_with_namespace, nil, self)
@repository ||= Repository.new(path_with_namespace, self)
end
def commit(id = 'HEAD')
......@@ -382,6 +382,10 @@ class Project < ActiveRecord::Base
external_import? || forked?
end
def no_import?
import_status == 'none'
end
def external_import?
import_url.present?
end
......@@ -738,11 +742,20 @@ class Project < ActiveRecord::Base
def hook_attrs
{
name: name,
ssh_url: ssh_url_to_repo,
http_url: http_url_to_repo,
description: description,
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,
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
......
......@@ -112,7 +112,7 @@ class PushoverService < Service
priority: priority,
title: "#{project.name_with_namespace}",
message: message,
url: data[:repository][:homepage],
url: data[:project][:web_url],
url_title: "See project #{project.name_with_namespace}"
}
......
......@@ -123,7 +123,7 @@ class ProjectWiki
end
def repository
Repository.new(path_with_namespace, default_branch, @project)
Repository.new(path_with_namespace, @project)
end
def default_branch
......
......@@ -15,7 +15,7 @@ class Repository
Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
end
def initialize(path_with_namespace, default_branch = nil, project = nil)
def initialize(path_with_namespace, project)
@path_with_namespace = path_with_namespace
@project = project
end
......@@ -84,7 +84,8 @@ class Repository
offset: offset,
# --follow doesn't play well with --skip. See:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
follow: false
follow: false,
skip_merges: skip_merges
}
commits = Gitlab::Git::Commit.where(options)
......@@ -237,6 +238,15 @@ class Repository
expire_branch_cache(branch_name)
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)
# 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
......@@ -257,6 +267,14 @@ class Repository
@root_ref = nil
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
cache.expire(:has_visible_content?)
@has_visible_content = nil
......@@ -610,6 +628,8 @@ class Repository
end
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)
rescue Rugged::ReferenceError
nil
......
......@@ -146,11 +146,8 @@ class User < ActiveRecord::Base
# Validations
#
validates :name, presence: true
# Note that a 'uniqueness' and presence check is provided by devise :validatable for email. We do not need to
# duplicate that here as the validation framework will have duplicate errors in the event of a failure.
validates :email, presence: true, email: { strict_mode: true }
validates :notification_email, presence: true, email: { strict_mode: true }
validates :public_email, presence: true, email: { strict_mode: true }, allow_blank: true, uniqueness: true
validates :notification_email, presence: true, email: true
validates :public_email, presence: true, uniqueness: true, email: true, allow_blank: true
validates :bio, length: { maximum: 255 }, allow_blank: true
validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 }
validates :username,
......
module Ci
class ImageForBuildService
def execute(project, params)
sha = params[:sha]
sha ||=
if params[:ref]
project.commit(params[:ref]).try(:sha)
end
def execute(project, opts)
sha = opts[:sha] || ref_sha(project, opts[:ref])
commit = project.ci_commits.ordered.find_by(sha: sha)
image_name = image_for_commit(commit)
image_path = Rails.root.join('public/ci', image_name)
OpenStruct.new(
path: image_path,
name: image_name
)
OpenStruct.new(path: image_path, name: image_name)
end
private
def ref_sha(project, ref)
project.commit(ref).try(:sha) if ref
end
def image_for_commit(commit)
return 'build-unknown.svg' unless commit
'build-' + commit.status + ".svg"
end
end
......
......@@ -29,11 +29,7 @@ class CreateBranchService < BaseService
end
if new_branch
push_data = build_push_data(project, current_user, new_branch)
project.execute_hooks(push_data.dup, :push_hooks)
project.execute_services(push_data.dup, :push_hooks)
# GitPushService handles execution of services and hooks for branch pushes
success(new_branch)
else
error('Invalid reference name')
......
......@@ -25,11 +25,7 @@ class DeleteBranchService < BaseService
end
if repository.rm_branch(current_user, branch_name)
push_data = build_push_data(branch)
project.execute_hooks(push_data.dup, :push_hooks)
project.execute_services(push_data.dup, :push_hooks)
# GitPushService handles execution of services and hooks for branch pushes
success('Branch was removed')
else
error('Failed to remove branch')
......
......@@ -16,11 +16,15 @@ module Projects
return false unless can?(current_user, :remove_project, project)
project.team.truncate
project.repository.expire_cache unless project.empty_repo?
repo_path = project.path_with_namespace
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.destroy!
......@@ -70,5 +74,13 @@ module Projects
def removal_path(path)
"#{path}+#{project.id}#{DELETED_FLAG}"
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
......@@ -274,12 +274,15 @@ class SystemNoteService
# 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
# 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
# mentioner - Mentionable object
#
# Returns Boolean
def self.cross_reference_exists?(noteable, mentioner)
# Initial scope should be system notes of this noteable type
notes = Note.system.where(noteable_type: noteable.class)
......@@ -291,14 +294,20 @@ class SystemNoteService
notes = notes.where(noteable_id: noteable.id)
end
gfm_reference = mentioner.gfm_reference(noteable.project)
notes = notes.where(note: cross_reference_note_content(gfm_reference))
notes.count > 0
notes_for_mentioner(mentioner, noteable, notes).count > 0
end
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 = {})
Note.create(args.merge(system: true))
end
......
# EmailValidator
#
# Based on https://github.com/balexand/email_validator
#
# Extended to use only strict mode with following allowed characters:
# ' - apostrophe
#
# See http://www.remote.org/jochen/mail/info/chars.html
#
class EmailValidator < ActiveModel::EachValidator
PATTERN = /\A\s*([-a-z0-9+._']{1,64})@((?:[-a-z0-9]+\.)+[a-z]{2,})\s*\z/i.freeze
def validate_each(record, attribute, value)
unless value =~ PATTERN
record.errors.add(attribute, options[:message] || :invalid)
end
record.errors.add(attribute, :invalid) unless value =~ Devise.email_regexp
end
end
......@@ -14,7 +14,7 @@
.nav-controls
= form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short', spellcheck: false, id: 'project-filter-form-field'
= search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short projects-list-filter', spellcheck: false, id: 'project-filter-form-field'
= render 'explore/projects/dropdown'
- if current_user.can_create_project?
= link_to new_project_path, class: 'btn btn-new' do
......
.pull-left
= form_tag explore_projects_filter_path, method: :get, class: 'form-inline form-tiny' do |f|
.form-group
= search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "projects_search", spellcheck: false
= hidden_field_tag :sort, @sort
.form-group
= button_tag 'Search', class: "btn"
.pull-right.hidden-sm.hidden-xs
- if current_user
.dropdown.inline.append-right-10
......
- if projects.any?
.public-projects
.projects-list-holder
= render 'shared/projects/list', projects: projects
- else
.nothing-here-block
......
.projects-list-holder.prepend-top-default
.input-group
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
- if can? current_user, :create_projects, @group
%span.input-group-btn
= link_to new_project_path(namespace_id: @group.id), class: 'btn btn-new' do
.top-area
.nav-controls
= form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'input-short project-filter-form-field form-control projects-list-filter', spellcheck: false, id: 'project-filter-form-field'
- if current_user && current_user.can_create_project?
= link_to new_project_path, class: 'btn btn-new' do
= icon('plus')
New Project
.projects-list-holder
= render 'shared/projects/list', projects: @projects, projects_limit: 20, stars: false, skip_namespace: true
......@@ -47,7 +47,7 @@
= render 'shared/event_filter'
.content_list
.content_list{data: {href: events_group_path}}
= spinner
.tab-pane#projects
......
- content_for :header do
%h1{style: "background: #c40834; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"}
GitLab (build failed)
%h3
Project:
= link_to ci_project_url(@project) do
= link_to namespace_project_url(@project.namespace, @project) do
= @project.name
%p
......
......@@ -4,7 +4,7 @@
%h3
Project:
= link_to ci_project_url(@project) do
= link_to namespace_project_url(@project.namespace, @project) do
= @project.name
%p
......
......@@ -36,7 +36,7 @@
= render "download", blob: blob
- elsif blob.text?
- if blob_svg?(blob)
= render "image", blob: sanitize_svg(blob)
= render "image", blob: blob
- else
= render "text", blob: blob
- elsif blob.image?
......
.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)}
......@@ -11,7 +11,10 @@
= render 'shared/ref_switcher', destination: 'commits'
.block-controls.hidden-xs.hidden-sm
- if create_mr_button?(@repository.root_ref, @ref)
- if @merge_request.present?
.control
= link_to "View Open Merge Request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn'
- elsif create_mr_button?(@repository.root_ref, @ref)
.control
= link_to create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' do
= icon('plus')
......
......@@ -13,12 +13,13 @@
= text_field_tag :to, params[:to], class: "form-control", required: true
&nbsp;
= button_tag "Compare", class: "btn btn-create commits-compare-btn"
- if create_mr_button?
- if @merge_request.present?
= link_to "View Open Merge Request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'prepend-left-10 btn'
- elsif create_mr_button?
= link_to create_mr_path, class: 'prepend-left-10 btn' do
= icon("plus")
Create Merge Request
:javascript
var availableTags = #{@project.repository.ref_names.to_json};
......
- 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
.image
%span.wrap
.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}"
- else
.image
......@@ -11,7 +13,7 @@
%span.wrap
.frame.deleted
%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
%span.meta-filesize= "#{number_to_human_size old_file.size}"
|
......@@ -23,7 +25,7 @@
%span.wrap
.frame.added
%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
%span.meta-filesize= "#{number_to_human_size file.size}"
|
......@@ -36,10 +38,10 @@
%div.swipe.view.hide
.swipe-frame
.frame.deleted
%img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
%img{src: old_file_raw_path}
.swipe-wrap
.frame.added
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
%img{src: file_raw_path}
%span.swipe-bar
%span.top-handle
%span.bottom-handle
......@@ -47,9 +49,9 @@
%div.onion-skin.view.hide
.onion-skin-frame
.frame.deleted
%img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
%img{src: old_file_raw_path}
.frame.added
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
%img{src: file_raw_path}
.controls
.transparent
.drag-track
......
......@@ -6,7 +6,7 @@
== #{pluralize(@all_forks.size, 'fork')}: #{full_count_title}
.nav-controls
= search_field_tag :filter_projects, nil, placeholder: 'Search forks', class: 'projects-list-filter form-control input-short',
= search_field_tag :filter_projects, nil, placeholder: 'Search forks', class: 'projects-list-filter project-filter-form-field form-control input-short',
spellcheck: false, data: { 'filter-selector' => 'span.namespace-name' }
.dropdown
......@@ -29,6 +29,7 @@
= link_to page_filter_path(sort: sort_value_oldest_updated, without: excluded_filters) do
= sort_title_oldest_updated
- if current_user && can?(current_user, :fork_project, @project)
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-new' do
= icon('code-fork fw')
......
......@@ -6,16 +6,6 @@
.issue
.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
- 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
......@@ -29,6 +19,19 @@
= icon('pencil-square-o')
Edit
.pull-left
.status-box{ class: "status-box-closed #{issue_button_visibility(@issue, false)}"} Closed
.status-box{ class: "status-box-open #{issue_button_visibility(@issue, true)}"} Open
.issue-meta
%span.identifier
Issue ##{@issue.iid}
%span.creator
&middot;
by #{link_to_member(@project, @issue.author, size: 24)}
&middot;
= time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago')
.issue-details.issuable-details
.detail-page-description.content-block
%h2.title
......
$('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}";
$('aside.right-sidebar').effect('highlight');
new Issue();
\ No newline at end of file
new IssuableContext();
$('aside.right-sidebar')[0].outerHTML= "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}";
$('aside.right-sidebar').effect('highlight')
merge_request = new MergeRequest();
$('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}";
$('aside.right-sidebar').effect('highlight');
new IssuableContext();
%article.file-holder.readme-holder
.file-title
= blob_icon readme.mode, readme.name
= link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)) do
= link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, @path, readme.name)) do
%strong
= readme.name
.file-content.wiki
......
......@@ -3,9 +3,11 @@
Secret Variables
%p.light
These variables will be set to environment by the runner and will be hidden in the build log.
These variables will be set to environment by the runner.
%br
So you can use them for passwords, secret keys or whatever you want.
%br
The value of the variable can be visible in build log if explicitly asked to do so.
%hr
......
<svg width="36px" height="36px" viewBox="0 0 210 210" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="tanuki-logo">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="logo" sketch:type="MSLayerGroup" transform="translate(0.000000, 10.000000)">
<g id="Page-1" sketch:type="MSShapeGroup">
<g id="Fill-1-+-Group-24">
<g id="Group-24">
<g id="Group">
<path id="tanuki-right-ear" d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 L12.2685,74.7342 Z" fill="#E24329" class="tanuki-shape"></path>
<path id="tanuki-right-cheek" d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 L12.2685,74.7341 Z" fill="#FCA326" class="tanuki-shape"></path>
<path id="tanuki-right-eye" d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" fill="#FC6D26" class="tanuki-shape"></path>
<path id="tanuki-nose" d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" fill="#E24329" class="tanuki-shape"></path>
<path id="tanuki-left-eye" d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" fill="#FC6D26" class="tanuki-shape"></path>
<path id="tanuki-left-cheek" d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 L197.8544,74.7341 Z" fill="#FCA326" class="tanuki-shape"></path>
<path id="tanuki-left-ear" d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 L197.8544,74.7342 Z" fill="#E24329" class="tanuki-shape"></path>
</g>
</g>
</g>
</g>
</g>
</g>
<svg width="36" height="36" id="tanuki-logo">
<path id="tanuki-right-ear" class="tanuki-shape" fill="#e24329" d="M2 14l9.38 9v-9l-4-12.28c-.205-.632-1.176-.632-1.38 0z"/>
<path id="tanuki-left-ear" class="tanuki-shape" fill="#e24329" d="M34 14l-9.38 9v-9l4-12.28c.205-.632 1.176-.632 1.38 0z"/>
<path id="tanuki-nose" class="tanuki-shape" fill="#e24329" d="M18,34.38 3,14 33,14 Z"/>
<path id="tanuki-right-eye" class="tanuki-shape" fill="#fc6d26" d="M18,34.38 11.38,14 2,14 6,25Z"/>
<path id="tanuki-left-eye" class="tanuki-shape" fill="#fc6d26" d="M18,34.38 24.62,14 34,14 30,25Z"/>
<path id="tanuki-right-cheek" class="tanuki-shape" fill="#fca326" d="M2 14L.1 20.16c-.18.565 0 1.2.5 1.56l17.42 12.66z"/>
<path id="tanuki-left-cheek" class="tanuki-shape" fill="#fca326" d="M34 14l1.9 6.16c.18.565 0 1.2-.5 1.56L18 34.38z"/>
</svg>
......@@ -4,21 +4,18 @@
%span.issuable-count.pull-left
= issuable.iid
of
= issuable_count(:all, @project)
= issuables_count(issuable)
%span.pull-right
%a.gutter-toggle{href: '#'}
- if sidebar_gutter_collapsed?
= icon('angle-double-left')
- else
= icon('angle-double-right')
= sidebar_gutter_toggle_icon
.issuable-nav.pull-right.btn-group{role: 'group', "aria-label" => '...'}
- if has_prev_issuable?(@project, issuable.id)
= link_to 'Prev', issuable_link_prev(@project, issuable), class: 'btn btn-default prev-btn'
- if prev_issuable = prev_issuable_for(issuable)
= link_to 'Prev', [@project.namespace.becomes(Namespace), @project, prev_issuable], class: 'btn btn-default prev-btn'
- else
%a.btn.btn-default.disabled{href: '#'}
Prev
- if has_next_issuable?(@project, issuable.id)
= link_to 'Next', issuable_link_next(@project, issuable), class: 'btn btn-default next-btn'
- if next_issuable = next_issuable_for(issuable)
= link_to 'Next', [@project.namespace.becomes(Namespace), @project, next_issuable], class: 'btn btn-default next-btn'
- else
%a.btn.btn-default.disabled{href: '#'}
Next
......@@ -50,7 +47,7 @@
.block.milestone
.sidebar-collapsed-icon
= icon('balance-scale')
= icon('clock-o')
%span
- if issuable.milestone
= issuable.milestone.title
......@@ -118,7 +115,7 @@
- project_ref = cross_project_reference(@project, issuable)
.block.project-reference
.sidebar-collapsed-icon
= icon('clipboard')
= clipboard_button(clipboard_text: project_ref)
.title
.cross-project-reference
%span
......
......@@ -21,9 +21,10 @@
#{projects_limit} of #{pluralize(projects.count, 'project')} displayed.
= link_to '#', class: 'js-expand' do
Show all
= paginate projects, theme: "gitlab" if !projects.kind_of?(Array)
= paginate projects, theme: "gitlab" if projects.respond_to? :total_pages
- else
%h3 No projects found
:javascript
new ProjectsList();
Dashboard.init();
......@@ -27,6 +27,7 @@ class RepositoryForkWorker
return
end
project.repository.expire_emptiness_caches
project.import_finish
end
end
......@@ -18,6 +18,7 @@ class RepositoryImportWorker
return
end
project.repository.expire_emptiness_caches
project.import_finish
end
end
......@@ -31,7 +31,7 @@ module Gitlab
config.encoding = "utf-8"
# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters.push(:password, :password_confirmation, :private_token, :otp_attempt)
config.filter_parameters.push(:password, :password_confirmation, :private_token, :otp_attempt, :variables)
# Enable escaping HTML in JSON.
config.active_support.escape_html_entities_in_json = true
......@@ -52,17 +52,11 @@ module Gitlab
config.action_view.sanitized_allowed_protocols = %w(smb)
# Relative url support
# Uncomment and customize the last line to run in a non-root path
# WARNING: We recommend creating a FQDN to host GitLab in a root path instead of this.
# Note that following settings need to be changed for this to work.
# 1) In your application.rb file: config.relative_url_root = "/gitlab"
# 2) In your gitlab.yml file: relative_url_root: /gitlab
# 3) In your unicorn.rb: ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"
# 4) In ../gitlab-shell/config.yml: gitlab_url: "http://127.0.0.1/gitlab"
# 5) In lib/support/nginx/gitlab : do not use asset gzipping, remove block starting with "location ~ ^/(assets)/"
#
# To update the path, run: sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
# Relative URL support
# WARNING: We recommend using an FQDN to host GitLab in a root path instead
# of using a relative URL.
# Documentation: http://doc.gitlab.com/ce/install/relative_url.html
# Uncomment and customize the following line to run in a non-root path
#
# config.relative_url_root = "/gitlab"
......
......@@ -38,8 +38,12 @@ production: &base
# Otherwise, ssh host will be set to the `host:` value above
# ssh_host: ssh.host_example.com
# WARNING: See config/application.rb under "Relative url support" for the list of
# other files that need to be changed for relative url support
# Relative URL support
# WARNING: We recommend using an FQDN to host GitLab in a root path instead
# of using a relative URL.
# Documentation: http://doc.gitlab.com/ce/install/relative_url.html
# Uncomment and customize the following line to run in a non-root path
#
# relative_url_root: /gitlab
# Uncomment and customize if you can't use the default user to run GitLab (default: 'git')
......
......@@ -352,6 +352,7 @@ Rails.application.routes.draw do
get :issues
get :merge_requests
get :projects
get :events
end
scope module: :groups do
......@@ -607,7 +608,7 @@ Rails.application.routes.draw do
resource :variables, only: [:show, :update]
resources :triggers, only: [:index, :create, :destroy]
resources :builds, only: [:index, :show] do
resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do
collection do
post :cancel_all
end
......@@ -696,6 +697,12 @@ Rails.application.routes.draw do
end
resources :runner_projects, only: [:create, :destroy]
resources :badges, only: [], path: 'badges/*ref',
constraints: { ref: Gitlab::Regex.git_reference_regex } do
collection do
get :build, constraints: { format: /svg/ }
end
end
end
end
end
......
......@@ -10,9 +10,12 @@
# Note: If you change this file in a Merge Request, please also create a
# Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
#
# WARNING: See config/application.rb under "Relative url support" for the list of
# other files that need to be changed for relative url support
# Relative URL support
# WARNING: We recommend using an FQDN to host GitLab in a root path instead
# of using a relative URL.
# Documentation: http://doc.gitlab.com/ce/install/relative_url.html
# Uncomment and customize the following line to run in a non-root path
#
# ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"
......
class Gitlab::Seeder::Builds
BUILD_STATUSES = %w(running pending success failed canceled)
def initialize(project)
@project = project
end
def seed!
ci_commits.each do |ci_commit|
build = Ci::Build.new(build_attributes_for(ci_commit))
artifacts_cache_file(artifacts_archive_path) do |file|
build.artifacts_file = file
end
artifacts_cache_file(artifacts_metadata_path) do |file|
build.artifacts_metadata = file
end
begin
build.save!
build_create!(ci_commit, name: 'test build 1')
build_create!(ci_commit, status: 'success', name: 'test build 2')
print '.'
rescue ActiveRecord::RecordInvalid
print 'F'
......@@ -36,6 +25,28 @@ class Gitlab::Seeder::Builds
[]
end
def build_create!(ci_commit, opts = {})
attributes = build_attributes_for(ci_commit).merge(opts)
build = Ci::Build.new(attributes)
if %w(success failed).include?(build.status)
artifacts_cache_file(artifacts_archive_path) do |file|
build.artifacts_file = file
end
artifacts_cache_file(artifacts_metadata_path) do |file|
build.artifacts_metadata = file
end
end
build.save!
if %w(running success failed).include?(build.status)
# We need to set build trace after saving a build (id required)
build.trace = FFaker::Lorem.paragraphs(6).join("\n\n")
end
end
def build_attributes_for(ci_commit)
{ name: 'test build', commands: "$ build command",
stage: 'test', stage_idx: 1, ref: 'master',
......@@ -49,7 +60,7 @@ class Gitlab::Seeder::Builds
end
def build_status
BUILD_STATUSES.sample
Ci::Build::AVAILABLE_STATUSES.sample
end
def artifacts_archive_path
......
......@@ -8,20 +8,16 @@ Get a list of builds in a project.
GET /projects/:id/builds
```
### Parameters
| Attribute | Type | required | Description |
| Attribute | Type | Required | Description |
|-----------|---------|----------|---------------------|
| id | integer | yes | The ID of a project |
| scope | string|array of strings | no | The scope of builds to show, one or array of: `pending`, `running`, `failed`, `success`, `canceled`; showing all builds if none provided |
### Example of request
| `id` | integer | yes | The ID of a project |
| `scope` | string **or** array of strings | no | The scope of builds to show, one or array of: `pending`, `running`, `failed`, `success`, `canceled`; showing all builds if none provided |
```
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds"
```
### Example of response
Example of response
```json
[
......@@ -112,21 +108,17 @@ Get a list of builds for specific commit in a project.
GET /projects/:id/repository/commits/:sha/builds
```
### Parameters
| Attribute | Type | required | Description |
| Attribute | Type | Required | Description |
|-----------|---------|----------|---------------------|
| id | integer | yes | The ID of a project |
| sha | string | yes | The SHA id of a commit |
| scope | string|array of strings | no | The scope of builds to show, one or array of: `pending`, `running`, `failed`, `success`, `canceled`; showing all builds if none provided |
### Example of request
| `id` | integer | yes | The ID of a project |
| `sha` | string | yes | The SHA id of a commit |
| `scope` | string **or** array of strings | no | The scope of builds to show, one or array of: `pending`, `running`, `failed`, `success`, `canceled`; showing all builds if none provided |
```
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/repository/commits/0ff3ae198f8601a285adcf5c0fff204ee6fba5fd/builds"
```
### Example of response
Example of response
```json
[
......@@ -203,20 +195,16 @@ Get a single build of a project
GET /projects/:id/builds/:build_id
```
### Parameters
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
| id | integer | yes | The ID of a project |
| build\_id | integer | yes | The ID of a build |
### Example of request
| Attribute | Type | Required | Description |
|------------|---------|----------|---------------------|
| `id` | integer | yes | The ID of a project |
| `build_id` | integer | yes | The ID of a build |
```
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/8"
```
### Example of response
Example of response
```json
{
......@@ -267,20 +255,16 @@ Cancel a single build of a project
POST /projects/:id/builds/:build_id/cancel
```
### Parameters
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
| id | integer | yes | The ID of a project |
| build\_id | integer | yes | The ID of a build |
### Example of request
| Attribute | Type | Required | Description |
|------------|---------|----------|---------------------|
| `id` | integer | yes | The ID of a project |
| `build_id` | integer | yes | The ID of a build |
```
curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/cancel"
```
### Example of response
Example of response
```json
{
......@@ -317,20 +301,16 @@ Retry a single build of a project
POST /projects/:id/builds/:build_id/retry
```
### Parameters
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
| id | integer | yes | The ID of a project |
| build\_id | integer | yes | The ID of a build |
### Example of request
| Attribute | Type | Required | Description |
|------------|---------|----------|---------------------|
| `id` | integer | yes | The ID of a project |
| `build_id` | integer | yes | The ID of a build |
```
curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/retry"
```
### Example of response
Example of response
```json
{
......
......@@ -259,6 +259,7 @@ Parameters:
- `description` (optional) - Description of MR
- `target_project_id` (optional) - The target project (numeric id)
- `labels` (optional) - Labels for MR as a comma-separated list
- `milestone_id` (optional) - Milestone ID
```json
{
......@@ -328,6 +329,7 @@ Parameters:
- `description` - Description of MR
- `state_event` - New state (close|reopen|merge)
- `labels` (optional) - Labels for MR as a comma-separated list
- `milestone_id` (optional) - Milestone ID
```json
{
......@@ -516,3 +518,65 @@ Parameters:
## Comments on merge requets
Comments are done via the [notes](notes.md) resource.
## List issues that will close on merge
Get all the issues that would be closed by merging the provided merge request.
```
GET /projects/:id/merge_requests/:merge_request_id/closes_issues
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `merge_request_id` | integer | yes | The ID of the merge request |
```bash
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/76/merge_requests/1/closes_issues
```
Example response:
```json
[
{
"state" : "opened",
"description" : "Ratione dolores corrupti mollitia soluta quia.",
"author" : {
"state" : "active",
"id" : 18,
"web_url" : "https://gitlab.example.com/u/eileen.lowe",
"name" : "Alexandra Bashirian",
"avatar_url" : null,
"username" : "eileen.lowe"
},
"milestone" : {
"project_id" : 1,
"description" : "Ducimus nam enim ex consequatur cumque ratione.",
"state" : "closed",
"due_date" : null,
"iid" : 2,
"created_at" : "2016-01-04T15:31:39.996Z",
"title" : "v4.0",
"id" : 17,
"updated_at" : "2016-01-04T15:31:39.996Z"
},
"project_id" : 1,
"assignee" : {
"state" : "active",
"id" : 1,
"name" : "Administrator",
"web_url" : "https://gitlab.example.com/u/root",
"avatar_url" : null,
"username" : "root"
},
"updated_at" : "2016-01-04T15:31:51.081Z",
"id" : 76,
"title" : "Consequatur vero maxime deserunt laboriosam est voluptas dolorem.",
"created_at" : "2016-01-04T15:31:51.081Z",
"iid" : 6,
"labels" : []
},
]
```
......@@ -270,7 +270,7 @@ This will forcefully (`-f`) remove the `build` container, the two service
containers as well as all volumes (`-v`) that were created with the container
creation.
[Docker Fundamentals]: https://docs.docker.com/engine/introduction/understanding-docker/
[Docker Fundamentals]: https://docs.docker.com/engine/understanding-docker/
[hub]: https://hub.docker.com/
[linking-containers]: https://docs.docker.com/engine/userguide/networking/default_network/dockerlinks/
[tutum/wordpress]: https://registry.hub.docker.com/u/tutum/wordpress/
......
......@@ -184,6 +184,14 @@ you expected.
You are also able to view the status of any commit in the various pages in
GitLab, such as **Commits** and **Merge Requests**.
## Builds badge
You can access a builds badge image using following link:
```
http://example.gitlab.com/namespace/project/badges/branch/build.svg
```
## Next steps
Awesome! You started using CI in GitLab!
......
......@@ -16,7 +16,7 @@ The API_TOKEN will take the Secure Variable value: `SECURE`.
### Predefined variables (Environment Variables)
| Variable | Runner | Description |
|-------------------------|-------------|
|-------------------------|-----|--------|
| **CI** | 0.4 | Mark that build is executed in 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 |
......@@ -77,9 +77,12 @@ More information about Docker integration can be found in [Using Docker Images](
GitLab CI allows you to define per-project **Secure Variables** that are set in build environment.
The secure variables are stored out of the repository (the `.gitlab-ci.yml`).
These variables are securely stored in GitLab CI database and are hidden in the build log.
The variables are securely passed to GitLab Runner and are available in build environment.
It's desired method to use them for storing passwords, secret keys or whatever you want.
**The value of the variable can be shown in build log if explicitly asked to do so.**
If your project is public or internal you can make the builds private.
Secure Variables can added by going to `Project > Variables > Add Variable`.
They will be available for all subsequent builds.
......
......@@ -8,12 +8,14 @@ In addition, having to take a server offline for a an upgrade small or big is
a big burden for most organizations. For this reason it is important that your
migrations are written carefully, can be applied online and adhere to the style guide below.
It's advised to have offline migrations only in major GitLab releases.
When writing your migrations, also consider that databases might have stale data
or inconsistencies and guard for that. Try to make as little assumptions as possible
about the state of the database.
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
......@@ -33,6 +35,8 @@ It is always preferable to have a migration run online. If you expect the migrat
to take particularly long (for instance, if it loops through all notes),
this is valuable information to add.
If you don't provide the information it means that a migration is safe to run online.
### Reversibility
Your migration should be reversible. This is very important, as it should
......
......@@ -182,25 +182,20 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
## 6. Redis
As of this writing, most Debian/Ubuntu distributions ship with Redis 2.2 or
2.4. GitLab requires at least Redis 2.8.
GitLab requires at least Redis 2.8.
Ubuntu users [can use a PPA](https://launchpad.net/~chris-lea/+archive/ubuntu/redis-server)
to install a recent version of Redis.
The following instructions cover building and installing Redis from scratch:
If you are using Debian 8 or Ubuntu 14.04 and up, then you can simply install
Redis 2.8 with:
```sh
# Build Redis
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
sudo apt-get install redis-server
```
# Install Redis
cd utils
sudo ./install_server.sh
If you are using Debian 7 or Ubuntu 12.04, follow the special documentation
on [an alternate Redis installation](redis.md). Once done, follow the rest of
the guide here.
```
# Configure redis to use sockets
sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.orig
......@@ -224,7 +219,7 @@ if [ -d /etc/tmpfiles.d ]; then
fi
# Activate the changes to redis.conf
sudo service redis_6379 start
sudo service redis-server restart
# Add git to the redis group
sudo usermod -aG redis git
......@@ -238,9 +233,9 @@ sudo usermod -aG redis git
### Clone the Source
# Clone GitLab repository
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-4-stable gitlab
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-5-stable gitlab
**Note:** You can change `8-4-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
**Note:** You can change `8-5-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
......@@ -358,7 +353,7 @@ GitLab Shell is an SSH access and repository management software developed speci
cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
cd gitlab-workhorse
sudo -u git -H git checkout 0.6.3
sudo -u git -H git checkout 0.6.4
sudo -u git -H make
### Initialize Database and Activate Advanced Features
......@@ -484,6 +479,11 @@ You can use `sudo service gitlab start` and `sudo service gitlab stop` to start
## Advanced Setup Tips
### Relative URL support
See the [Relative URL documentation](relative_url.md) for more information on
how to configure GitLab with a relative URL.
### Using HTTPS
To use GitLab with HTTPS:
......
# 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"
## Install GitLab under a relative URL
_**Note:**
This document describes how to run GitLab under a relative URL for installations
from source. If you are using an Omnibus package,
[the steps are different][omnibus-rel]. Use this guide along with the
[installation guide](installation.md) if you are installing GitLab for the
first time._
---
While it is recommended to install GitLab on its own (sub)domain, sometimes
this is not possible due to a variety of reasons. In that case, GitLab can also
be installed under a relative URL, for example `https://example.com/gitlab`.
There is no limit to how deeply nested the relative URL can be. For example you
could serve GitLab under `/foo/bar/gitlab/git` without any issues.
Note that by changing the URL on an existing GitLab installation, all remote
URLs will change, so you'll have to manually edit them in any local repository
that points to your GitLab instance.
---
The TL;DR list of configuration files that you need to change in order to
serve GitLab under a relative URL is:
- `/home/git/gitlab/config/application.rb`
- `/home/git/gitlab/config/gitlab.yml`
- `/home/git/gitlab/config/unicorn.rb`
- `/home/git/gitlab-shell/config.yml`
- `/etc/default/gitlab`
After all the changes you need to recompile the assets and [restart GitLab].
### Relative URL requirements
If you configure GitLab with a relative URL, the assets (JavaScript, CSS, fonts,
images, etc.) will need to be recompiled, which is a task which consumes a lot
of CPU and memory resources. To avoid out-of-memory errors, you should have at
least 2GB of RAM available on your system, while we recommend 4GB RAM, and 4 or
8 CPU cores.
See the [requirements](requirements.md) document for more information.
### Enable relative URL in GitLab
_**Note:**
Do not make any changes to your web server configuration file regarding
relative URL. The relative URL support is implemented by GitLab Workhorse._
---
Before following the steps below to enable relative URL in GitLab, some
assumptions are made:
- GitLab is served under `/gitlab`
- The directory under which GitLab is installed is `/home/git/`
Make sure to follow all steps below:
1. (Optional) If you run short on resources, you can temporarily free up some
memory by shutting down the GitLab service with the following command:
```shell
sudo service gitlab stop
```
1. Edit `/home/git/gitlab/config/application.rb` and uncomment/change the
following line:
```ruby
config.relative_url_root = "/gitlab"
```
1. Edit `/home/git/gitlab/config/gitlab.yml` and uncomment/change the
following line:
```yaml
relative_url_root: /gitlab
```
1. Edit `/home/git/gitlab/config/unicorn.rb` and uncomment/change the
following line:
```ruby
ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"
```
1. Edit `/home/git/gitlab-shell/config.yml` and append the relative path to
the following line:
```yaml
gitlab_url: http://127.0.0.1/gitlab
```
1. Make sure you have copied the supplied init script and the defaults file
as stated in the [installation guide](installation.md#install-init-script).
Then, edit `/etc/default/gitlab` and set in `gitlab_workhorse_options` the
`-authBackend` setting to read like:
```shell
-authBackend http://127.0.0.1:8080/gitlab
```
**Note:**
If you are using a custom init script, make sure to edit the above
gitlab-workhorse setting as needed.
1. After all the above changes recompile the assets. This is an important task
and will take some time to complete depending on the server resources:
```
cd /home/git/gitlab
sudo -u git -H bundle exec rake assets:clean assets:precompile RAILS_ENV=production
```
1. [Restart GitLab][] for the changes to take effect.
### Disable relative URL in GitLab
To disable the relative URL, follow the same steps as above and set up the
GitLab URL to one that doesn't contain a relative path.
[omnibus-rel]: http://doc.gitlab.com/omnibus/settings/configuration.html#configuring-a-relative-url-for-gitlab "How to setup relative URL in Omnibus GitLab"
[restart gitlab]: ../administration/restart_gitlab.md#installations-from-source "How to restart GitLab"
......@@ -424,24 +424,24 @@ will point the link to `wikis/style` when the link is inside of a wiki markdown
Here's our logo (hover to see the title text):
Inline-style:
![alt text](assets/logo.svg)
![alt text](img/logo.png)
Reference-style:
![alt text1][logo]
[logo]: assets/logo.svg
[logo]: img/logo.png
Here's our logo:
Inline-style:
![alt text](/assets/logo.svg)
![alt text](img/logo.png)
Reference-style:
![alt text][logo]
[logo]: /assets/logo.svg
[logo]: img/logo.png
## Blockquotes
......
......@@ -18,8 +18,6 @@ for two-factor authentication. If you restore a GitLab backup without
restoring the database encryption key, users who have two-factor
authentication enabled will lose access to your GitLab server.
If you are interested in GitLab CI backup please follow to the [CI backup documentation](https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/raketasks/backup_restore.md)*
```
# use this command if you've installed GitLab with the Omnibus package
sudo gitlab-rake gitlab:backup:create
......
# From 8.4 to 8.5
### 1. Stop server
sudo service gitlab stop
### 2. Backup
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
### 3. Get latest code
```bash
sudo -u git -H git fetch --all
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
```
For GitLab Community Edition:
```bash
sudo -u git -H git checkout 8-5-stable
```
OR
For GitLab Enterprise Edition:
```bash
sudo -u git -H git checkout 8-5-stable-ee
```
### 4. Update gitlab-shell
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch --all
sudo -u git -H git checkout v2.6.10
```
### 5. Update gitlab-workhorse
Install and compile gitlab-workhorse. This requires
[Go 1.5](https://golang.org/dl) which should already be on your system from
GitLab 8.1.
```bash
cd /home/git/gitlab-workhorse
sudo -u git -H git fetch --all
sudo -u git -H git checkout 0.6.4
sudo -u git -H make
```
### 6. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
# MySQL installations (note: the line below states '--without postgres')
sudo -u git -H bundle install --without postgres development test --deployment
# PostgreSQL installations (note: the line below states '--without mysql')
sudo -u git -H bundle install --without mysql development test --deployment
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
# Clean up assets and cache
sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
```
### 7. Update configuration files
#### New configuration options for `gitlab.yml`
There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
```sh
git diff origin/8-4-stable:config/gitlab.yml.example origin/8-5-stable:config/gitlab.yml.example
```
### 8. Start application
sudo service gitlab start
sudo service nginx restart
### 9. Check application status
Check if GitLab and its environment are configured correctly:
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
To make sure you didn't miss anything run a more thorough check:
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
If all items are green, then congratulations, the upgrade is complete!
## Things went south? Revert to previous version (8.4)
### 1. Revert the code to the previous version
Follow the [upgrade guide from 8.3 to 8.4](8.3-to-8.4.md), except for the
database migration (the backup is already migrated to the previous version).
### 2. Restore from the backup
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
# 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.
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
"user_id": 4,
"user_name": "John Smith",
"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,
"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",
"url": "git@example.com:mike/diasporadiaspora.git",
"description": "",
......@@ -76,7 +100,6 @@ X-Gitlab-Event: Push Hook
}
],
"total_commits_count": 4
}
```
......@@ -101,8 +124,25 @@ X-Gitlab-Event: Tag Push Hook
"after": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7",
"user_id": 1,
"user_name": "John Smith",
"user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
"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",
"url": "ssh://git@example.com/jsmith/example.git",
"description": "",
......@@ -136,7 +176,23 @@ X-Gitlab-Event: Issue Hook
"username": "root",
"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",
"url": "http://example.com/gitlabhq/gitlab-test.git",
"description": "Aut reprehenderit ut est.",
......@@ -158,6 +214,11 @@ X-Gitlab-Event: Issue Hook
"iid": 23,
"url": "http://example.com/diaspora/issues/23",
"action": "open"
},
"assignee": {
"name": "User1",
"username": "user1",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
}
}
```
......@@ -193,9 +254,25 @@ X-Gitlab-Event: Note Hook
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
},
"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",
"url": "http://localhost/gitlab-org/gitlab-test.git",
"url": "http://example.com/gitlab-org/gitlab-test.git",
"description": "Aut reprehenderit ut est.",
"homepage": "http://example.com/gitlab-org/gitlab-test"
},
......@@ -256,9 +333,25 @@ X-Gitlab-Event: Note Hook
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
},
"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",
"url": "http://example.com/gitlab-org/gitlab-test.git",
"url": "http://localhost/gitlab-org/gitlab-test.git",
"description": "Aut reprehenderit ut est.",
"homepage": "http://example.com/gitlab-org/gitlab-test"
},
......@@ -296,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.",
"position": 0,
"locked_at": null,
"source": {
"name": "Gitlab Test",
"ssh_url": "git@example.com:gitlab-org/gitlab-test.git",
"http_url": "http://example.com/gitlab-org/gitlab-test.git",
"web_url": "http://example.com/gitlab-org/gitlab-test",
"namespace": "Gitlab Org",
"visibility_level": 10
"source":{
"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"
},
"target": {
"name": "Gitlab Test",
"ssh_url": "git@example.com:gitlab-org/gitlab-test.git",
"http_url": "http://example.com/gitlab-org/gitlab-test.git",
"web_url": "http://example.com/gitlab-org/gitlab-test",
"namespace": "Gitlab Org",
"visibility_level": 10
"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"
},
"last_commit": {
"id": "562e173be03b8ff2efb05345d12df18815438a4b",
......@@ -322,7 +431,12 @@ X-Gitlab-Event: Note Hook
"email": "john@example.com"
}
},
"work_in_progress": false
"work_in_progress": false,
"assignee": {
"name": "User1",
"username": "user1",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
}
}
}
```
......@@ -346,11 +460,27 @@ X-Gitlab-Event: Note Hook
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
},
"project_id": 5,
"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"
"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":"diaspora",
"url":"git@example.com:mike/diasporadiaspora.git",
"description":"",
"homepage":"http://example.com/mike/diaspora"
},
"object_attributes": {
"id": 1241,
......@@ -388,7 +518,6 @@ X-Gitlab-Event: Note Hook
### Comment on code snippet
**Request header**:
```
......@@ -397,7 +526,7 @@ X-Gitlab-Event: Note Hook
**Request body:**
```
```json
{
"object_kind": "note",
"user": {
......@@ -406,11 +535,27 @@ X-Gitlab-Event: Note Hook
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
},
"project_id": 5,
"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"
"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",
"url":"http://example.com/gitlab-org/gitlab-test.git",
"description":"Aut reprehenderit ut est.",
"homepage":"http://example.com/gitlab-org/gitlab-test"
},
"object_attributes": {
"id": 1245,
......@@ -482,21 +627,37 @@ X-Gitlab-Event: Merge Request Hook
"target_project_id": 14,
"iid": 1,
"description": "",
"source": {
"name": "awesome_project",
"ssh_url": "ssh://git@example.com/awesome_space/awesome_project.git",
"http_url": "http://example.com/awesome_space/awesome_project.git",
"web_url": "http://example.com/awesome_space/awesome_project",
"visibility_level": 20,
"namespace": "awesome_space"
"source":{
"name":"Awesome Project",
"description":"Aut reprehenderit ut est.",
"web_url":"http://example.com/awesome_space/awesome_project",
"avatar_url":null,
"git_ssh_url":"git@example.com:awesome_space/awesome_project.git",
"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": {
"name": "awesome_project",
"ssh_url": "ssh://git@example.com/awesome_space/awesome_project.git",
"http_url": "http://example.com/awesome_space/awesome_project.git",
"web_url": "http://example.com/awesome_space/awesome_project",
"visibility_level": 20,
"namespace": "awesome_space"
"name":"Awesome Project",
"description":"Aut reprehenderit ut est.",
"web_url":"http://example.com/awesome_space/awesome_project",
"avatar_url":null,
"git_ssh_url":"git@example.com:awesome_space/awesome_project.git",
"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": {
"id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
......@@ -510,7 +671,12 @@ X-Gitlab-Event: Merge Request Hook
},
"work_in_progress": false,
"url": "http://example.com/diaspora/merge_requests/1",
"action": "open"
"action": "open",
"assignee": {
"name": "User1",
"username": "user1",
"avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
}
}
}
```
......
......@@ -187,12 +187,15 @@ If you have an issue that spans across multiple repositories, the best thing is
![Vim screen showing the rebase view](rebase.png)
With git you can use an interactive rebase (`rebase -i`) to squash multiple commits into one and reorder them.
In GitLab EE and .com you can also [rebase before merge](http://doc.gitlab.com/ee/workflow/rebase_before_merge.html) from the web interface.
This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical.
However you should never rebase commits you have pushed to a remote server.
Somebody can have referred to the commits or cherry-picked them.
When you rebase you change the identifier (SHA-1) of the commit and this is confusing.
If you do that the same change will be known under multiple identifiers and this can cause much confusion.
If people already reviewed your code it will be hard for them to review only the improvements you made since then if you have rebased everything into one commit.
Another reasons not to rebase is that you lose authorship information, maybe someone created a merge request, another person pushed a commit on there to improve it and a third one merged it.
In this case rebasing all the commits into one prevent the other authors from being properly attributed and sharing part of the [git blame](https://git-scm.com/docs/git-blame).
People are encouraged to commit often and to frequently push to the remote repository so other people are aware what everyone is working on.
This will lead to many commits per change which makes the history harder to understand.
......@@ -221,13 +224,11 @@ You can reuse recorded resolutions (rerere) sometimes, but without rebasing you
There has to be a better way to avoid many merge commits.
The way to prevent creating many merge commits is to not frequently merge master into the feature branch.
We'll discuss the three reasons to merge in master: leveraging code, solving merge conflicts and long running branches.
We'll discuss the three reasons to merge in master: leveraging code, merge conflicts, and long running branches.
If you need to leverage some code that was introduced in master after you created the feature branch you can sometimes solve this by just cherry-picking a commit.
If your feature branch has a merge conflict, creating a merge commit is a normal way of solving this.
You should aim to prevent merge conflicts where they are likely to occur.
One example is the CHANGELOG file where each significant change in the codebase is documented under a version header.
Instead of everyone adding their change at the bottom of the list for the current version it is better to randomly insert it in the current list for that version.
This it is likely that multiple feature branches that add to the CHANGELOG can be merged before a conflict occurs.
You can prevent some merge conflicts by using [gitattributes](http://git-scm.com/docs/gitattributes) for files that can be in a random order.
For example in GitLab our changelog file is specified in .gitattributes as `CHANGELOG merge=union` so that there are fewer merge conflicts in it.
The last reason for creating merge commits is having long lived branches that you want to keep up to date with the latest state of the project.
Martin Fowler, in [his article about feature branches](http://martinfowler.com/bliki/FeatureBranch.html) talks about this Continuous Integration (CI).
At GitLab we are guilty of confusing CI with branch testing. Quoting Martin Fowler: "I've heard people say they are doing CI because they are running builds, perhaps using a CI server, on every branch with every commit.
......
Feature: Project Badges Build
Background:
Given I sign in as a user
And I own a project
And project has CI enabled
And project has a recent build
Scenario: I want to see a badge for successfully built project
Given recent build is successful
When I display builds badge for a master branch
Then I should see a build success badge
Scenario: I want to see a badge for project with failed builds
Given recent build failed
When I display builds badge for a master branch
Then I should see a build failed badge
Scenario: I want to see a badge for project with running builds
Given recent build is successful
And project has another build that is running
When I display builds badge for a master branch
Then I should see a build running badge
......@@ -7,6 +7,26 @@ Feature: Project Commits
Scenario: I browse commits list for master branch
Then I see project commits
And I should not see button to create a new merge request
Then I click the "Compare" tab
And I should not see button to create a new merge request
Scenario: I browse commits list for feature branch without a merge request
Given I visit commits list page for feature branch
Then I see feature branch commits
And I see button to create a new merge request
Then I click the "Compare" tab
And I see button to create a new merge request
Scenario: I browse commits list for feature branch with an open merge request
Given project have an open merge request
And I visit commits list page for feature branch
Then I see feature branch commits
And I should not see button to create a new merge request
And I should see button to the merge request
Then I click the "Compare" tab
And I should not see button to create a new merge request
And I should see button to the merge request
Scenario: I browse atom feed of commits list for master branch
Given I click atom feed link
......@@ -30,6 +50,22 @@ Feature: Project Commits
And I click side-by-side diff button
Then I see inline diff button
@javascript
Scenario: I compare branches without a merge request
Given I visit compare refs page
And I fill compare fields with branches
Then I see compared branches
And I see button to create a new merge request
@javascript
Scenario: I compare branches with an open merge request
Given project have an open merge request
And I visit compare refs page
And I fill compare fields with branches
Then I see compared branches
And I should not see button to create a new merge request
And I should see button to the merge request
@javascript
Scenario: I compare refs
Given I visit compare refs page
......
......@@ -7,7 +7,16 @@ Feature: Award Emoji
And I visit "Bugfix" issue page
@javascript
Scenario: I add and remove award in the issue
Scenario: I repeatedly add and remove thumbsup award in the issue
Given I click the thumbsup award Emoji
Then I have award added
Given I click the thumbsup award Emoji
Then I have no awards added
Given I click the thumbsup award Emoji
Then I have award added
@javascript
Scenario: I add and remove custom award in the issue
Given I click to emoji-picker
Then The search field is focused
And I click to emoji in the picker
......
......@@ -25,9 +25,16 @@ Feature: Project Issues
Scenario: I visit issue page
Given I click link "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
Scenario: I visit issue page
Scenario: I filter by author
Given I add a user to project "Shop"
And I click "author" dropdown
Then I see current user as the first user
......
......@@ -39,6 +39,7 @@ Feature: Project Merge Requests
Scenario: I visit merge request page
Given I click link "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
Given I click link "Bug NS-04"
......
class Spinach::Features::ProjectBadgesBuild < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedBuilds
include RepoHelpers
step 'I display builds badge for a master branch' do
visit build_namespace_project_badges_path(@project.namespace, @project, ref: :master, format: :svg)
end
step 'I should see a build success badge' do
expect_badge('success')
end
step 'I should see a build failed badge' do
expect_badge('failed')
end
step 'I should see a build running badge' do
expect_badge('running')
end
def expect_badge(status)
svg = Nokogiri::XML.parse(page.body)
expect(page.response_headers).to include('Content-Type' => 'image/svg+xml')
expect(svg.at(%Q{text:contains("#{status}")})).to be_truthy
end
end
......@@ -33,6 +33,13 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
expect(page).to have_content "Showing #{sample_commit.files_changed_count} changed files"
end
step 'I fill compare fields with branches' do
fill_in 'from', with: 'feature'
fill_in 'to', with: 'master'
click_button 'Compare'
end
step 'I fill compare fields with refs' do
fill_in "from", with: sample_commit.parent_id
fill_in "to", with: sample_commit.id
......@@ -56,6 +63,56 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
expect(page).to have_content "Showing 2 changed files"
end
step 'I visit commits list page for feature branch' do
visit namespace_project_commits_path(@project.namespace, @project, 'feature', { limit: 5 })
end
step 'I see feature branch commits' do
commit = @project.repository.commit('0b4bc9a')
expect(page).to have_content(@project.name)
expect(page).to have_content(commit.message[0..12])
expect(page).to have_content(commit.short_id)
end
step 'project have an open merge request' do
create(:merge_request,
title: 'Feature',
source_project: @project,
source_branch: 'feature',
target_branch: 'master',
author: @project.users.first
)
end
step 'I click the "Compare" tab' do
click_link('Compare')
end
step 'I fill compare fields with branches' do
fill_in 'from', with: 'master'
fill_in 'to', with: 'feature'
click_button 'Compare'
end
step 'I see compared branches' do
expect(page).to have_content 'Commits (1)'
expect(page).to have_content 'Showing 1 changed file with 5 additions and 0 deletions'
end
step 'I see button to create a new merge request' do
expect(page).to have_link 'Create Merge Request'
end
step 'I should not see button to create a new merge request' do
expect(page).to_not have_link 'Create Merge Request'
end
step 'I should see button to the merge request' do
merge_request = MergeRequest.find_by(title: 'Feature')
expect(page).to have_link "View Open Merge Request", href: namespace_project_merge_request_path(@project.namespace, @project, merge_request)
end
step 'I see breadcrumb links' do
expect(page).to have_selector('ul.breadcrumb')
expect(page).to have_selector('ul.breadcrumb a', count: 4)
......
......@@ -8,6 +8,15 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
visit namespace_project_issue_path(@project.namespace, @project, @issue)
end
step 'I click the thumbsup award Emoji' do
page.within '.awards' do
thumbsup = page.find('.award .emoji-1F44D')
thumbsup.click
thumbsup.hover
sleep 0.3
end
end
step 'I click to emoji-picker' do
page.within '.awards-controls' do
page.find('.add-award').click
......@@ -37,9 +46,28 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
end
step 'I have award added' do
sleep 0.2
page.within '.awards' do
expect(page).to have_selector '.award'
expect(page.find('.award.active .counter')).to have_content '1'
expect(page.find('.award.active')['data-original-title']).to eq('me')
end
end
step 'I have no awards added' do
page.within '.awards' do
expect(page).to have_selector '.award'
expect(page.all('.award').size).to eq(2)
# Check tooltip data
page.all('.award').each do |element|
expect(element['title']).to eq("")
end
page.all('.award .counter').each do |element|
expect(element).to have_content '0'
end
end
end
......
......@@ -54,6 +54,10 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
expect(page).to have_content "Release 0.4"
end
step 'I should see issue "Tweet control"' do
expect(page).to have_content "Tweet control"
end
step 'I click link "New Issue"' do
click_link "New Issue"
end
......@@ -301,4 +305,5 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
def filter_issue(text)
fill_in 'issue_search', with: text
end
end
......@@ -6,8 +6,20 @@ module SharedBuilds
end
step 'project has a recent build' do
ci_commit = create :ci_commit, project: @project, sha: sample_commit.id
@build = create :ci_build, commit: ci_commit
@ci_commit = create(:ci_commit, project: @project, sha: @project.commit.sha)
@build = create(:ci_build, commit: @ci_commit)
end
step 'recent build is successful' do
@build.update_column(:status, 'success')
end
step 'recent build failed' do
@build.update_column(:status, 'failed')
end
step 'project has another build that is running' do
create(:ci_build, commit: @ci_commit, name: 'second build', status: 'running')
end
step 'I visit recent build details page' do
......
......@@ -119,6 +119,24 @@ module SharedIssuable
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)
project = Project.find_by(name: project_name)
......@@ -159,4 +177,10 @@ module SharedIssuable
expect(page).to have_content("mentioned in #{issuable.class.to_s.titleize.downcase} #{issuable.to_reference(project)}")
end
def expect_sidebar_content(content)
page.within '.issuable-sidebar' do
expect(page).to have_content content
end
end
end
......@@ -71,6 +71,7 @@ module API
# title (required) - Title of MR
# description - Description of MR
# labels (optional) - Labels for MR as a comma-separated list
# milestone_id (optional) - Milestone ID
#
# Example:
# POST /projects/:id/merge_requests
......@@ -78,7 +79,7 @@ module API
post ":id/merge_requests" do
authorize! :create_merge_request, user_project
required_attributes! [:source_branch, :target_branch, :title]
attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description]
attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description, :milestone_id]
# Validate label names in advance
if (errors = validate_label_params(params)).any?
......@@ -163,11 +164,12 @@ module API
# state_event - Status of MR. (close|reopen|merge)
# description - Description of MR
# labels (optional) - Labels for a MR as a comma-separated list
# milestone_id (optional) - Milestone ID
# Example:
# PUT /projects/:id/merge_requests/:merge_request_id
#
put path do
attrs = attributes_for_keys [:target_branch, :assignee_id, :title, :state_event, :description]
attrs = attributes_for_keys [:target_branch, :assignee_id, :title, :state_event, :description, :milestone_id]
merge_request = user_project.merge_requests.find(params[:merge_request_id])
authorize! :update_merge_request, merge_request
......@@ -300,6 +302,19 @@ module API
render_api_error!("Failed to save note #{note.errors.messages}", 400)
end
end
# List issues that will close on merge
#
# Parameters:
# id (required) - The ID of a project
# merge_request_id (required) - ID of MR
# Examples:
# GET /projects/:id/merge_requests/:merge_request_id/closes_issues
get "#{path}/closes_issues" do
merge_request = user_project.merge_requests.find(params[:merge_request_id])
issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user))
present paginate(issues), with: Entities::Issue
end
end
end
end
......
module Banzai
module Pipeline
class AsciidocPipeline < BasePipeline
def self.filters
[
Filter::RelativeLinkFilter
]
end
end
end
end
......@@ -31,9 +31,7 @@ module Gitlab
html = ::Asciidoctor.convert(input, asciidoc_opts)
if context[:project]
html = Banzai.render(html, context.merge(pipeline: :asciidoc))
end
html = Banzai.post_process(html, context)
html.html_safe
end
......
......@@ -40,6 +40,7 @@ module Gitlab
end
def highlighted_lines
@blob.load_all_data!(repository)
@highlighted_lines ||= Gitlab::Highlight.highlight(@blob.name, @blob.data).lines
end
......
......@@ -17,7 +17,7 @@ module Gitlab
end
def true_value
if self.class.postgresql?
if Gitlab::Database.postgresql?
"'t'"
else
1
......@@ -25,7 +25,7 @@ module Gitlab
end
def false_value
if self.class.postgresql?
if Gitlab::Database.postgresql?
"'f'"
else
0
......@@ -47,9 +47,5 @@ module Gitlab
row.first
end
end
def connection
self.class.connection
end
end
end
......@@ -53,13 +53,10 @@ module Gitlab
object_kind: "note",
user: user.hook_attrs,
project_id: project.id,
repository: {
name: project.name,
url: project.url_to_repo,
description: project.description,
homepage: project.web_url,
},
object_attributes: note.hook_attrs
project: project.hook_attrs,
object_attributes: note.hook_attrs,
# DEPRECATED
repository: project.hook_attrs.slice(:name, :url, :description, :homepage)
}
base_data[:object_attributes][:url] =
......
module Gitlab
# Parser/renderer for markups without other special support code.
module OtherMarkup
# Public: Converts the provided markup into HTML.
#
# input - the source text in a markup format
# context - a Hash with the template context:
# :commit
# :project
# :project_wiki
# :requested_path
# :ref
#
def self.render(file_name, input, context)
html = GitHub::Markup.render(file_name, input).
force_encoding(input.encoding)
html = Banzai.post_process(html, context)
html.html_safe
end
end
end
......@@ -22,6 +22,8 @@ module Gitlab
# }
#
def build(project, user, oldrev, newrev, ref, commits = [], message = nil)
commits = Array(commits)
# Total commits count
commits_count = commits.size
......@@ -47,18 +49,14 @@ module Gitlab
user_id: user.id,
user_name: user.name,
user_email: user.email,
user_avatar: user.avatar_url,
project_id: project.id,
repository: {
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
},
project: project.hook_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
......
......@@ -34,11 +34,16 @@ sidekiq_pid_path="$pid_path/sidekiq.pid"
# /home/git/gitlab-workhorse .
gitlab_workhorse_dir=$(cd $app_root/../gitlab-workhorse && pwd)
gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid"
# The -listenXxx settings determine where gitlab-workhorse
# listens for connections from NGINX. To listen on localhost:8181, write
# '-listenNetwork tcp -listenAddr localhost:8181'.
# The -authBackend setting tells gitlab-workhorse where it can reach
# Unicorn.
# listens for connections from the web server. By default it listens to a
# socket. To listen on TCP connections (needed by Apache) change to:
# '-listenNetwork tcp -listenAddr 127.0.0.1:8181'
#
# The -authBackend setting tells gitlab-workhorse where it can reach Unicorn.
# For relative URL support change to:
# '-authBackend http://127.0.0.1/8080/gitlab'
# Read more in http://doc.gitlab.com/ce/install/relative_url.html
gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080 -authSocket $socket_path/gitlab.socket -documentRoot $app_root/public"
gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log"
......
......@@ -90,24 +90,6 @@ namespace :gitlab do
end
end
def check_database_is_not_sqlite
print "Database is SQLite ... "
database_config_file = Rails.root.join("config", "database.yml")
unless File.read(database_config_file) =~ /adapter:\s+sqlite/
puts "no".green
else
puts "yes".red
puts "Please fix this by removing the SQLite entry from the database.yml".blue
for_more_information(
"https://github.com/gitlabhq/gitlabhq/wiki/Migrate-from-SQLite-to-MySQL",
see_database_guide
)
fix_and_rerun
end
end
def check_gitlab_config_exists
print "GitLab config exists? ... "
......
#!/bin/bash
if [ -f /.dockerinit ]; then
# Docker runners use `/cache` folder which is persisted every build
if [ ! -e /cache/phantomjs_1.9.8-0jessie_amd64.deb ]; then
mkdir -p vendor
if [ ! -e vendor/phantomjs_1.9.8-0jessie_amd64.deb ]; then
wget -q https://gitlab.com/axil/phantomjs-debian/raw/master/phantomjs_1.9.8-0jessie_amd64.deb
mv phantomjs_1.9.8-0jessie_amd64.deb /cache
mv phantomjs_1.9.8-0jessie_amd64.deb vendor/
fi
dpkg -i /cache/phantomjs_1.9.8-0jessie_amd64.deb
dpkg -i vendor/phantomjs_1.9.8-0jessie_amd64.deb
apt-get update -qq
apt-get -o dir::cache::archives="/cache/apt" install -y -qq --force-yes \
apt-get -o dir::cache::archives="vendor/apt" install -y -qq --force-yes \
libicu-dev libkrb5-dev cmake nodejs postgresql-client mysql-client unzip
cp config/database.yml.mysql config/database.yml
......@@ -20,7 +20,7 @@ if [ -f /.dockerinit ]; then
cp config/resque.yml.example config/resque.yml
sed -i 's/localhost/redis/g' config/resque.yml
export FLAGS=(--path /cache)
export FLAGS=(--path vendor)
else
export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin
cp config/database.yml.mysql config/database.yml
......
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
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
......@@ -293,6 +293,10 @@ describe ApplicationHelper do
describe 'render_markup' do
let(:content) { 'Noël' }
let(:user) { create(:user) }
before do
allow(helper).to receive(:current_user).and_return(user)
end
it 'should preserve encoding' do
expect(content.encoding.name).to eq('UTF-8')
......
......@@ -42,22 +42,6 @@ module Gitlab
end
end
context "with project in context" do
let(:context) { { project: create(:project) } }
it "should filter converted input via HTML pipeline and return result" do
filtered_html = '<b>ASCII</b>'
allow(Asciidoctor).to receive(:convert).and_return(html)
expect(Banzai).to receive(:render)
.with(html, context.merge(pipeline: :asciidoc))
.and_return(filtered_html)
expect( render('foo', context) ).to eql filtered_html
end
end
def render(*args)
described_class.render(*args)
end
......
require 'spec_helper'
class MigrationTest
include Gitlab::Database
end
describe Gitlab::Database, lib: true do
# These are just simple smoke tests to check if the methods work (regardless
# of what they may return).
......@@ -34,4 +38,32 @@ describe Gitlab::Database, lib: true do
end
end
end
describe '#true_value' do
it 'returns correct value for PostgreSQL' do
expect(described_class).to receive(:postgresql?).and_return(true)
expect(MigrationTest.new.true_value).to eq "'t'"
end
it 'returns correct value for MySQL' do
expect(described_class).to receive(:postgresql?).and_return(false)
expect(MigrationTest.new.true_value).to eq 1
end
end
describe '#false_value' do
it 'returns correct value for PostgreSQL' do
expect(described_class).to receive(:postgresql?).and_return(true)
expect(MigrationTest.new.false_value).to eq "'f'"
end
it 'returns correct value for MySQL' do
expect(described_class).to receive(:postgresql?).and_return(false)
expect(MigrationTest.new.false_value).to eq 0
end
end
end
......@@ -16,62 +16,80 @@ describe 'Gitlab::NoteDataBuilder', lib: true do
end
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
expect(data).to have_key(:commit)
end
include_examples 'project hook data'
include_examples 'deprecated repository hook data'
end
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
expect(data).to have_key(:commit)
end
include_examples 'project hook data'
include_examples 'deprecated repository hook data'
end
describe 'When asking for a note on issue' do
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
expect(data).to have_key(:issue)
expect(data[:issue].except('updated_at')).to eq(issue.hook_attrs.except('updated_at'))
expect(data[:issue]['updated_at']).to be > issue.hook_attrs['updated_at']
end
include_examples 'project hook data'
include_examples 'deprecated repository hook data'
end
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(: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
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]['updated_at']).to be > merge_request.hook_attrs['updated_at']
end
include_examples 'project hook data'
include_examples 'deprecated repository hook data'
end
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(: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
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]['updated_at']).to be > merge_request.hook_attrs['updated_at']
end
include_examples 'project hook data'
include_examples 'deprecated repository hook data'
end
describe 'When asking for a note on project snippet' do
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
expect(data).to have_key(:snippet)
expect(data[:snippet].except('updated_at')).to eq(snippet.hook_attrs.except('updated_at'))
expect(data[:snippet]['updated_at']).to be > snippet.hook_attrs['updated_at']
end
include_examples 'project hook data'
include_examples 'deprecated repository hook data'
end
end
require 'spec_helper'
describe 'Gitlab::PushDataBuilder', lib: true do
describe Gitlab::PushDataBuilder, lib: true do
let(:project) { create(:project) }
let(:user) { create(:user) }
describe :build_sample do
let(:data) { Gitlab::PushDataBuilder.build_sample(project, user) }
describe '.build_sample' do
let(:data) { described_class.build_sample(project, user) }
it { expect(data).to be_a(Hash) }
it { expect(data[:before]).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
it { expect(data[:after]).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
it { expect(data[:ref]).to eq('refs/heads/master') }
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[:commits].first[:added]).to eq(["gitlab-grack"]) }
it { expect(data[:commits].first[:modified]).to eq([".gitmodules"]) }
it { expect(data[:commits].first[:removed]).to eq([]) }
include_examples 'project hook data'
include_examples 'deprecated repository hook data'
end
describe :build do
describe '.build' do
let(:data) do
Gitlab::PushDataBuilder.build(project,
user,
Gitlab::Git::BLANK_SHA,
described_class.build(project, user, Gitlab::Git::BLANK_SHA,
'8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b',
'refs/tags/v1.1.0')
end
......@@ -38,5 +36,10 @@ describe 'Gitlab::PushDataBuilder', lib: true do
it { expect(data[:ref]).to eq('refs/tags/v1.1.0') }
it { expect(data[:commits]).to be_empty }
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
require 'spec_helper'
require 'email_spec'
require 'mailers/shared/notify'
describe Notify do
include EmailSpec::Matchers
include_context 'gitlab email notification'
describe 'build notification email' do
let(:build) { create(:ci_build) }
let(:project) { build.project }
shared_examples 'build email' do
it 'contains name of project' do
is_expected.to have_body_text build.project_name
end
it 'contains link to project' do
is_expected.to have_body_text namespace_project_path(project.namespace, project)
end
end
shared_examples 'an email with X-GitLab headers containing build details' do
it 'has X-GitLab-Build* headers' do
is_expected.to have_header 'X-GitLab-Build-Id', /#{build.id}/
is_expected.to have_header 'X-GitLab-Build-Ref', /#{build.ref}/
end
end
describe 'build success' do
subject { Notify.build_success_email(build.id, 'wow@example.com') }
before { build.success }
it_behaves_like 'build email'
it_behaves_like 'an email with X-GitLab headers containing build details'
it_behaves_like 'an email with X-GitLab headers containing project details'
it 'has header indicating build status' do
is_expected.to have_header 'X-GitLab-Build-Status', 'success'
end
it 'has the correct subject' do
is_expected.to have_subject /Build success for/
end
end
describe 'build fail' do
subject { Notify.build_fail_email(build.id, 'wow@example.com') }
before { build.drop }
it_behaves_like 'build email'
it_behaves_like 'an email with X-GitLab headers containing build details'
it_behaves_like 'an email with X-GitLab headers containing project details'
it 'has header indicating build status' do
is_expected.to have_header 'X-GitLab-Build-Status', 'failed'
end
it 'has the correct subject' do
is_expected.to have_subject /Build failed for/
end
end
end
end
require 'spec_helper'
require 'email_spec'
require 'mailers/shared/notify'
describe Notify do
include EmailSpec::Matchers
include_context 'gitlab email notification'
describe 'profile notifications' do
describe 'for new users, the email' do
let(:example_site_path) { root_path }
let(:new_user) { create(:user, email: new_user_address, created_by_id: 1) }
let(:token) { 'kETLwRaayvigPq_x3SNM' }
subject { Notify.new_user_email(new_user.id, token) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'a new user email'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'contains the password text' do
is_expected.to have_body_text /Click here to set your password/
end
it 'includes a link for user to set password' do
params = "reset_password_token=#{token}"
is_expected.to have_body_text(
%r{http://localhost(:\d+)?/users/password/edit\?#{params}}
)
end
it 'explains the reset link expiration' do
is_expected.to have_body_text(/This link is valid for \d+ (hours?|days?)/)
is_expected.to have_body_text(new_user_password_url)
is_expected.to have_body_text(/\?user_email=.*%40.*/)
end
end
describe 'for users that signed up, the email' do
let(:example_site_path) { root_path }
let(:new_user) { create(:user, email: new_user_address, password: "securePassword") }
subject { Notify.new_user_email(new_user.id) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'a new user email'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'should not contain the new user\'s password' do
is_expected.not_to have_body_text /password/
end
end
describe 'user added ssh key' do
let(:key) { create(:personal_key) }
subject { Notify.new_ssh_key_email(key.id) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'is sent to the new user' do
is_expected.to deliver_to key.user.email
end
it 'has the correct subject' do
is_expected.to have_subject /^SSH key was added to your account$/i
end
it 'contains the new ssh key title' do
is_expected.to have_body_text /#{key.title}/
end
it 'includes a link to ssh keys page' do
is_expected.to have_body_text /#{profile_keys_path}/
end
end
describe 'user added email' do
let(:email) { create(:email) }
subject { Notify.new_email_email(email.id) }
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'is sent to the new user' do
is_expected.to deliver_to email.user.email
end
it 'has the correct subject' do
is_expected.to have_subject /^Email was added to your account$/i
end
it 'contains the new email address' do
is_expected.to have_body_text /#{email.email}/
end
it 'includes a link to emails page' do
is_expected.to have_body_text /#{profile_emails_path}/
end
end
end
end
require 'spec_helper'
require 'email_spec'
require 'mailers/shared/notify'
describe Notify do
include EmailSpec::Helpers
include EmailSpec::Matchers
include RepoHelpers
new_user_address = 'newguy@example.com'
let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name }
let(:gitlab_sender) { Gitlab.config.gitlab.email_from }
let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to }
let(:recipient) { create(:user, email: 'recipient@example.com') }
let(:project) { create(:project) }
let(:build) { create(:ci_build) }
before(:each) do
ActionMailer::Base.deliveries.clear
email = recipient.emails.create(email: "notifications@example.com")
recipient.update_attribute(:notification_email, email.email)
end
shared_examples 'a multiple recipients email' do
it 'is sent to the given recipient' do
is_expected.to deliver_to recipient.notification_email
end
end
shared_examples 'an email sent from GitLab' do
it 'is sent from GitLab' do
sender = subject.header[:from].addrs[0]
expect(sender.display_name).to eq(gitlab_sender_display_name)
expect(sender.address).to eq(gitlab_sender)
end
it 'has a Reply-To address' do
reply_to = subject.header[:reply_to].addresses
expect(reply_to).to eq([gitlab_sender_reply_to])
end
end
shared_examples 'an email with X-GitLab headers containing project details' do
it 'has X-GitLab-Project* headers' do
is_expected.to have_header 'X-GitLab-Project', /#{project.name}/
is_expected.to have_header 'X-GitLab-Project-Id', /#{project.id}/
is_expected.to have_header 'X-GitLab-Project-Path', /#{project.path_with_namespace}/
end
end
shared_examples 'an email with X-GitLab headers containing build details' do
it 'has X-GitLab-Build* headers' do
is_expected.to have_header 'X-GitLab-Build-Id', /#{build.id}/
is_expected.to have_header 'X-GitLab-Build-Ref', /#{build.ref}/
end
end
shared_examples 'an email that contains a header with author username' do
it 'has X-GitLab-Author header containing author\'s username' do
is_expected.to have_header 'X-GitLab-Author', user.username
end
end
shared_examples 'an email starting a new thread' do |message_id_prefix|
include_examples 'an email with X-GitLab headers containing project details'
it 'has a discussion identifier' do
is_expected.to have_header 'Message-ID', /<#{message_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
end
end
shared_examples 'an answer to an existing thread' do |thread_id_prefix|
include_examples 'an email with X-GitLab headers containing project details'
it 'has a subject that begins with Re: ' do
is_expected.to have_subject /^Re: /
end
it 'has headers that reference an existing thread' do
is_expected.to have_header 'Message-ID', /<(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'References', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
end
end
shared_examples 'a new user email' do |user_email, site_path|
it 'is sent to the new user' do
is_expected.to deliver_to user_email
end
it 'has the correct subject' do
is_expected.to have_subject /^Account was created for you$/i
end
it 'contains the new user\'s login name' do
is_expected.to have_body_text /#{user_email}/
end
it 'includes a link to the site' do
is_expected.to have_body_text /#{site_path}/
end
end
shared_examples 'it should have Gmail Actions links' do
it { is_expected.to have_body_text /ViewAction/ }
end
shared_examples 'it should not have Gmail Actions links' do
it { is_expected.to_not have_body_text /ViewAction/ }
end
shared_examples 'it should show Gmail Actions View Issue link' do
it_behaves_like 'it should have Gmail Actions links'
it { is_expected.to have_body_text /View Issue/ }
end
shared_examples 'it should show Gmail Actions View Merge request link' do
it_behaves_like 'it should have Gmail Actions links'
it { is_expected.to have_body_text /View Merge request/ }
end
shared_examples 'it should show Gmail Actions View Commit link' do
it_behaves_like 'it should have Gmail Actions links'
it { is_expected.to have_body_text /View Commit/ }
end
shared_examples 'an unsubscribeable thread' do
it { is_expected.to have_body_text /unsubscribe/ }
end
shared_examples "a user cannot unsubscribe through footer link" do
it { is_expected.not_to have_body_text /unsubscribe/ }
end
describe 'for new users, the email' do
let(:example_site_path) { root_path }
let(:new_user) { create(:user, email: new_user_address, created_by_id: 1) }
token = 'kETLwRaayvigPq_x3SNM'
subject { Notify.new_user_email(new_user.id, token) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'a new user email', new_user_address
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'contains the password text' do
is_expected.to have_body_text /Click here to set your password/
end
it 'includes a link for user to set password' do
params = "reset_password_token=#{token}"
is_expected.to have_body_text(
%r{http://localhost(:\d+)?/users/password/edit\?#{params}}
)
end
it 'explains the reset link expiration' do
is_expected.to have_body_text(/This link is valid for \d+ (hours?|days?)/)
is_expected.to have_body_text(new_user_password_url)
is_expected.to have_body_text(/\?user_email=.*%40.*/)
end
end
describe 'for users that signed up, the email' do
let(:example_site_path) { root_path }
let(:new_user) { create(:user, email: new_user_address, password: "securePassword") }
subject { Notify.new_user_email(new_user.id) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'a new user email', new_user_address
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'should not contain the new user\'s password' do
is_expected.not_to have_body_text /password/
end
end
describe 'user added ssh key' do
let(:key) { create(:personal_key) }
subject { Notify.new_ssh_key_email(key.id) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'is sent to the new user' do
is_expected.to deliver_to key.user.email
end
it 'has the correct subject' do
is_expected.to have_subject /^SSH key was added to your account$/i
end
it 'contains the new ssh key title' do
is_expected.to have_body_text /#{key.title}/
end
it 'includes a link to ssh keys page' do
is_expected.to have_body_text /#{profile_keys_path}/
end
end
describe 'user added email' do
let(:email) { create(:email) }
subject { Notify.new_email_email(email.id) }
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'is sent to the new user' do
is_expected.to deliver_to email.user.email
end
it 'has the correct subject' do
is_expected.to have_subject /^Email was added to your account$/i
end
it 'contains the new email address' do
is_expected.to have_body_text /#{email.email}/
end
it 'includes a link to emails page' do
is_expected.to have_body_text /#{profile_emails_path}/
end
end
include_context 'gitlab email notification'
context 'for a project' do
describe 'items that are assignable, the email' do
......@@ -971,49 +747,4 @@ describe Notify do
end
end
describe 'build success' do
before { build.success }
subject { Notify.build_success_email(build.id, 'wow@example.com') }
it_behaves_like 'an email with X-GitLab headers containing build details'
it_behaves_like 'an email with X-GitLab headers containing project details' do
let(:project) { build.project }
end
it 'has header indicating build status' do
is_expected.to have_header 'X-GitLab-Build-Status', 'success'
end
it 'has the correct subject' do
should have_subject /Build success for/
end
it 'contains name of project' do
should have_body_text build.project_name
end
end
describe 'build fail' do
before { build.drop }
subject { Notify.build_fail_email(build.id, 'wow@example.com') }
it_behaves_like 'an email with X-GitLab headers containing build details'
it_behaves_like 'an email with X-GitLab headers containing project details' do
let(:project) { build.project }
end
it 'has header indicating build status' do
is_expected.to have_header 'X-GitLab-Build-Status', 'failed'
end
it 'has the correct subject' do
should have_subject /Build failed for/
end
it 'contains name of project' do
should have_body_text build.project_name
end
end
end
shared_context 'gitlab email notification' do
let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name }
let(:gitlab_sender) { Gitlab.config.gitlab.email_from }
let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to }
let(:recipient) { create(:user, email: 'recipient@example.com') }
let(:project) { create(:project) }
let(:new_user_address) { 'newguy@example.com' }
before do
ActionMailer::Base.deliveries.clear
email = recipient.emails.create(email: "notifications@example.com")
recipient.update_attribute(:notification_email, email.email)
end
end
shared_examples 'a multiple recipients email' do
it 'is sent to the given recipient' do
is_expected.to deliver_to recipient.notification_email
end
end
shared_examples 'an email sent from GitLab' do
it 'is sent from GitLab' do
sender = subject.header[:from].addrs[0]
expect(sender.display_name).to eq(gitlab_sender_display_name)
expect(sender.address).to eq(gitlab_sender)
end
it 'has a Reply-To address' do
reply_to = subject.header[:reply_to].addresses
expect(reply_to).to eq([gitlab_sender_reply_to])
end
end
shared_examples 'an email that contains a header with author username' do
it 'has X-GitLab-Author header containing author\'s username' do
is_expected.to have_header 'X-GitLab-Author', user.username
end
end
shared_examples 'an email with X-GitLab headers containing project details' do
it 'has X-GitLab-Project* headers' do
is_expected.to have_header 'X-GitLab-Project', /#{project.name}/
is_expected.to have_header 'X-GitLab-Project-Id', /#{project.id}/
is_expected.to have_header 'X-GitLab-Project-Path', /#{project.path_with_namespace}/
end
end
shared_examples 'an email starting a new thread' do |message_id_prefix|
include_examples 'an email with X-GitLab headers containing project details'
it 'has a discussion identifier' do
is_expected.to have_header 'Message-ID', /<#{message_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
end
end
shared_examples 'an answer to an existing thread' do |thread_id_prefix|
include_examples 'an email with X-GitLab headers containing project details'
it 'has a subject that begins with Re: ' do
is_expected.to have_subject /^Re: /
end
it 'has headers that reference an existing thread' do
is_expected.to have_header 'Message-ID', /<(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'References', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
end
end
shared_examples 'a new user email' do
it 'is sent to the new user' do
is_expected.to deliver_to new_user_address
end
it 'has the correct subject' do
is_expected.to have_subject /^Account was created for you$/i
end
it 'contains the new user\'s login name' do
is_expected.to have_body_text /#{new_user_address}/
end
end
shared_examples 'it should have Gmail Actions links' do
it { is_expected.to have_body_text /ViewAction/ }
end
shared_examples 'it should not have Gmail Actions links' do
it { is_expected.to_not have_body_text /ViewAction/ }
end
shared_examples 'it should show Gmail Actions View Issue link' do
it_behaves_like 'it should have Gmail Actions links'
it { is_expected.to have_body_text /View Issue/ }
end
shared_examples 'it should show Gmail Actions View Merge request link' do
it_behaves_like 'it should have Gmail Actions links'
it { is_expected.to have_body_text /View Merge request/ }
end
shared_examples 'it should show Gmail Actions View Commit link' do
it_behaves_like 'it should have Gmail Actions links'
it { is_expected.to have_body_text /View Commit/ }
end
shared_examples 'an unsubscribeable thread' do
it { is_expected.to have_body_text /unsubscribe/ }
end
shared_examples "a user cannot unsubscribe through footer link" do
it { is_expected.not_to have_body_text /unsubscribe/ }
end
......@@ -74,6 +74,10 @@ describe ApplicationSetting, models: true do
.only_integer
.is_greater_than(0)
end
it_behaves_like 'an object with email-formated attributes', :admin_notification_email do
subject { setting }
end
end
context 'restricted signup domains' do
......
......@@ -69,17 +69,28 @@ describe Issue, "Issuable" do
end
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
expect(hook_data[:object_kind]).to eq("issue")
expect(hook_data[:user]).to eq(user.hook_attrs)
expect(hook_data[:repository][:name]).to eq(issue.project.name)
expect(hook_data[:repository][:url]).to eq(issue.project.url_to_repo)
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(data[:object_kind]).to eq("issue")
expect(data[:user]).to eq(user.hook_attrs)
expect(data[:object_attributes]).to eq(issue.hook_attrs)
expect(data).to_not have_key(:assignee)
end
context "issue is assigned" do
before { issue.update_attribute(:assignee, user) }
it "returns correct hook data" do
expect(data[:object_attributes]['assignee_id']).to eq(user.id)
expect(data[:assignee]).to eq(user.hook_attrs)
end
end
include_examples 'project hook data'
include_examples 'deprecated repository hook data'
end
describe '#card_attributes' do
......
# == Schema Information
#
# Table name: emails
#
# id :integer not null, primary key
# user_id :integer not null
# email :string(255) not null
# created_at :datetime
# updated_at :datetime
#
require 'spec_helper'
describe Email, models: true do
describe 'validations' do
it_behaves_like 'an object with email-formated attributes', :email do
subject { build(:email) }
end
end
end
......@@ -31,6 +31,10 @@ describe Member, models: true do
it { is_expected.to validate_presence_of(:source) }
it { is_expected.to validate_inclusion_of(:access_level).in_array(Gitlab::Access.values) }
it_behaves_like 'an object with email-formated attributes', :invite_email do
subject { build(:project_member) }
end
context "when an invite email is provided" do
let(:member) { build(:project_member, invite_email: "user@example.com", user: nil) }
......
......@@ -254,13 +254,22 @@ describe MergeRequest, models: true do
end
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
attrs = subject.hook_attrs
attrs = attrs.to_h
expect(attrs).to include(:source)
expect(attrs).to include(:target)
expect(attrs).to include(:last_commit)
expect(attrs).to include(:work_in_progress)
expect(attrs_hash).to include(:source)
expect(attrs_hash).to include(:target)
expect(attrs_hash).to include(:last_commit)
expect(attrs_hash).to include(:work_in_progress)
end
end
......
......@@ -354,4 +354,21 @@ describe Repository, models: true do
repository.expire_branch_cache('foo')
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
subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", nil, 100, 0, true).map{ |k| k.id } }
it { is_expected.not_to include('e56497bb5f03a90a51293fc6d516788730953899') }
end
end
......@@ -119,37 +119,15 @@ describe User, models: true do
it { is_expected.to validate_length_of(:bio).is_within(0..255) }
describe 'email' do
it 'accepts info@example.com' do
user = build(:user, email: 'info@example.com')
expect(user).to be_valid
it_behaves_like 'an object with email-formated attributes', :email do
subject { build(:user) }
end
it 'accepts info+test@example.com' do
user = build(:user, email: 'info+test@example.com')
expect(user).to be_valid
end
it "accepts o'reilly@example.com" do
user = build(:user, email: "o'reilly@example.com")
expect(user).to be_valid
end
it 'rejects test@test@example.com' do
user = build(:user, email: 'test@test@example.com')
expect(user).to be_invalid
end
it 'rejects mailto:test@example.com' do
user = build(:user, email: 'mailto:test@example.com')
expect(user).to be_invalid
end
it "rejects lol!'+=?><#$%^&*()@gmail.com" do
user = build(:user, email: "lol!'+=?><#$%^&*()@gmail.com")
expect(user).to be_invalid
it_behaves_like 'an object with email-formated attributes', :public_email, :notification_email do
subject { build(:user).tap { |user| user.emails << build(:email, email: email_value) } }
end
describe 'email' do
context 'when no signup domains listed' do
before { allow(current_application_settings).to receive(:restricted_signup_domains).and_return([]) }
it 'accepts any email' do
......
......@@ -10,6 +10,7 @@ describe API::API, api: true do
let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds) }
let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") }
let(:milestone) { create(:milestone, title: '1.0.0', project: project) }
before do
project.team << [user, :reporters]
......@@ -170,10 +171,12 @@ describe API::API, api: true do
source_branch: 'feature_conflict',
target_branch: 'master',
author: user,
labels: 'label, label2'
labels: 'label, label2',
milestone_id: milestone.id
expect(response.status).to eq(201)
expect(json_response['title']).to eq('Test merge_request')
expect(json_response['labels']).to eq(['label', 'label2'])
expect(json_response['milestone']['id']).to eq(milestone.id)
end
it "should return 422 when source_branch equals target_branch" do
......@@ -374,18 +377,24 @@ describe API::API, api: true do
end
describe "PUT /projects/:id/merge_requests/:merge_request_id" do
it "should return merge_request" do
it "updates title and returns merge_request" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), title: "New title"
expect(response.status).to eq(200)
expect(json_response['title']).to eq('New title')
end
it "should return merge_request" do
it "updates description and returns merge_request" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), description: "New description"
expect(response.status).to eq(200)
expect(json_response['description']).to eq('New description')
end
it "updates milestone_id and returns merge_request" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), milestone_id: milestone.id
expect(response.status).to eq(200)
expect(json_response['milestone']['id']).to eq(milestone.id)
end
it "should return 400 when source_branch is specified" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user),
source_branch: "master", target_branch: "master"
......@@ -449,6 +458,28 @@ describe API::API, api: true do
end
end
describe 'GET :id/merge_requests/:merge_request_id/closes_issues' do
it 'returns the issue that will be closed on merge' do
issue = create(:issue, project: project)
mr = merge_request.tap do |mr|
mr.update_attribute(:description, "Closes #{issue.to_reference(mr.project)}")
end
get api("/projects/#{project.id}/merge_requests/#{mr.id}/closes_issues", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(issue.id)
end
it 'returns an empty array when there are no issues to be closed' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
end
def mr_with_later_created_and_updated_at_time
merge_request
merge_request.created_at += 1.hour
......
......@@ -424,6 +424,21 @@ describe SystemNoteService, services: true do
to be_falsey
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
include JiraServiceHelper
......
# Specifications for behavior common to all objects with an email attribute.
# Takes a list of email-format attributes and requires:
# - subject { "the object with a attribute= setter" }
# Note: You have access to `email_value` which is the email address value
# being currently tested).
shared_examples 'an object with email-formated attributes' do |*attributes|
attributes.each do |attribute|
describe "specifically its :#{attribute} attribute" do
%w[
info@example.com
info+test@example.com
o'reilly@example.com
mailto:test@example.com
lol!'+=?><#$%^&*()@gmail.com
].each do |valid_email|
context "with a value of '#{valid_email}'" do
let(:email_value) { valid_email }
it 'is valid' do
subject.send("#{attribute}=", valid_email)
expect(subject).to be_valid
end
end
end
%w[
foobar
test@test@example.com
].each do |invalid_email|
context "with a value of '#{invalid_email}'" do
let(:email_value) { invalid_email }
it 'is invalid' do
subject.send("#{attribute}=", invalid_email)
expect(subject).to be_invalid
end
end
end
end
end
end
RSpec.shared_examples 'project hook data' do |project_key: :project|
it 'contains project data' do
expect(data[project_key][:name]).to eq(project.name)
expect(data[project_key][:description]).to eq(project.description)
expect(data[project_key][:web_url]).to eq(project.web_url)
expect(data[project_key][:avatar_url]).to eq(project.avatar_url)
expect(data[project_key][:git_http_url]).to eq(project.http_url_to_repo)
expect(data[project_key][:git_ssh_url]).to eq(project.ssh_url_to_repo)
expect(data[project_key][:namespace]).to eq(project.namespace.name)
expect(data[project_key][:visibility_level]).to eq(project.visibility_level)
expect(data[project_key][:path_with_namespace]).to eq(project.path_with_namespace)
expect(data[project_key][:default_branch]).to eq(project.default_branch)
expect(data[project_key][:homepage]).to eq(project.web_url)
expect(data[project_key][:url]).to eq(project.url_to_repo)
expect(data[project_key][:ssh_url]).to eq(project.ssh_url_to_repo)
expect(data[project_key][:http_url]).to eq(project.http_url_to_repo)
end
end
RSpec.shared_examples 'deprecated repository hook data' do |project_key: :project|
it 'contains deprecated repository data' do
expect(data[:repository][:name]).to eq(project.name)
expect(data[:repository][:description]).to eq(project.description)
expect(data[:repository][:url]).to eq(project.url_to_repo)
expect(data[:repository][:homepage]).to eq(project.web_url)
end
end
......@@ -19,6 +19,18 @@ describe RepositoryForkWorker do
fork_project.namespace.path)
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
expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).and_return(false)
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