Commit 4f24eb2a authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into merge-notifs

parents c6799b0e fff36a8b
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 7.14.0 (unreleased) v 7.14.0 (unreleased)
- Fix "Network" and "Graphs" pages for branches with encoded slashes (Stan Hu)
- Fix errors deleting and creating branches with encoded slashes (Stan Hu)
- Fix multi-line syntax highlighting (Stan Hu)
- Fix network graph when branch name has single quotes (Stan Hu) - Fix network graph when branch name has single quotes (Stan Hu)
- Add "Confirm user" button in user admin page (Stan Hu)
- Upgrade gitlab_git to version 7.2.6 to fix Error 500 when creating network graphs (Stan Hu) - Upgrade gitlab_git to version 7.2.6 to fix Error 500 when creating network graphs (Stan Hu)
- Add support for Unicode filenames in relative links (Hiroyuki Sato)
- Fix URL used for refreshing notes if relative_url is present (Bartłomiej Święcki) - Fix URL used for refreshing notes if relative_url is present (Bartłomiej Święcki)
- Fix commit data retrieval when branch name has single quotes (Stan Hu) - Fix commit data retrieval when branch name has single quotes (Stan Hu)
- Check that project was actually created rather than just validated in import:repos task (Stan Hu) - Check that project was actually created rather than just validated in import:repos task (Stan Hu)
...@@ -16,10 +21,23 @@ v 7.14.0 (unreleased) ...@@ -16,10 +21,23 @@ v 7.14.0 (unreleased)
- Set OmniAuth full_host parameter to ensure redirect URIs are correct (Stan Hu) - Set OmniAuth full_host parameter to ensure redirect URIs are correct (Stan Hu)
- Expire Rails cache entries after two weeks to prevent endless Redis growth - Expire Rails cache entries after two weeks to prevent endless Redis growth
- Add support for destroying project milestones (Stan Hu) - Add support for destroying project milestones (Stan Hu)
- Add fetch command to the MR page.
- Allow custom backup archive permissions
- Add fetch command to the MR page - Add fetch command to the MR page
- Add project star and fork count, group avatar URL and user/group web URL attributes to API
- Fix bug causing Bitbucket importer to crash when OAuth application had been removed. - Fix bug causing Bitbucket importer to crash when OAuth application had been removed.
- Add fetch command to the MR page. - Add fetch command to the MR page.
- Send notification to all participants when MR is merged. - Send notification to all participants when MR is merged.
- Add ability to manage user email addresses via the API.
- Show buttons to add license, changelog and contribution guide if they're missing.
- Tweak project page buttons.
- Disabled autocapitalize and autocorrect on login field (Daryl Chan)
- Mention group and project name in creation, update and deletion notices (Achilleas Pipinellis)
- Remove redis-store TTL monkey patch
- Add support for CI skipped status
- Fetch code from forks to refs/merge-requests/:id/head when merge request created
- Remove satellites
- Remove comments and email addresses when publicly exposing ssh keys (Zeger-Jan van de Weg)
v 7.13.2 v 7.13.2
- Fix randomly failed spec - Fix randomly failed spec
...@@ -30,7 +48,6 @@ v 7.13.2 ...@@ -30,7 +48,6 @@ v 7.13.2
- Show the first tab automatically on MergeRequests#new - Show the first tab automatically on MergeRequests#new
- Add rake task 'gitlab:update_commit_count' (Daniel Gerhardt) - Add rake task 'gitlab:update_commit_count' (Daniel Gerhardt)
- Fix Gmail Actions - Fix Gmail Actions
- Disabled autocapitalize and autocorrect on login field (Daryl Chan)
v 7.13.1 v 7.13.1
- Fix: Label modifications are not reflected in existing notes and in the issue list - Fix: Label modifications are not reflected in existing notes and in the issue list
...@@ -46,6 +63,8 @@ v 7.13.1 ...@@ -46,6 +63,8 @@ v 7.13.1
v 7.13.0 v 7.13.0
- Remove repository graph log to fix slow cache updates after push event (Stan Hu) - Remove repository graph log to fix slow cache updates after push event (Stan Hu)
- Return comments in created order in merge request API (Stan Hu) - Return comments in created order in merge request API (Stan Hu)
v 7.13.0 (unreleased)
- Only enable HSTS header for HTTPS and port 443 (Stan Hu) - Only enable HSTS header for HTTPS and port 443 (Stan Hu)
- Fix user autocomplete for unauthenticated users accessing public projects (Stan Hu) - Fix user autocomplete for unauthenticated users accessing public projects (Stan Hu)
- Fix redirection to home page URL for unauthorized users (Daniel Gerhardt) - Fix redirection to home page URL for unauthorized users (Daniel Gerhardt)
...@@ -72,15 +91,15 @@ v 7.13.0 ...@@ -72,15 +91,15 @@ v 7.13.0
- Update ssl_ciphers in Nginx example to remove DHE settings. This will deny forward secrecy for Android 2.3.7, Java 6 and OpenSSL 0.9.8 - Update ssl_ciphers in Nginx example to remove DHE settings. This will deny forward secrecy for Android 2.3.7, Java 6 and OpenSSL 0.9.8
- Admin can edit and remove user identities - Admin can edit and remove user identities
- Convert CRLF newlines to LF when committing using the web editor. - Convert CRLF newlines to LF when committing using the web editor.
- API request /projects/:project_id/merge_requests?state=closed will return only closed merge requests without merged one. If you need ones that were merged - use state=merged. - API request /projects/:project_id/merge_requests?state=closed will return only closed merge requests without merged one. If you need ones that were merged - use state=merged.
- Allow Administrators to filter the user list by those with or without Two-factor Authentication enabled. - Allow Administrators to filter the user list by those with or without Two-factor Authentication enabled.
- Show a user's Two-factor Authentication status in the administration area. - Show a user's Two-factor Authentication status in the administration area.
- Explicit error when commit not found in the CI - Explicit error when commit not found in the CI
- Improve performance for issue and merge request pages - Improve performance for issue and merge request pages
- Users with guest access level can not set assignee, labels or milestones for issue and merge request - Users with guest access level can not set assignee, labels or milestones for issue and merge request
- Reporter role can manage issue tracker now: edit any issue, set assignee or milestone and manage labels - Reporter role can manage issue tracker now: edit any issue, set assignee or milestone and manage labels
- Better performance for pages with events list, issues list and commits list - Better performance for pages with events list, issues list and commits list
- Faster automerge check and merge itself when source and target branches are in same repository - Faster automerge check and merge itself when source and target branches are in same repository
- Correctly show anonymous authorized applications under Profile > Applications. - Correctly show anonymous authorized applications under Profile > Applications.
- Query Optimization in MySQL. - Query Optimization in MySQL.
- Allow users to be blocked and unblocked via the API - Allow users to be blocked and unblocked via the API
...@@ -88,7 +107,7 @@ v 7.13.0 ...@@ -88,7 +107,7 @@ v 7.13.0
- Redesign project page. Show README as default instead of activity. Move project activity to separate page - Redesign project page. Show README as default instead of activity. Move project activity to separate page
- Make left menu more hierarchical and less contextual by adding back item at top - Make left menu more hierarchical and less contextual by adding back item at top
- A fork can’t have a visibility level that is greater than the original project. - A fork can’t have a visibility level that is greater than the original project.
- Faster code search in repository and wiki. Fixes search page timeout for big repositories - Faster code search in repository and wiki. Fixes search page timeout for big repositories
- Allow administrators to disable 2FA for a specific user - Allow administrators to disable 2FA for a specific user
- Add error message for SSH key linebreaks - Add error message for SSH key linebreaks
- Store commits count in database (will populate with valid values only after first push) - Store commits count in database (will populate with valid values only after first push)
...@@ -107,7 +126,7 @@ v 7.12.1 ...@@ -107,7 +126,7 @@ v 7.12.1
- Add SAML to list of social_provider (Matt Firtion) - Add SAML to list of social_provider (Matt Firtion)
- Fix merge requests API scope to keep compatibility in 7.12.x patch release (Dmitriy Zaporozhets) - Fix merge requests API scope to keep compatibility in 7.12.x patch release (Dmitriy Zaporozhets)
- Fix closed merge request scope at milestone page (Dmitriy Zaporozhets) - Fix closed merge request scope at milestone page (Dmitriy Zaporozhets)
- Revert merge request states renaming - Revert merge request states renaming
- Fix hooks for web based events with external issue references (Daniel Gerhardt) - Fix hooks for web based events with external issue references (Daniel Gerhardt)
- Improve performance for issue and merge request pages - Improve performance for issue and merge request pages
- Compress database dumps to reduce backup size - Compress database dumps to reduce backup size
......
...@@ -508,7 +508,7 @@ GEM ...@@ -508,7 +508,7 @@ GEM
rdoc (3.12.2) rdoc (3.12.2)
json (~> 1.4) json (~> 1.4)
redcarpet (3.3.2) redcarpet (3.3.2)
redis (3.1.0) redis (3.2.1)
redis-actionpack (4.0.0) redis-actionpack (4.0.0)
actionpack (~> 4) actionpack (~> 4)
redis-rack (~> 1.5.0) redis-rack (~> 1.5.0)
...@@ -525,7 +525,7 @@ GEM ...@@ -525,7 +525,7 @@ GEM
redis-actionpack (~> 4) redis-actionpack (~> 4)
redis-activesupport (~> 4) redis-activesupport (~> 4)
redis-store (~> 1.1.0) redis-store (~> 1.1.0)
redis-store (1.1.4) redis-store (1.1.6)
redis (>= 2.2) redis (>= 2.2)
request_store (1.0.5) request_store (1.0.5)
rerun (0.10.0) rerun (0.10.0)
...@@ -875,4 +875,4 @@ DEPENDENCIES ...@@ -875,4 +875,4 @@ DEPENDENCIES
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH BUNDLED WITH
1.10.5 1.10.4
# GitLab # GitLab
[![build status](https://ci.gitlab.com/projects/1/status.png?ref=master)](https://ci.gitlab.com/projects/1?ref=master) [![build status](https://ci.gitlab.com/projects/1/status.png?ref=master)](https://ci.gitlab.com/projects/1?ref=master)
[![Build Status](https://semaphoreapp.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/243338/badge.png)](https://semaphoreapp.com/gitlabhq/gitlabhq) [![Build Status](https://semaphoreci.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/400484/shields_badge.svg)](https://semaphoreci.com/gitlabhq/gitlabhq)
[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
[![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master) [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master)
...@@ -69,7 +69,7 @@ Instructions on how to start GitLab and how to run the tests can be found in the ...@@ -69,7 +69,7 @@ Instructions on how to start GitLab and how to run the tests can be found in the
GitLab is a Ruby on Rails application that runs on the following software: GitLab is a Ruby on Rails application that runs on the following software:
- Ubuntu/Debian/CentOS/RHEL - Ubuntu/Debian/CentOS/RHEL
- Ruby (MRI) 2.0 or 2.1 - Ruby (MRI) 2.1
- Git 1.7.10+ - Git 1.7.10+
- Redis 2.0+ - Redis 2.0+
- MySQL or PostgreSQL - MySQL or PostgreSQL
...@@ -103,4 +103,4 @@ Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on ...@@ -103,4 +103,4 @@ Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on
## Is it awesome? ## Is it awesome?
Thanks for [asking this question](https://twitter.com/supersloth/status/489462789384056832) Joshua. Thanks for [asking this question](https://twitter.com/supersloth/status/489462789384056832) Joshua.
[These people](https://twitter.com/gitlab/favorites) seem to like it. [These people](https://twitter.com/gitlab/favorites) seem to like it.
\ No newline at end of file
...@@ -19,7 +19,7 @@ class @MergeRequestWidget ...@@ -19,7 +19,7 @@ class @MergeRequestWidget
when 'merged' when 'merged'
location.reload() location.reload()
else else
setTimeout(merge_request_widget.mergeInProgress, 3000) setTimeout(merge_request_widget.mergeInProgress, 2000)
dataType: 'json' dataType: 'json'
getMergeStatus: -> getMergeStatus: ->
...@@ -36,7 +36,7 @@ class @MergeRequestWidget ...@@ -36,7 +36,7 @@ class @MergeRequestWidget
showCiState: (state) -> showCiState: (state) ->
$('.ci_widget').hide() $('.ci_widget').hide()
allowed_states = ["failed", "canceled", "running", "pending", "success", "not_found"] allowed_states = ["failed", "canceled", "running", "pending", "success", "skipped", "not_found"]
if state in allowed_states if state in allowed_states
$('.ci_widget.ci-' + state).show() $('.ci_widget.ci-' + state).show()
switch state switch state
......
class @Project class @Project
constructor: -> constructor: ->
# Git clone panel switcher # Git clone panel switcher
scope = $ '.git-clone-holder' cloneHolder = $('.git-clone-holder')
if scope.length > 0 if cloneHolder.length
$('a, button', scope).click -> $('a, button', cloneHolder).click ->
$('a, button', scope).removeClass 'active' $('a, button', cloneHolder).removeClass 'active'
$(@).addClass 'active' $(@).addClass 'active'
$('#project_clone', scope).val $(@).data 'clone' $('#project_clone', cloneHolder).val $(@).data 'clone'
$(".clone").text("").append $(@).data 'clone' $(".clone").text("").append $(@).data 'clone'
# Ref switcher # Ref switcher
...@@ -24,3 +24,8 @@ class @Project ...@@ -24,3 +24,8 @@ class @Project
$.cookie('hide_no_password_message', 'false', { path: path }) $.cookie('hide_no_password_message', 'false', { path: path })
$(@).parents('.no-password-message').remove() $(@).parents('.no-password-message').remove()
e.preventDefault() e.preventDefault()
$('.js-toggle-clone-holder').on 'click', (e) ->
cloneHolder.toggle()
cloneHolder.hide() unless $('.empty-project').length
...@@ -70,7 +70,7 @@ ...@@ -70,7 +70,7 @@
font-family: $monospace_font; font-family: $monospace_font;
white-space: pre; white-space: pre;
word-wrap: normal; word-wrap: normal;
padding: 0; padding: 1px 2px;
} }
kbd { kbd {
......
...@@ -38,6 +38,10 @@ code { ...@@ -38,6 +38,10 @@ code {
} }
} }
a > code {
color: $link-color;
}
/** /**
* Wiki typography * Wiki typography
* *
......
...@@ -139,6 +139,11 @@ ...@@ -139,6 +139,11 @@
color: $gl-success; color: $gl-success;
} }
&.ci-skipped {
background-color: #eee;
color: #888;
}
&.ci-pending, &.ci-pending,
&.ci-running { &.ci-running {
color: $gl-warning; color: $gl-warning;
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
.project-home-panel { .project-home-panel {
text-align: center; text-align: center;
margin-bottom: 20px;
.project-identicon-holder { .project-identicon-holder {
margin-bottom: 15px; margin-bottom: 15px;
...@@ -39,7 +38,7 @@ ...@@ -39,7 +38,7 @@
.git-clone-holder { .git-clone-holder {
max-width: 600px; max-width: 600px;
margin: 0 auto; margin: 20px auto;
} }
.visibility-level-label { .visibility-level-label {
...@@ -297,6 +296,15 @@ table.table.protected-branches-list tr.no-border { ...@@ -297,6 +296,15 @@ table.table.protected-branches-list tr.no-border {
ul.nav-pills { display:inline-block; } ul.nav-pills { display:inline-block; }
li { display:inline; } li { display:inline; }
a { float:left; } a { float:left; }
li.missing a {
color: #bbb;
border: 1px dashed #ccc;
&:hover {
background-color: #FAFAFA;
}
}
} }
pre.light-well { pre.light-well {
......
...@@ -60,11 +60,7 @@ ...@@ -60,11 +60,7 @@
} }
.tree_author { .tree_author {
padding-right: 8px; padding-left: 8px;
.commit-author-name {
color: gray;
}
} }
.tree_commit { .tree_commit {
......
...@@ -55,6 +55,14 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -55,6 +55,14 @@ class Admin::UsersController < Admin::ApplicationController
end end
end end
def confirm
if user.confirm!
redirect_to :back, notice: "Successfully confirmed"
else
redirect_to :back, alert: "Error occurred. User was not confirmed"
end
end
def disable_two_factor def disable_two_factor
user.disable_two_factor! user.disable_two_factor!
redirect_to admin_user_path(user), redirect_to admin_user_path(user),
......
...@@ -18,4 +18,10 @@ class Groups::ApplicationController < ApplicationController ...@@ -18,4 +18,10 @@ class Groups::ApplicationController < ApplicationController
return render_404 return render_404
end end
end end
def authorize_admin_group_member!
unless can?(current_user, :admin_group_member, group)
return render_403
end
end
end end
...@@ -5,6 +5,7 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -5,6 +5,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
# Authorize # Authorize
before_action :authorize_read_group! before_action :authorize_read_group!
before_action :authorize_admin_group!, except: [:index, :leave] before_action :authorize_admin_group!, except: [:index, :leave]
before_action :authorize_admin_group_member!, only: [:create, :resend_invite]
def index def index
@project = @group.projects.find(params[:project_id]) if params[:project_id] @project = @group.projects.find(params[:project_id]) if params[:project_id]
...@@ -28,6 +29,9 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -28,6 +29,9 @@ class Groups::GroupMembersController < Groups::ApplicationController
def update def update
@member = @group.group_members.find(params[:id]) @member = @group.group_members.find(params[:id])
return render_403 unless can?(current_user, :update_group_member, @member)
@member.update_attributes(member_params) @member.update_attributes(member_params)
end end
......
...@@ -24,7 +24,7 @@ class GroupsController < Groups::ApplicationController ...@@ -24,7 +24,7 @@ class GroupsController < Groups::ApplicationController
if @group.save if @group.save
@group.add_owner(current_user) @group.add_owner(current_user)
redirect_to @group, notice: 'Group was successfully created.' redirect_to @group, notice: "Group '#{@group.name}' was successfully created."
else else
render action: "new" render action: "new"
end end
...@@ -75,7 +75,7 @@ class GroupsController < Groups::ApplicationController ...@@ -75,7 +75,7 @@ class GroupsController < Groups::ApplicationController
def update def update
if @group.update_attributes(group_params) if @group.update_attributes(group_params)
redirect_to edit_group_path(@group), notice: 'Group was successfully updated.' redirect_to edit_group_path(@group), notice: "Group '#{@group.name}' was successfully updated."
else else
render action: "edit" render action: "edit"
end end
...@@ -84,7 +84,7 @@ class GroupsController < Groups::ApplicationController ...@@ -84,7 +84,7 @@ class GroupsController < Groups::ApplicationController
def destroy def destroy
DestroyGroupService.new(@group, current_user).execute DestroyGroupService.new(@group, current_user).execute
redirect_to root_path, notice: 'Group was removed.' redirect_to root_path, alert: "Group '#{@group.name} was deleted."
end end
protected protected
......
...@@ -17,7 +17,9 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -17,7 +17,9 @@ class Projects::BranchesController < Projects::ApplicationController
def create def create
branch_name = sanitize(strip_tags(params[:branch_name])) branch_name = sanitize(strip_tags(params[:branch_name]))
branch_name = Addressable::URI.unescape(branch_name)
ref = sanitize(strip_tags(params[:ref])) ref = sanitize(strip_tags(params[:ref]))
ref = Addressable::URI.unescape(ref)
result = CreateBranchService.new(project, current_user). result = CreateBranchService.new(project, current_user).
execute(branch_name, ref) execute(branch_name, ref)
...@@ -32,9 +34,8 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -32,9 +34,8 @@ class Projects::BranchesController < Projects::ApplicationController
end end
def destroy def destroy
status = DeleteBranchService.new(project, current_user).execute(params[:id]) @branch_name = Addressable::URI.unescape(params[:id])
@branch_name = params[:id] status = DeleteBranchService.new(project, current_user).execute(@branch_name)
respond_to do |format| respond_to do |format|
format.html do format.html do
redirect_to namespace_project_branches_path(@project.namespace, redirect_to namespace_project_branches_path(@project.namespace,
......
...@@ -13,13 +13,8 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -13,13 +13,8 @@ class Projects::CompareController < Projects::ApplicationController
base_ref = Addressable::URI.unescape(params[:from]) base_ref = Addressable::URI.unescape(params[:from])
@ref = head_ref = Addressable::URI.unescape(params[:to]) @ref = head_ref = Addressable::URI.unescape(params[:to])
compare_result = CompareService.new.execute( compare_result = CompareService.new.
current_user, execute(@project, head_ref, @project, base_ref)
@project,
head_ref,
@project,
base_ref
)
@commits = compare_result.commits @commits = compare_result.commits
@diffs = compare_result.diffs @diffs = compare_result.diffs
......
require 'gitlab/satellite/satellite'
class Projects::MergeRequestsController < Projects::ApplicationController class Projects::MergeRequestsController < Projects::ApplicationController
before_action :module_enabled before_action :module_enabled
before_action :merge_request, only: [ before_action :merge_request, only: [
:edit, :update, :show, :diffs, :commits, :automerge, :automerge_check, :edit, :update, :show, :diffs, :commits, :merge, :merge_check,
:ci_status, :toggle_subscription :ci_status, :toggle_subscription
] ]
before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits] before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits]
...@@ -137,7 +135,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -137,7 +135,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
end end
def automerge_check def merge_check
if @merge_request.unchecked? if @merge_request.unchecked?
@merge_request.check_if_can_be_merged @merge_request.check_if_can_be_merged
end end
...@@ -147,11 +145,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -147,11 +145,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
render partial: "projects/merge_requests/widget/show.html.haml", layout: false render partial: "projects/merge_requests/widget/show.html.haml", layout: false
end end
def automerge def merge
return access_denied! unless @merge_request.can_be_merged_by?(current_user) return access_denied! unless @merge_request.can_be_merged_by?(current_user)
if @merge_request.automergeable? if @merge_request.mergeable?
AutoMergeWorker.perform_async(@merge_request.id, current_user.id, params) MergeWorker.perform_async(@merge_request.id, current_user.id, params)
@status = true @status = true
else else
@status = false @status = false
......
...@@ -24,7 +24,7 @@ class ProjectsController < ApplicationController ...@@ -24,7 +24,7 @@ class ProjectsController < ApplicationController
if @project.saved? if @project.saved?
redirect_to( redirect_to(
project_path(@project), project_path(@project),
notice: 'Project was successfully created.' notice: "Project '#{@project.name}' was successfully created."
) )
else else
render 'new' render 'new'
...@@ -36,11 +36,11 @@ class ProjectsController < ApplicationController ...@@ -36,11 +36,11 @@ class ProjectsController < ApplicationController
respond_to do |format| respond_to do |format|
if status if status
flash[:notice] = 'Project was successfully updated.' flash[:notice] = "Project '#{@project.name}' was successfully updated."
format.html do format.html do
redirect_to( redirect_to(
edit_project_path(@project), edit_project_path(@project),
notice: 'Project was successfully updated.' notice: "Project '#{@project.name}' was successfully updated."
) )
end end
format.js format.js
...@@ -100,7 +100,7 @@ class ProjectsController < ApplicationController ...@@ -100,7 +100,7 @@ class ProjectsController < ApplicationController
return access_denied! unless can?(current_user, :remove_project, @project) return access_denied! unless can?(current_user, :remove_project, @project)
::Projects::DestroyService.new(@project, current_user, {}).execute ::Projects::DestroyService.new(@project, current_user, {}).execute
flash[:alert] = 'Project deleted.' flash[:alert] = "Project '#{@project.name}' was deleted."
if request.referer.include?('/admin') if request.referer.include?('/admin')
redirect_to admin_namespaces_projects_path redirect_to admin_namespaces_projects_path
......
...@@ -184,7 +184,43 @@ module ProjectsHelper ...@@ -184,7 +184,43 @@ module ProjectsHelper
end end
end end
def contribution_guide_url(project) def add_contribution_guide_path(project)
if project && !project.repository.contribution_guide
namespace_project_new_blob_path(
project.namespace,
project,
project.default_branch,
file_name: "CONTRIBUTING.md",
commit_message: "Add contribution guide"
)
end
end
def add_changelog_path(project)
if project && !project.repository.changelog
namespace_project_new_blob_path(
project.namespace,
project,
project.default_branch,
file_name: "CHANGELOG",
commit_message: "Add changelog"
)
end
end
def add_license_path(project)
if project && !project.repository.license
namespace_project_new_blob_path(
project.namespace,
project,
project.default_branch,
file_name: "LICENSE",
commit_message: "Add license"
)
end
end
def contribution_guide_path(project)
if project && contribution_guide = project.repository.contribution_guide if project && contribution_guide = project.repository.contribution_guide
namespace_project_blob_path( namespace_project_blob_path(
project.namespace, project.namespace,
...@@ -195,7 +231,7 @@ module ProjectsHelper ...@@ -195,7 +231,7 @@ module ProjectsHelper
end end
end end
def changelog_url(project) def changelog_path(project)
if project && changelog = project.repository.changelog if project && changelog = project.repository.changelog
namespace_project_blob_path( namespace_project_blob_path(
project.namespace, project.namespace,
...@@ -206,7 +242,7 @@ module ProjectsHelper ...@@ -206,7 +242,7 @@ module ProjectsHelper
end end
end end
def license_url(project) def license_path(project)
if project && license = project.repository.license if project && license = project.repository.license
namespace_project_blob_path( namespace_project_blob_path(
project.namespace, project.namespace,
...@@ -217,7 +253,7 @@ module ProjectsHelper ...@@ -217,7 +253,7 @@ module ProjectsHelper
end end
end end
def version_url(project) def version_path(project)
if project && version = project.repository.version if project && version = project.repository.version
namespace_project_blob_path( namespace_project_blob_path(
project.namespace, project.namespace,
......
...@@ -233,7 +233,8 @@ class Ability ...@@ -233,7 +233,8 @@ class Ability
if group.has_owner?(user) || user.admin? if group.has_owner?(user) || user.admin?
rules.push(*[ rules.push(*[
:admin_group, :admin_group,
:admin_namespace :admin_namespace,
:admin_group_member
]) ])
end end
...@@ -295,7 +296,7 @@ class Ability ...@@ -295,7 +296,7 @@ class Ability
rules = [] rules = []
target_user = subject.user target_user = subject.user
group = subject.group group = subject.group
can_manage = group_abilities(user, group).include?(:admin_group) can_manage = group_abilities(user, group).include?(:admin_group_member)
if can_manage && (user != target_user) if can_manage && (user != target_user)
rules << :update_group_member rules << :update_group_member
......
...@@ -14,13 +14,14 @@ ...@@ -14,13 +14,14 @@
# default_branch_protection :integer default(2) # default_branch_protection :integer default(2)
# twitter_sharing_enabled :boolean default(TRUE) # twitter_sharing_enabled :boolean default(TRUE)
# restricted_visibility_levels :text # restricted_visibility_levels :text
# version_check_enabled :boolean default(TRUE)
# max_attachment_size :integer default(10), not null # max_attachment_size :integer default(10), not null
# session_expire_delay :integer default(10080), not null
# default_project_visibility :integer # default_project_visibility :integer
# default_snippet_visibility :integer # default_snippet_visibility :integer
# restricted_signup_domains :text # restricted_signup_domains :text
# user_oauth_applications :bool default(TRUE) # user_oauth_applications :boolean default(TRUE)
# after_sign_out_path :string(255) # after_sign_out_path :string(255)
# session_expire_delay :integer default(10080), not null
# #
class ApplicationSetting < ActiveRecord::Base class ApplicationSetting < ActiveRecord::Base
......
# == Schema Information
#
# Table name: audit_events
#
# id :integer not null, primary key
# author_id :integer not null
# type :string(255) not null
# entity_id :integer not null
# entity_type :string(255) not null
# details :text
# created_at :datetime
# updated_at :datetime
#
class AuditEvent < ActiveRecord::Base class AuditEvent < ActiveRecord::Base
serialize :details, Hash serialize :details, Hash
......
...@@ -56,6 +56,12 @@ class Group < Namespace ...@@ -56,6 +56,12 @@ class Group < Namespace
name name
end end
def avatar_url(size = nil)
if avatar.present?
[gitlab_config.url, avatar.url].join
end
end
def owners def owners
@owners ||= group_members.owners.map(&:user) @owners ||= group_members.owners.map(&:user)
end end
......
...@@ -39,6 +39,11 @@ class Key < ActiveRecord::Base ...@@ -39,6 +39,11 @@ class Key < ActiveRecord::Base
self.key = key.strip unless key.blank? self.key = key.strip unless key.blank?
end end
def publishable_key
#Removes anything beyond the keytype and key itself
self.key.split[0..1].join(' ')
end
# projects that has this key # projects that has this key
def projects def projects
user.authorized_projects user.authorized_projects
......
...@@ -41,8 +41,6 @@ class MergeRequest < ActiveRecord::Base ...@@ -41,8 +41,6 @@ class MergeRequest < ActiveRecord::Base
delegate :commits, :diffs, :last_commit, :last_commit_short_sha, to: :merge_request_diff, prefix: nil delegate :commits, :diffs, :last_commit, :last_commit_short_sha, to: :merge_request_diff, prefix: nil
attr_accessor :should_remove_source_branch
# When this attribute is true some MR validation is ignored # When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests # It allows us to close or modify broken merge requests
attr_accessor :allow_broken attr_accessor :allow_broken
...@@ -57,7 +55,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -57,7 +55,7 @@ class MergeRequest < ActiveRecord::Base
transition [:reopened, :opened] => :closed transition [:reopened, :opened] => :closed
end end
event :merge do event :mark_as_merged do
transition [:reopened, :opened, :locked] => :merged transition [:reopened, :opened, :locked] => :merged
end end
...@@ -206,11 +204,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -206,11 +204,7 @@ class MergeRequest < ActiveRecord::Base
def check_if_can_be_merged def check_if_can_be_merged
can_be_merged = can_be_merged =
if for_fork? project.repository.can_be_merged?(source_sha, target_branch)
Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged?
else
project.repository.can_be_merged?(source_branch, target_branch)
end
if can_be_merged if can_be_merged
mark_as_mergeable mark_as_mergeable
...@@ -227,18 +221,6 @@ class MergeRequest < ActiveRecord::Base ...@@ -227,18 +221,6 @@ class MergeRequest < ActiveRecord::Base
self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
end end
def automerge!(current_user, commit_message = nil)
return unless automergeable?
MergeRequests::AutoMergeService.
new(target_project, current_user).
execute(self, commit_message)
end
def remove_source_branch?
self.should_remove_source_branch && !self.source_project.root_ref?(self.source_branch) && !self.for_fork?
end
def open? def open?
opened? || reopened? opened? || reopened?
end end
...@@ -247,11 +229,11 @@ class MergeRequest < ActiveRecord::Base ...@@ -247,11 +229,11 @@ class MergeRequest < ActiveRecord::Base
title =~ /\A\[?WIP\]?:? /i title =~ /\A\[?WIP\]?:? /i
end end
def automergeable? def mergeable?
open? && !work_in_progress? && can_be_merged? open? && !work_in_progress? && can_be_merged?
end end
def automerge_status def gitlab_merge_status
if work_in_progress? if work_in_progress?
"work_in_progress" "work_in_progress"
else else
...@@ -278,14 +260,14 @@ class MergeRequest < ActiveRecord::Base ...@@ -278,14 +260,14 @@ class MergeRequest < ActiveRecord::Base
# #
# see "git diff" # see "git diff"
def to_diff(current_user) def to_diff(current_user)
Gitlab::Satellite::MergeAction.new(current_user, self).diff_in_satellite target_project.repository.diff_text(target_branch, source_sha)
end end
# Returns the commit as a series of email patches. # Returns the commit as a series of email patches.
# #
# see "git format-patch" # see "git format-patch"
def to_patch(current_user) def to_patch(current_user)
Gitlab::Satellite::MergeAction.new(current_user, self).format_patch target_project.repository.format_patch(target_branch, source_sha)
end end
def hook_attrs def hook_attrs
...@@ -436,4 +418,30 @@ class MergeRequest < ActiveRecord::Base ...@@ -436,4 +418,30 @@ class MergeRequest < ActiveRecord::Base
"Open" "Open"
end end
end end
def target_sha
@target_sha ||= target_project.
repository.commit(target_branch).sha
end
def source_sha
commits.first.sha
end
def fetch_ref
target_project.repository.fetch_ref(
source_project.repository.path_to_repo,
"refs/heads/#{source_branch}",
"refs/merge-requests/#{id}/head"
)
end
def in_locked_state
begin
lock_mr
yield
ensure
unlock_mr if locked?
end
end
end end
...@@ -16,9 +16,8 @@ require Rails.root.join("app/models/commit") ...@@ -16,9 +16,8 @@ require Rails.root.join("app/models/commit")
class MergeRequestDiff < ActiveRecord::Base class MergeRequestDiff < ActiveRecord::Base
include Sortable include Sortable
# Prevent store of diff # Prevent store of diff if commits amount more then 500
# if commits amount more then 200 COMMITS_SAFE_SIZE = 500
COMMITS_SAFE_SIZE = 200
attr_reader :commits, :diffs attr_reader :commits, :diffs
...@@ -124,12 +123,12 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -124,12 +123,12 @@ class MergeRequestDiff < ActiveRecord::Base
if new_diffs.any? if new_diffs.any?
if new_diffs.size > Commit::DIFF_HARD_LIMIT_FILES if new_diffs.size > Commit::DIFF_HARD_LIMIT_FILES
self.state = :overflow_diff_files_limit self.state = :overflow_diff_files_limit
new_diffs = [] new_diffs = new_diffs.first[Commit::DIFF_HARD_LIMIT_LINES]
end end
if new_diffs.sum { |diff| diff.diff.lines.count } > Commit::DIFF_HARD_LIMIT_LINES if new_diffs.sum { |diff| diff.diff.lines.count } > Commit::DIFF_HARD_LIMIT_LINES
self.state = :overflow_diff_lines_limit self.state = :overflow_diff_lines_limit
new_diffs = [] new_diffs = new_diffs.first[Commit::DIFF_HARD_LIMIT_LINES]
end end
end end
...@@ -160,12 +159,21 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -160,12 +159,21 @@ class MergeRequestDiff < ActiveRecord::Base
private private
def compare_result def compare_result
@compare_result ||= CompareService.new.execute( @compare_result ||=
merge_request.author, begin
merge_request.source_project, # Update ref if merge request is from fork
merge_request.source_branch, merge_request.fetch_ref if merge_request.for_fork?
merge_request.target_project,
merge_request.target_branch, # Get latest sha of branch from source project
) source_sha = merge_request.source_project.commit(source_branch).sha
Gitlab::CompareResult.new(
Gitlab::Git::Compare.new(
merge_request.target_project.repository.raw_repository,
merge_request.target_branch,
source_sha,
)
)
end
end end
end end
...@@ -115,12 +115,11 @@ class Namespace < ActiveRecord::Base ...@@ -115,12 +115,11 @@ class Namespace < ActiveRecord::Base
def move_dir def move_dir
if gitlab_shell.mv_namespace(path_was, path) if gitlab_shell.mv_namespace(path_was, path)
# If repositories moved successfully we need to remove old satellites # If repositories moved successfully we need to
# and send update instructions to users. # send update instructions to users.
# However we cannot allow rollback since we moved namespace dir # However we cannot allow rollback since we moved namespace dir
# So we basically we mute exceptions in next actions # So we basically we mute exceptions in next actions
begin begin
gitlab_shell.rm_satellites(path_was)
send_update_instructions send_update_instructions
rescue rescue
# Returning false does not rollback after_* transaction but gives # Returning false does not rollback after_* transaction but gives
......
...@@ -31,7 +31,7 @@ class Note < ActiveRecord::Base ...@@ -31,7 +31,7 @@ class Note < ActiveRecord::Base
participant :author, :mentioned_users participant :author, :mentioned_users
belongs_to :project belongs_to :project
belongs_to :noteable, polymorphic: true, touch: true belongs_to :noteable, polymorphic: true
belongs_to :author, class_name: "User" belongs_to :author, class_name: "User"
delegate :name, to: :project, prefix: true delegate :name, to: :project, prefix: true
......
...@@ -21,12 +21,13 @@ ...@@ -21,12 +21,13 @@
# import_url :string(255) # import_url :string(255)
# visibility_level :integer default(0), not null # visibility_level :integer default(0), not null
# archived :boolean default(FALSE), not null # archived :boolean default(FALSE), not null
# avatar :string(255)
# import_status :string(255) # import_status :string(255)
# repository_size :float default(0.0) # repository_size :float default(0.0)
# star_count :integer default(0), not null # star_count :integer default(0), not null
# import_type :string(255) # import_type :string(255)
# import_source :string(255) # import_source :string(255)
# avatar :string(255) # commit_count :integer default(0)
# #
require 'carrierwave/orm/activerecord' require 'carrierwave/orm/activerecord'
...@@ -36,7 +37,6 @@ class Project < ActiveRecord::Base ...@@ -36,7 +37,6 @@ class Project < ActiveRecord::Base
include Gitlab::ConfigHelper include Gitlab::ConfigHelper
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
include Gitlab::VisibilityLevel include Gitlab::VisibilityLevel
include Rails.application.routes.url_helpers
include Referable include Referable
include Sortable include Sortable
...@@ -316,7 +316,7 @@ class Project < ActiveRecord::Base ...@@ -316,7 +316,7 @@ class Project < ActiveRecord::Base
end end
def web_url def web_url
[gitlab_config.url, path_with_namespace].join('/') Rails.application.routes.url_helpers.namespace_project_url(self.namespace, self)
end end
def web_url_without_protocol def web_url_without_protocol
...@@ -433,7 +433,7 @@ class Project < ActiveRecord::Base ...@@ -433,7 +433,7 @@ class Project < ActiveRecord::Base
if avatar.present? if avatar.present?
[gitlab_config.url, avatar.url].join [gitlab_config.url, avatar.url].join
elsif avatar_in_git elsif avatar_in_git
[gitlab_config.url, namespace_project_avatar_path(namespace, self)].join Rails.application.routes.url_helpers.namespace_project_avatar_url(namespace, self)
end end
end end
...@@ -520,14 +520,6 @@ class Project < ActiveRecord::Base ...@@ -520,14 +520,6 @@ class Project < ActiveRecord::Base
!repository.exists? || repository.empty? !repository.exists? || repository.empty?
end end
def ensure_satellite_exists
self.satellite.create unless self.satellite.exists?
end
def satellite
@satellite ||= Gitlab::Satellite::Satellite.new(self)
end
def repo def repo
repository.raw repository.raw
end end
...@@ -571,7 +563,7 @@ class Project < ActiveRecord::Base ...@@ -571,7 +563,7 @@ class Project < ActiveRecord::Base
end end
def http_url_to_repo def http_url_to_repo
[gitlab_config.url, '/', path_with_namespace, '.git'].join('') "#{web_url}.git"
end end
# Check if current branch name is marked as protected in the system # Check if current branch name is marked as protected in the system
...@@ -597,14 +589,11 @@ class Project < ActiveRecord::Base ...@@ -597,14 +589,11 @@ class Project < ActiveRecord::Base
new_path_with_namespace = File.join(namespace_dir, path) new_path_with_namespace = File.join(namespace_dir, path)
if gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace) if gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace)
# If repository moved successfully we need to remove old satellite # If repository moved successfully we need to send update instructions to users.
# and send update instructions to users.
# However we cannot allow rollback since we moved repository # However we cannot allow rollback since we moved repository
# So we basically we mute exceptions in next actions # So we basically we mute exceptions in next actions
begin begin
gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki") gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
gitlab_shell.rm_satellites(old_path_with_namespace)
ensure_satellite_exists
send_move_instructions send_move_instructions
reset_events_cache reset_events_cache
rescue rescue
...@@ -702,7 +691,6 @@ class Project < ActiveRecord::Base ...@@ -702,7 +691,6 @@ class Project < ActiveRecord::Base
def create_repository def create_repository
if forked? if forked?
if gitlab_shell.fork_repository(forked_from_project.path_with_namespace, self.namespace.path) if gitlab_shell.fork_repository(forked_from_project.path_with_namespace, self.namespace.path)
ensure_satellite_exists
true true
else else
errors.add(:base, 'Failed to fork repository via gitlab-shell') errors.add(:base, 'Failed to fork repository via gitlab-shell')
......
...@@ -41,7 +41,7 @@ class CiService < Service ...@@ -41,7 +41,7 @@ class CiService < Service
# Return string with build status or :error symbol # Return string with build status or :error symbol
# #
# Allowed states: 'success', 'failed', 'running', 'pending' # Allowed states: 'success', 'failed', 'running', 'pending', 'skipped'
# #
# #
# Ex. # Ex.
......
...@@ -74,6 +74,8 @@ class GitlabCiService < CiService ...@@ -74,6 +74,8 @@ class GitlabCiService < CiService
else else
:error :error
end end
rescue Errno::ECONNREFUSED
:error
end end
def fork_registration(new_project, private_token) def fork_registration(new_project, private_token)
...@@ -103,6 +105,8 @@ class GitlabCiService < CiService ...@@ -103,6 +105,8 @@ class GitlabCiService < CiService
if response.code == 200 and response["coverage"] if response.code == 200 and response["coverage"]
response["coverage"] response["coverage"]
end end
rescue Errno::ECONNREFUSED
nil
end end
def build_page(sha, ref) def build_page(sha, ref)
......
...@@ -411,15 +411,36 @@ class Repository ...@@ -411,15 +411,36 @@ class Repository
} }
end end
def can_be_merged?(source_branch, target_branch) def can_be_merged?(source_sha, target_branch)
our_commit = rugged.branches[target_branch].target our_commit = rugged.branches[target_branch].target
their_commit = rugged.branches[source_branch].target their_commit = rugged.lookup(source_sha)
if our_commit && their_commit if our_commit && their_commit
!rugged.merge_commits(our_commit, their_commit).conflicts? !rugged.merge_commits(our_commit, their_commit).conflicts?
else
false
end end
end end
def merge(source_sha, target_branch, options = {})
our_commit = rugged.branches[target_branch].target
their_commit = rugged.lookup(source_sha)
raise "Invalid merge target" if our_commit.nil?
raise "Invalid merge source" if their_commit.nil?
merge_index = rugged.merge_commits(our_commit, their_commit)
return false if merge_index.conflicts?
actual_options = options.merge(
parents: [our_commit, their_commit],
tree: merge_index.write_tree(rugged),
update_ref: "refs/heads/#{target_branch}"
)
Rugged::Commit.create(rugged, actual_options)
end
def search_files(query, ref) def search_files(query, ref)
offset = 2 offset = 2
args = %W(git grep -i -n --before-context #{offset} --after-context #{offset} #{query} #{ref || root_ref}) args = %W(git grep -i -n --before-context #{offset} --after-context #{offset} #{query} #{ref || root_ref})
...@@ -453,6 +474,11 @@ class Repository ...@@ -453,6 +474,11 @@ class Repository
) )
end end
def fetch_ref(source_path, source_ref, target_ref)
args = %W(git fetch #{source_path} #{source_ref}:#{target_ref})
Gitlab::Popen.popen(args, path_to_repo)
end
private private
def cache def cache
......
# == Schema Information
#
# Table name: audit_events
#
# id :integer not null, primary key
# author_id :integer not null
# type :string(255) not null
# entity_id :integer not null
# entity_type :string(255) not null
# details :text
# created_at :datetime
# updated_at :datetime
#
class SecurityEvent < AuditEvent class SecurityEvent < AuditEvent
end end
...@@ -57,6 +57,7 @@ ...@@ -57,6 +57,7 @@
# otp_backup_codes :text # otp_backup_codes :text
# public_email :string(255) default(""), not null # public_email :string(255) default(""), not null
# dashboard :integer default(0) # dashboard :integer default(0)
# project_view :integer default(0)
# #
require 'carrierwave/orm/activerecord' require 'carrierwave/orm/activerecord'
...@@ -618,7 +619,7 @@ class User < ActiveRecord::Base ...@@ -618,7 +619,7 @@ class User < ActiveRecord::Base
end end
def all_ssh_keys def all_ssh_keys
keys.map(&:key) keys.map(&:publishable_key)
end end
def temp_oauth_email? def temp_oauth_email?
......
...@@ -31,6 +31,10 @@ class BaseService ...@@ -31,6 +31,10 @@ class BaseService
SystemHooksService.new SystemHooksService.new
end end
def repository
project.repository
end
# Add an error to the specified model for restricted visibility levels # Add an error to the specified model for restricted visibility levels
def deny_visibility_level(model, denied_visibility_level = nil) def deny_visibility_level(model, denied_visibility_level = nil)
denied_visibility_level ||= model.visibility_level denied_visibility_level ||= model.visibility_level
......
require 'securerandom'
# Compare 2 branches for one repo or between repositories # Compare 2 branches for one repo or between repositories
# and return Gitlab::CompareResult object that responds to commits and diffs # and return Gitlab::CompareResult object that responds to commits and diffs
class CompareService class CompareService
def execute(current_user, source_project, source_branch, target_project, target_branch) def execute(source_project, source_branch, target_project, target_branch)
# Try to compare branches to get commits list and diffs source_sha = source_project.commit(source_branch).sha
#
# Note: Use satellite only when need to compare between two repos # If compare with other project we need to fetch ref first
# because satellites are slower than operations on bare repo unless target_project == source_project
if target_project == source_project random_string = SecureRandom.hex
Gitlab::CompareResult.new(
Gitlab::Git::Compare.new( target_project.repository.fetch_ref(
target_project.repository.raw_repository, source_project.repository.path_to_repo,
target_branch, "refs/heads/#{source_branch}",
source_branch, "refs/tmp/#{random_string}/head"
)
) )
else
Gitlab::Satellite::CompareAction.new(
current_user,
target_project,
target_branch,
source_project,
source_branch
).result
end end
Gitlab::CompareResult.new(
Gitlab::Git::Compare.new(
target_project.repository.raw_repository,
target_branch,
source_sha,
)
)
end end
end end
...@@ -33,15 +33,8 @@ module Files ...@@ -33,15 +33,8 @@ module Files
private private
def repository
project.repository
end
def after_commit(sha, branch) def after_commit(sha, branch)
commit = repository.commit(sha) PostCommitService.new(project, current_user).execute(sha, branch)
full_ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch}"
old_sha = commit.parent_id || Gitlab::Git::BLANK_SHA
GitPushService.new.execute(project, current_user, old_sha, sha, full_ref)
end end
def current_branch def current_branch
......
...@@ -10,16 +10,14 @@ class GitPushService ...@@ -10,16 +10,14 @@ class GitPushService
# #
# Next, this method: # Next, this method:
# 1. Creates the push event # 1. Creates the push event
# 2. Ensures that the project satellite exists # 2. Updates merge requests
# 3. Updates merge requests # 3. Recognizes cross-references from commit messages
# 4. Recognizes cross-references from commit messages # 4. Executes the project's web hooks
# 5. Executes the project's web hooks # 5. Executes the project's services
# 6. Executes the project's services
# #
def execute(project, user, oldrev, newrev, ref) def execute(project, user, oldrev, newrev, ref)
@project, @user = project, user @project, @user = project, user
project.ensure_satellite_exists
project.repository.expire_cache project.repository.expire_cache
if push_remove_branch?(ref, newrev) if push_remove_branch?(ref, newrev)
......
module MergeRequests
# AutoMergeService class
#
# Do git merge in satellite and in case of success
# mark merge request as merged and execute all hooks and notifications
# Called when you do merge via GitLab UI
class AutoMergeService < BaseMergeService
attr_reader :merge_request, :commit_message
def execute(merge_request, commit_message)
@commit_message = commit_message
@merge_request = merge_request
merge_request.lock_mr
if merge!
merge_request.merge
create_merge_event(merge_request, current_user)
create_note(merge_request)
notification_service.merge_mr(merge_request, current_user)
execute_hooks(merge_request, 'merge')
true
else
merge_request.unlock_mr
false
end
rescue
merge_request.unlock_mr if merge_request.locked?
merge_request.mark_as_unmergeable
false
end
def merge!
if merge_request.for_fork?
Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message)
else
# Merge local branches using rugged instead of satellites
if sha = commit
after_commit(sha, merge_request.target_branch)
if merge_request.remove_source_branch?
DeleteBranchService.new(merge_request.source_project, current_user).execute(merge_request.source_branch)
end
true
else
false
end
end
end
def commit
committer = repository.user_to_comitter(current_user)
options = {
message: commit_message,
author: committer,
committer: committer
}
repository.merge(merge_request.source_branch, merge_request.target_branch, options)
end
def after_commit(sha, branch)
commit = repository.commit(sha)
full_ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch}"
old_sha = commit.parent_id || Gitlab::Git::BLANK_SHA
GitPushService.new.execute(project, current_user, old_sha, sha, full_ref)
end
def repository
project.repository
end
end
end
module MergeRequests
class BaseMergeService < MergeRequests::BaseService
private
def create_merge_event(merge_request, current_user)
EventCreateService.new.merge_mr(merge_request, current_user)
end
end
end
...@@ -12,12 +12,16 @@ module MergeRequests ...@@ -12,12 +12,16 @@ module MergeRequests
merge_request.target_project ||= (project.forked_from_project || project) merge_request.target_project ||= (project.forked_from_project || project)
merge_request.target_branch ||= merge_request.target_project.default_branch merge_request.target_branch ||= merge_request.target_project.default_branch
unless merge_request.target_branch && merge_request.source_branch if merge_request.target_branch.blank? || merge_request.source_branch.blank?
return build_failed(merge_request, nil) message =
if params[:source_branch] || params[:target_branch]
"You must select source and target branch"
end
return build_failed(merge_request, message)
end end
compare_result = CompareService.new.execute( compare_result = CompareService.new.execute(
current_user,
merge_request.source_project, merge_request.source_project,
merge_request.source_branch, merge_request.source_branch,
merge_request.target_project, merge_request.target_project,
...@@ -40,7 +44,6 @@ module MergeRequests ...@@ -40,7 +44,6 @@ module MergeRequests
merge_request.compare_diffs = diffs merge_request.compare_diffs = diffs
elsif diffs == false elsif diffs == false
# satellite timeout return false
merge_request.can_be_created = false merge_request.can_be_created = false
merge_request.compare_failed = true merge_request.compare_failed = true
end end
...@@ -59,9 +62,6 @@ module MergeRequests ...@@ -59,9 +62,6 @@ module MergeRequests
end end
merge_request merge_request
rescue Gitlab::Satellite::BranchesWithoutParent
return build_failed(merge_request, "Selected branches have no common commit so they cannot be merged.")
end end
def build_failed(merge_request, message) def build_failed(merge_request, message)
......
module MergeRequests module MergeRequests
# MergeService class # MergeService class
# #
# Mark existing merge request as merged # Do git merge and in case of success
# and execute all hooks and notifications # mark merge request as merged and execute all hooks and notifications
# Called when you do merge via command line and push code # Executed when you do merge via GitLab UI
# to target branch #
class MergeService < BaseMergeService class MergeService < MergeRequests::BaseService
attr_reader :merge_request, :commit_message
def execute(merge_request, commit_message) def execute(merge_request, commit_message)
merge_request.merge @commit_message = commit_message
@merge_request = merge_request
create_merge_event(merge_request, current_user) unless @merge_request.mergeable?
create_note(merge_request) return error('Merge request is not mergeable')
notification_service.merge_mr(merge_request, current_user) end
execute_hooks(merge_request, 'merge')
merge_request.in_locked_state do
if merge_changes
after_merge
success
else
error('Can not merge changes')
end
end
end
private
def merge_changes
if sha = commit
after_commit(sha, merge_request.target_branch)
end
end
def commit
committer = repository.user_to_comitter(current_user)
options = {
message: commit_message,
author: committer,
committer: committer
}
repository.merge(merge_request.source_sha, merge_request.target_branch, options)
end
def after_commit(sha, branch)
PostCommitService.new(project, current_user).execute(sha, branch)
end
true def after_merge
rescue MergeRequests::PostMergeService.new(project, current_user).execute(merge_request)
false
end end
end end
end end
module MergeRequests
# PostMergeService class
#
# Mark existing merge request as merged
# and execute all hooks and notifications
#
class PostMergeService < MergeRequests::BaseService
def execute(merge_request)
merge_request.mark_as_merged
create_merge_event(merge_request, current_user)
create_note(merge_request)
notification_service.merge_mr(merge_request, current_user)
execute_hooks(merge_request, 'merge')
end
private
def create_merge_event(merge_request, current_user)
EventCreateService.new.merge_mr(merge_request, current_user)
end
end
end
...@@ -33,9 +33,9 @@ module MergeRequests ...@@ -33,9 +33,9 @@ module MergeRequests
merge_requests.uniq.select(&:source_project).each do |merge_request| merge_requests.uniq.select(&:source_project).each do |merge_request|
MergeRequests::MergeService. MergeRequests::PostMergeService.
new(merge_request.target_project, @current_user). new(merge_request.target_project, @current_user).
execute(merge_request, nil) execute(merge_request)
end end
end end
......
class PostCommitService < BaseService
def execute(sha, branch)
commit = repository.commit(sha)
full_ref = 'refs/heads/' + branch
old_sha = commit.parent_id || Gitlab::Git::BLANK_SHA
GitPushService.new.execute(project, current_user, old_sha, sha, full_ref)
end
end
...@@ -27,7 +27,6 @@ module Projects ...@@ -27,7 +27,6 @@ module Projects
end end
end end
project.satellite.destroy
log_info("Project \"#{project.name}\" was removed") log_info("Project \"#{project.name}\" was removed")
system_hook_service.execute_hooks_for(project, :destroy) system_hook_service.execute_hooks_for(project, :destroy)
true true
......
...@@ -33,9 +33,6 @@ module Projects ...@@ -33,9 +33,6 @@ module Projects
raise TransferError.new("Project with same path in target namespace already exists") raise TransferError.new("Project with same path in target namespace already exists")
end end
# Remove old satellite
project.satellite.destroy
# Apply new namespace id # Apply new namespace id
project.namespace = new_namespace project.namespace = new_namespace
project.save! project.save!
...@@ -51,9 +48,6 @@ module Projects ...@@ -51,9 +48,6 @@ module Projects
# Move wiki repo also if present # Move wiki repo also if present
gitlab_shell.mv_repository("#{old_path}.wiki", "#{new_path}.wiki") gitlab_shell.mv_repository("#{old_path}.wiki", "#{new_path}.wiki")
# Create a new satellite (reload project from DB)
Project.find(project.id).ensure_satellite_exists
# clear project cached events # clear project cached events
project.reset_events_cache project.reset_events_cache
......
...@@ -51,21 +51,22 @@ ...@@ -51,21 +51,22 @@
= paginate @projects, param_name: 'projects_page', theme: 'gitlab' = paginate @projects, param_name: 'projects_page', theme: 'gitlab'
.col-md-6 .col-md-6
.panel.panel-default - if can?(current_user, :admin_group_member, @group)
.panel-heading .panel.panel-default
Add user(s) to the group: .panel-heading
.panel-body.form-holder Add user(s) to the group:
%p.light .panel-body.form-holder
Read more about project permissions %p.light
%strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink" Read more about project permissions
%strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
= form_tag members_update_admin_group_path(@group), id: "new_project_member", class: "bulk_import", method: :put do = form_tag members_update_admin_group_path(@group), id: "new_project_member", class: "bulk_import", method: :put do
%div %div
= users_select_tag(:user_ids, multiple: true, email_user: true, scope: :all) = users_select_tag(:user_ids, multiple: true, email_user: true, scope: :all)
%div.prepend-top-10 %div.prepend-top-10
= select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2" = select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
%hr %hr
= button_tag 'Add users to group', class: "btn btn-create" = button_tag 'Add users to group', class: "btn btn-create"
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
%h3.panel-title %h3.panel-title
...@@ -86,7 +87,8 @@ ...@@ -86,7 +87,8 @@
(invited) (invited)
%span.pull-right.light %span.pull-right.light
= member.human_access = member.human_access
= link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do - if can?(current_user, :destroy_group_member, member)
%i.fa.fa-minus.fa-inverse = link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
%i.fa.fa-minus.fa-inverse
.panel-footer .panel-footer
= paginate @members, param_name: 'members_page', theme: 'gitlab' = paginate @members, param_name: 'members_page', theme: 'gitlab'
...@@ -105,6 +105,16 @@ ...@@ -105,6 +105,16 @@
.col-md-6 .col-md-6
- unless @user == current_user - unless @user == current_user
- unless @user.confirmed?
.panel.panel-info
.panel-heading
Confirm user
.panel-body
- if @user.unconfirmed_email.present?
- email = " (#{@user.unconfirmed_email})"
%p This user has an unconfirmed email address#{email}. You may force a confirmation.
%br
= link_to 'Confirm user', confirm_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?' }
- if @user.blocked? - if @user.blocked?
.panel.panel-info .panel.panel-info
.panel-heading .panel-heading
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
= link_to member.created_by.name, user_path(member.created_by) = link_to member.created_by.name, user_path(member.created_by)
= time_ago_with_tooltip(member.created_at) = time_ago_with_tooltip(member.created_at)
- if show_controls && can?(current_user, :admin_group, @group) - if show_controls && can?(current_user, :admin_group_member, member)
= link_to resend_invite_group_group_member_path(@group, member), method: :post, class: "btn-xs btn", title: 'Resend invite' do = link_to resend_invite_group_group_member_path(@group, member), method: :post, class: "btn-xs btn", title: 'Resend invite' do
Resend invite Resend invite
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
= search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input' } = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input' }
= button_tag 'Search', class: 'btn' = button_tag 'Search', class: 'btn'
- if current_user && current_user.can?(:admin_group, @group) - if current_user && current_user.can?(:admin_group_member, @group)
.pull-right .pull-right
= button_tag class: 'btn btn-new js-toggle-button', type: 'button' do = button_tag class: 'btn btn-new js-toggle-button', type: 'button' do
Add members Add members
......
...@@ -19,9 +19,15 @@ ...@@ -19,9 +19,15 @@
Forked from Forked from
= forked_from_project.namespace.try(:name) = forked_from_project.namespace.try(:name)
- if can? current_user, :download_code, @project
= link_to "#", class: 'btn js-toggle-clone-holder' do
= icon('cloud-download fw')
Clone
- if can? current_user, :download_code, @project - if can? current_user, :download_code, @project
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn', rel: 'nofollow' do = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
%i.fa.fa-download = icon('download fw')
Download
= render 'projects/buttons/dropdown' = render 'projects/buttons/dropdown'
......
- if current_user - if current_user
%span.dropdown %span.dropdown
%a.dropdown-toggle.btn.btn-new{href: '#', "data-toggle" => "dropdown"} %a.dropdown-toggle.btn.btn-new{href: '#', "data-toggle" => "dropdown"}
%i.fa.fa-plus = icon('plus')
%ul.dropdown-menu %ul.dropdown-menu
- if @project.issues_enabled && can?(current_user, :create_issue, @project) - if can?(current_user, :create_issue, @project)
%li %li
= link_to url_for_new_issue, title: "New Issue" do = link_to url_for_new_issue do
= icon('exclamation-circle fw')
New issue New issue
- if @project.merge_requests_enabled && can?(current_user, :create_merge_request, @project) - if can?(current_user, :create_merge_request, @project)
%li %li
= link_to new_namespace_project_merge_request_path(@project.namespace, @project), title: "New Merge Request" do = link_to new_namespace_project_merge_request_path(@project.namespace, @project) do
= icon('tasks fw')
New merge request New merge request
- if @project.snippets_enabled && can?(current_user, :create_snippet, @project) - if can?(current_user, :create_snippet, @project)
%li %li
= link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do = link_to new_namespace_project_snippet_path(@project.namespace, @project) do
= icon('file-text-o fw')
New snippet New snippet
- if can?(current_user, :admin_project_member, @project) - if can?(current_user, :push_code, @project)
%li
= link_to namespace_project_project_members_path(@project.namespace, @project), title: "New project member" do
New project member
- if can? current_user, :push_code, @project
%li.divider %li.divider
%li %li
= link_to new_namespace_project_branch_path(@project.namespace, @project) do = link_to new_namespace_project_branch_path(@project.namespace, @project) do
New git branch = icon('code-fork fw')
New branch
%li %li
= link_to new_namespace_project_tag_path(@project.namespace, @project) do = link_to new_namespace_project_tag_path(@project.namespace, @project) do
New git tag = icon('tags fw')
New tag
- if current_user && can?(current_user, :fork_project, @project) - if current_user && can?(current_user, :fork_project, @project)
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 - 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' do = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn' do
= icon('code-fork') = icon('code-fork fw')
Fork Fork
%span.count %span.count
= @project.forks_count = @project.forks_count
- else - else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn' do = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn' do
= icon('code-fork') = icon('code-fork fw')
Fork Fork
%span.count %span.count
= @project.forks_count = @project.forks_count
- if current_user - if current_user
= link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star', method: :post, remote: true do = link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star', method: :post, remote: true do
= icon('star') = icon('star fw')
- if current_user.starred?(@project) - if current_user.starred?(@project)
Unstar Unstar
- else - else
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
- else - else
= link_to new_user_session_path, class: 'btn has_tooltip star-btn', title: 'You must sign in to star a project' do = link_to new_user_session_path, class: 'btn has_tooltip star-btn', title: 'You must sign in to star a project' do
= icon('star') = icon('star fw')
Star Star
%span.count %span.count
= @project.star_count = @project.star_count
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
- if @merge_request.compare_failed - if @merge_request.compare_failed
.alert.alert-danger .alert.alert-danger
%h4 Compare failed %h4 Compare failed
%p We can't compare selected branches. It may be because of huge diff or satellite timeout. Please try again or select different branches. %p We can't compare selected branches. It may be because of huge diff. Please try again or select different branches.
- else - else
.light-well .light-well
.center .center
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
= icon('history') = icon('history')
Commits Commits
%span.badge= @commits.size %span.badge= @commits.size
%li.diffs-tab %li.diffs-tab.active
= link_to url_for(params), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do = link_to url_for(params), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
= icon('list-alt') = icon('list-alt')
Changes Changes
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
.tab-content .tab-content
#commits.commits.tab-pane #commits.commits.tab-pane
= render "projects/commits/commits", project: @project = render "projects/commits/commits", project: @project
#diffs.diffs.tab-pane #diffs.diffs.tab-pane.active
- if @diffs.present? - if @diffs.present?
= render "projects/diffs/diffs", diffs: @diffs, project: @project = render "projects/diffs/diffs", diffs: @diffs, project: @project
- elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
......
...@@ -6,6 +6,12 @@ ...@@ -6,6 +6,12 @@
for #{@merge_request.last_commit_short_sha}. for #{@merge_request.last_commit_short_sha}.
= link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink" = link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
.ci_widget.ci-skipped{style: "display:none"}
= icon("check")
%span CI build skipped
for #{@merge_request.last_commit_short_sha}.
= link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
.ci_widget.ci-failed{style: "display:none"} .ci_widget.ci-failed{style: "display:none"}
= icon("times") = icon("times")
%span CI build failed %span CI build failed
......
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
.mr-widget-body .mr-widget-body
- if @project.archived? - if @project.archived?
= render 'projects/merge_requests/widget/open/archived' = render 'projects/merge_requests/widget/open/archived'
- elsif !@project.satellite.exists?
= render 'projects/merge_requests/widget/open/no_satellite'
- elsif @merge_request.commits.blank? - elsif @merge_request.commits.blank?
= render 'projects/merge_requests/widget/open/nothing' = render 'projects/merge_requests/widget/open/nothing'
- elsif @merge_request.branch_missing? - elsif @merge_request.branch_missing?
......
...@@ -11,10 +11,10 @@ ...@@ -11,10 +11,10 @@
var merge_request_widget; var merge_request_widget;
merge_request_widget = new MergeRequestWidget({ merge_request_widget = new MergeRequestWidget({
url_to_automerge_check: "#{automerge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", url_to_automerge_check: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
check_enable: #{@merge_request.unchecked? ? "true" : "false"}, check_enable: #{@merge_request.unchecked? ? "true" : "false"},
url_to_ci_check: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", url_to_ci_check: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
ci_enable: #{@project.ci_service ? "true" : "false"}, ci_enable: #{@project.ci_service ? "true" : "false"},
current_status: "#{@merge_request.automerge_status}", current_status: "#{@merge_request.gitlab_merge_status}",
}); });
= form_for [:automerge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-requires-input' } do |f| = form_for [:merge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-requires-input' } do |f|
= hidden_field_tag :authenticity_token, form_authenticity_token = hidden_field_tag :authenticity_token, form_authenticity_token
.accept-merge-holder.clearfix.js-toggle-container .accept-merge-holder.clearfix.js-toggle-container
.accept-action .accept-action
......
...@@ -6,9 +6,11 @@ ...@@ -6,9 +6,11 @@
%span.label.label-inverse= @merge_request.source_branch %span.label.label-inverse= @merge_request.source_branch
does not exist in does not exist in
%span.label.label-info= @merge_request.source_project_path %span.label.label-info= @merge_request.source_project_path
%br
%strong Please close this merge request and open a new merge request to change source branches.
- else - else
%span.label.label-inverse= @merge_request.target_branch %span.label.label-inverse= @merge_request.target_branch
does not exist in does not exist in
%span.label.label-info= @merge_request.target_project_path %span.label.label-info= @merge_request.target_project_path
%br %br
%strong Please close this merge request or change branches with existing one %strong Please close this merge request or change to another target branch.
%p
%span
%strong This repository does not have a satellite. Please ask an administrator to fix this issue!
...@@ -22,19 +22,34 @@ ...@@ -22,19 +22,34 @@
%li %li
= link_to namespace_project_tags_path(@project.namespace, @project) do = link_to namespace_project_tags_path(@project.namespace, @project) do
= pluralize(number_with_delimiter(@repository.tag_names.count), 'tag') = pluralize(number_with_delimiter(@repository.tag_names.count), 'tag')
- if @repository.changelog - if @repository.changelog
%li %li
= link_to changelog_url(@project) do = link_to changelog_path(@project) do
Changelog Changelog
- if @repository.license - if @repository.license
%li %li
= link_to license_url(@project) do = link_to license_path(@project) do
License License
- if @repository.contribution_guide - if @repository.contribution_guide
%li %li
= link_to contribution_guide_url(@project) do = link_to contribution_guide_path(@project) do
Contribution guide Contribution guide
- if current_user && can_push_branch?(@project, @project.default_branch)
- unless @repository.changelog
%li.missing
= link_to add_changelog_path(@project) do
Add Changelog
- unless @repository.license
%li.missing
= link_to add_license_path(@project) do
Add License
- unless @repository.contribution_guide
%li.missing
= link_to add_contribution_guide_path(@project) do
Add Contribution guide
- if @project.archived? - if @project.archived?
.text-warning.center.prepend-top-20 .text-warning.center.prepend-top-20
%p %p
......
%span.str-truncated %span.str-truncated
%span.tree_author= commit_author_link(commit, avatar: true, size: 16)
= link_to_gfm commit.title, namespace_project_commit_path(@project.namespace, @project, commit.id), class: "tree-commit-link" = link_to_gfm commit.title, namespace_project_commit_path(@project.namespace, @project, commit.id), class: "tree-commit-link"
%span.tree_author
[
commit_author_link(commit, avatar: false)
]
...@@ -100,7 +100,7 @@ ...@@ -100,7 +100,7 @@
= link_to 'Change branches', mr_change_branches_path(@merge_request) = link_to 'Change branches', mr_change_branches_path(@merge_request)
.form-actions .form-actions
- if !issuable.project.empty_repo? && (guide_url = contribution_guide_url(issuable.project)) && !issuable.persisted? - if !issuable.project.empty_repo? && (guide_url = contribution_guide_path(issuable.project)) && !issuable.persisted?
%p %p
Please review the Please review the
%strong #{link_to 'guidelines for contribution', guide_url} %strong #{link_to 'guidelines for contribution', guide_url}
......
class AutoMergeWorker class MergeWorker
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options queue: :default sidekiq_options queue: :default
...@@ -7,7 +7,13 @@ class AutoMergeWorker ...@@ -7,7 +7,13 @@ class AutoMergeWorker
params = params.with_indifferent_access params = params.with_indifferent_access
current_user = User.find(current_user_id) current_user = User.find(current_user_id)
merge_request = MergeRequest.find(merge_request_id) merge_request = MergeRequest.find(merge_request_id)
merge_request.should_remove_source_branch = params[:should_remove_source_branch]
merge_request.automerge!(current_user, params[:commit_message]) result = MergeRequests::MergeService.new(merge_request.target_project, current_user).
execute(merge_request, params[:commit_message])
if result[:status] == :success && params[:should_remove_source_branch].present?
DeleteBranchService.new(merge_request.source_project, current_user).
execute(merge_request.source_branch)
end
end end
end end
...@@ -27,7 +27,6 @@ class RepositoryImportWorker ...@@ -27,7 +27,6 @@ class RepositoryImportWorker
project.import_finish project.import_finish
project.save project.save
project.satellite.create unless project.satellite.exists?
ProjectCacheWorker.perform_async(project.id) ProjectCacheWorker.perform_async(project.id)
Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket' Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket'
end end
......
...@@ -256,6 +256,7 @@ production: &base ...@@ -256,6 +256,7 @@ production: &base
## Backup settings ## Backup settings
backup: backup:
path: "tmp/backups" # Relative paths are relative to Rails.root (default: tmp/backups/) path: "tmp/backups" # Relative paths are relative to Rails.root (default: tmp/backups/)
# archive_permissions: 0640 # Permissions for the resulting backup.tar file (default: 0600)
# keep_time: 604800 # default: 0 (forever) (in seconds) # keep_time: 604800 # default: 0 (forever) (in seconds)
# upload: # upload:
# # Fog storage connection settings, see http://fog.io/storage/ . # # Fog storage connection settings, see http://fog.io/storage/ .
...@@ -347,6 +348,8 @@ test: ...@@ -347,6 +348,8 @@ test:
# user: YOUR_USERNAME # user: YOUR_USERNAME
satellites: satellites:
path: tmp/tests/gitlab-satellites/ path: tmp/tests/gitlab-satellites/
backup:
path: tmp/tests/backups
gitlab_shell: gitlab_shell:
path: tmp/tests/gitlab-shell/ path: tmp/tests/gitlab-shell/
repos_path: tmp/tests/repositories/ repos_path: tmp/tests/repositories/
......
...@@ -170,6 +170,7 @@ Settings.gitlab_shell['ssh_path_prefix'] ||= Settings.send(:build_gitlab_shell_s ...@@ -170,6 +170,7 @@ Settings.gitlab_shell['ssh_path_prefix'] ||= Settings.send(:build_gitlab_shell_s
Settings['backup'] ||= Settingslogic.new({}) Settings['backup'] ||= Settingslogic.new({})
Settings.backup['keep_time'] ||= 0 Settings.backup['keep_time'] ||= 0
Settings.backup['path'] = File.expand_path(Settings.backup['path'] || "tmp/backups/", Rails.root) Settings.backup['path'] = File.expand_path(Settings.backup['path'] || "tmp/backups/", Rails.root)
Settings.backup['archive_permissions'] ||= 0600
Settings.backup['upload'] ||= Settingslogic.new({ 'remote_directory' => nil, 'connection' => nil }) Settings.backup['upload'] ||= Settingslogic.new({ 'remote_directory' => nil, 'connection' => nil })
# Convert upload connection settings to use symbol keys, to make Fog happy # Convert upload connection settings to use symbol keys, to make Fog happy
if Settings.backup['upload']['connection'] if Settings.backup['upload']['connection']
......
# Monkey-patch Redis::Store to make 'setex' and 'expire' work with namespacing
module Gitlab
class Redis
class Store
module Namespace
# Redis::Store#setex in redis-store 1.1.4 does not respect namespaces;
# this new method does.
def setex(key, expires_in, value, options=nil)
namespace(key) { |key| super(key, expires_in, value) }
end
# Redis::Store#expire in redis-store 1.1.4 does not respect namespaces;
# this new method does.
def expire(key, expires_in)
namespace(key) { |key| super(key, expires_in) }
end
private
# Our new definitions of #setex and #expire above assume that the
# #namespace method exists. Because we cannot be sure of that, we
# re-implement the #namespace method from Redis::Store::Namespace so
# that it is available for all Redis::Store instances, whether they use
# namespacing or not.
#
# Based on lib/redis/store/namespace.rb L49-51 (redis-store 1.1.4)
def namespace(key)
if @namespace
yield interpolate(key)
else
# This Redis::Store instance does not use a namespace so we should
# just pass through the key.
yield key
end
end
end
end
end
end
Redis::Store.class_eval do
include Gitlab::Redis::Store::Namespace
end
...@@ -159,6 +159,7 @@ Gitlab::Application.routes.draw do ...@@ -159,6 +159,7 @@ Gitlab::Application.routes.draw do
put :block put :block
put :unblock put :unblock
put :unlock put :unlock
put :confirm
patch :disable_two_factor patch :disable_two_factor
delete 'remove/:email_id', action: 'remove_email', as: 'remove_email' delete 'remove/:email_id', action: 'remove_email', as: 'remove_email'
end end
...@@ -458,8 +459,8 @@ Gitlab::Application.routes.draw do ...@@ -458,8 +459,8 @@ Gitlab::Application.routes.draw do
member do member do
get :diffs get :diffs
get :commits get :commits
post :automerge post :merge
get :automerge_check get :merge_check
get :ci_status get :ci_status
post :toggle_subscription post :toggle_subscription
end end
......
...@@ -397,6 +397,138 @@ Parameters: ...@@ -397,6 +397,138 @@ Parameters:
Will return `200 OK` on success, or `404 Not found` if either user or key cannot be found. Will return `200 OK` on success, or `404 Not found` if either user or key cannot be found.
## List emails
Get a list of currently authenticated user's emails.
```
GET /user/emails
```
```json
[
{
"id": 1,
"email": "email@example.com"
},
{
"id": 3,
"email": "email2@example.com"
}
]
```
Parameters:
- **none**
## List emails for user
Get a list of a specified user's emails. Available only for admin
```
GET /users/:uid/emails
```
Parameters:
- `uid` (required) - id of specified user
## Single email
Get a single email.
```
GET /user/emails/:id
```
Parameters:
- `id` (required) - email ID
```json
{
"id": 1,
"email": "email@example.com"
}
```
## Add email
Creates a new email owned by the currently authenticated user.
```
POST /user/emails
```
Parameters:
- `email` (required) - email address
```json
{
"id": 4,
"email": "email@example.com"
}
```
Will return created email with status `201 Created` on success. If an
error occurs a `400 Bad Request` is returned with a message explaining the error:
```json
{
"message": {
"email": [
"has already been taken"
]
}
}
```
## Add email for user
Create new email owned by specified user. Available only for admin
```
POST /users/:id/emails
```
Parameters:
- `id` (required) - id of specified user
- `email` (required) - email address
Will return created email with status `201 Created` on success, or `404 Not found` on fail.
## Delete email for current user
Deletes email owned by currently authenticated user.
This is an idempotent function and calling it on a email that is already deleted
or not available results in `200 OK`.
```
DELETE /user/emails/:id
```
Parameters:
- `id` (required) - email ID
## Delete email for given user
Deletes email owned by a specified user. Available only for admin.
```
DELETE /users/:uid/emails/:id
```
Parameters:
- `uid` (required) - id of specified user
- `id` (required) - email ID
Will return `200 OK` on success, or `404 Not found` if either user or email cannot be found.
## Block user ## Block user
Blocks the specified user. Available only for admin. Blocks the specified user. Available only for admin.
......
...@@ -56,9 +56,9 @@ To serve repositories over SSH there's an add-on application called gitlab-shell ...@@ -56,9 +56,9 @@ To serve repositories over SSH there's an add-on application called gitlab-shell
A typical install of GitLab will be on GNU/Linux. It uses Nginx or Apache as a web front end to proxypass the Unicorn web server. By default, communication between Unicorn and the front end is via a Unix domain socket but forwarding requests via TCP is also supported. The web front end accesses `/home/git/gitlab/public` bypassing the Unicorn server to serve static pages, uploads (e.g. avatar images or attachments), and precompiled assets. GitLab serves web pages and a [GitLab API](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/api) using the Unicorn web server. It uses Sidekiq as a job queue which, in turn, uses redis as a non-persistent database backend for job information, meta data, and incoming jobs. A typical install of GitLab will be on GNU/Linux. It uses Nginx or Apache as a web front end to proxypass the Unicorn web server. By default, communication between Unicorn and the front end is via a Unix domain socket but forwarding requests via TCP is also supported. The web front end accesses `/home/git/gitlab/public` bypassing the Unicorn server to serve static pages, uploads (e.g. avatar images or attachments), and precompiled assets. GitLab serves web pages and a [GitLab API](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/api) using the Unicorn web server. It uses Sidekiq as a job queue which, in turn, uses redis as a non-persistent database backend for job information, meta data, and incoming jobs.
The GitLab web app uses MySQL or PostgreSQL for persistent database information (e.g. users, permissions, issues, other meta data). GitLab stores the bare git repositories it serves in `/home/git/repositories` by default. It also keeps default branch and hook information with the bare repository. `/home/git/gitlab-satellites` keeps checked out repositories when performing actions such as a merge request, editing files in the web interface, etc. The GitLab web app uses MySQL or PostgreSQL for persistent database information (e.g. users, permissions, issues, other meta data). GitLab stores the bare git repositories it serves in `/home/git/repositories` by default. It also keeps default branch and hook information with the bare repository.
The satellite repository is used by the web interface for editing repositories and the wiki which is also a git repository. When serving repositories over HTTP/HTTPS GitLab utilizes the GitLab API to resolve authorization and access as well as serving git objects. When serving repositories over HTTP/HTTPS GitLab utilizes the GitLab API to resolve authorization and access as well as serving git objects.
The add-on component gitlab-shell serves repositories over SSH. It manages the SSH keys within `/home/git/.ssh/authorized_keys` which should not be manually edited. gitlab-shell accesses the bare repositories directly to serve git objects and communicates with redis to submit jobs to Sidekiq for GitLab to process. gitlab-shell queries the GitLab API to determine authorization and access. The add-on component gitlab-shell serves repositories over SSH. It manages the SSH keys within `/home/git/.ssh/authorized_keys` which should not be manually edited. gitlab-shell accesses the bare repositories directly to serve git objects and communicates with redis to submit jobs to Sidekiq for GitLab to process. gitlab-shell queries the GitLab API to determine authorization and access.
...@@ -129,7 +129,7 @@ Note: `/home/git/` is shorthand for `/home/git`. ...@@ -129,7 +129,7 @@ Note: `/home/git/` is shorthand for `/home/git`.
gitlabhq (includes Unicorn and Sidekiq logs) gitlabhq (includes Unicorn and Sidekiq logs)
- `/home/git/gitlab/log/` contains `application.log`, `production.log`, `sidekiq.log`, `unicorn.stdout.log`, `githost.log`, `satellites.log`, and `unicorn.stderr.log` normally. - `/home/git/gitlab/log/` contains `application.log`, `production.log`, `sidekiq.log`, `unicorn.stdout.log`, `githost.log` and `unicorn.stderr.log` normally.
gitlab-shell gitlab-shell
......
...@@ -19,3 +19,5 @@ Step-by-step guides on the basics of working with Git and GitLab. ...@@ -19,3 +19,5 @@ Step-by-step guides on the basics of working with Git and GitLab.
* [Fork a project](fork-project.md) * [Fork a project](fork-project.md)
* [Add a file](add-file.md) * [Add a file](add-file.md)
* [Create a Merge Request](add-merge-request.md)
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
You can create a file in your [shell](command-line-commands.md) or in GitLab. You can create a file in your [shell](command-line-commands.md) or in GitLab.
To create a file in GitLab, sign in to [GitLab.com](https://gitlab.com). To create a file in GitLab, sign in to GitLab.
Select a project on the right side of your screen: Select a project on the right side of your screen:
......
# How to create a merge request
Merge Requests are useful to integrate separate changes that you've made to a project, on different branches.
To create a new Merge Request, sign in to GitLab.
Go to the project where you'd like to merge your changes:
![Select a project](basicsimages/select_project.png)
Click on "Merge Requests" on the left side of your screen:
![Merge requests](basicsimages/merge_requests.png)
Click on "+ new Merge Request" on the right side of the screen:
![New Merge Request](basicsimages/new_merge_request.png)
Select a source branch or branch:
![Select a branch](basicsimages/select_branch.png)
Click on the "compare branches" button:
![Compare branches](basicsimages/compare_branches.png)
Add a title and a description to your Merge Request:
![Add a title and description](basicsimages/title_description_mr.png)
Select a user to review your Merge Request and to accept or close it. You may also select milestones and labels (they are optional). Then click on the "submit new Merge Request" button:
![Add a new merge request](basicsimages/add_new_merge_request.png)
Your Merge Request will be ready to be approved and published.
### Note
After you created a new branch, you'll immediately find a "create a Merge Request" button at the top of your screen.
You may automatically create a Merge Request from your recently created branch when clicking on this button:
![Automatic MR button](basicsimages/button-create-mr.png)
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
## Start working on your project ## Start working on your project
In Git, when you copy a project you say you "clone" it. To work on a git project locally (from your own computer), you will need to clone it. To do this, sign in to [GitLab.com](https://gitlab.com). In Git, when you copy a project you say you "clone" it. To work on a git project locally (from your own computer), you will need to clone it. To do this, sign in to GitLab.
When you are on your Dashboard, click on the project that you'd like to clone, which you'll find at the right side of your screen. When you are on your Dashboard, click on the project that you'd like to clone, which you'll find at the right side of your screen.
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
## Create a group ## Create a group
Your projects in [GitLab.com](https://gitlab.com) can be organized in 2 different ways: Your projects in GitLab can be organized in 2 different ways:
under your own namespace for single projects, such as ´your-name/project-1'; or under groups. under your own namespace for single projects, such as ´your-name/project-1'; or under groups.
If you organize your projects under a group, it works like a folder. You can manage your group members' permissions and access to the projects. If you organize your projects under a group, it works like a folder. You can manage your group members' permissions and access to the projects.
......
# How to create a project in GitLab # How to create a project in GitLab
To create a new project, sign in to [GitLab.com](https://gitlab.com). To create a new project, sign in to GitLab.
Go to your Dashboard and click on "new project" on the right side of your screen. Go to your Dashboard and click on "new project" on the right side of your screen.
......
...@@ -6,7 +6,7 @@ You need to connect your computer to your GitLab account through SSH Keys. They ...@@ -6,7 +6,7 @@ You need to connect your computer to your GitLab account through SSH Keys. They
Create an account on GitLab. Sign up and check your email for your confirmation link. Create an account on GitLab. Sign up and check your email for your confirmation link.
After you confirm, go to [GitLab.com](https://about.gitlab.com/) and sign in to your account. After you confirm, go to GitLab and sign in to your account.
## Add your SSH Key ## Add your SSH Key
......
...@@ -6,7 +6,7 @@ publishing or not, without affecting your original project. ...@@ -6,7 +6,7 @@ publishing or not, without affecting your original project.
It takes just a few steps to fork a project in GitLab. It takes just a few steps to fork a project in GitLab.
Sign in to [gitlab.com](https://gitlab.com). Sign in to GitLab.
Select a project on the right side of your screen: Select a project on the right side of your screen:
......
# Start using Git on the command line # Start using Git on the command line
If you want to start using a Git and GitLab, make sure that you have created an account on [GitLab.com](https://about.gitlab.com/). If you want to start using a Git and GitLab, make sure that you have created an account on GitLab.
## Open a shell ## Open a shell
......
...@@ -195,9 +195,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ...@@ -195,9 +195,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
### Clone the Source ### Clone the Source
# Clone GitLab repository # Clone GitLab repository
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-12-stable gitlab sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-13-stable gitlab
**Note:** You can change `7-12-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! **Note:** You can change `7-13-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It ### Configure It
...@@ -216,10 +216,6 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ...@@ -216,10 +216,6 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
sudo chmod -R u+rwX,go-w log/ sudo chmod -R u+rwX,go-w log/
sudo chmod -R u+rwX tmp/ sudo chmod -R u+rwX tmp/
# Create directory for satellites
sudo -u git -H mkdir /home/git/gitlab-satellites
sudo chmod u+rwx,g=rx,o-rwx /home/git/gitlab-satellites
# Make sure GitLab can write to the tmp/pids/ and tmp/sockets/ directories # Make sure GitLab can write to the tmp/pids/ and tmp/sockets/ directories
sudo chmod -R u+rwX tmp/pids/ sudo chmod -R u+rwX tmp/pids/
sudo chmod -R u+rwX tmp/sockets/ sudo chmod -R u+rwX tmp/sockets/
......
...@@ -32,7 +32,7 @@ Please consider using a virtual machine to run GitLab. ...@@ -32,7 +32,7 @@ Please consider using a virtual machine to run GitLab.
## Ruby versions ## Ruby versions
GitLab requires Ruby (MRI) 2.0 or 2.1 GitLab requires Ruby (MRI) 2.1
You will have to use the standard MRI implementation of Ruby. You will have to use the standard MRI implementation of Ruby.
We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab needs several Gems that have native extensions. We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab needs several Gems that have native extensions.
...@@ -40,7 +40,7 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab ...@@ -40,7 +40,7 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab
### Storage ### Storage
The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least twice as much free space as all your repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo. The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least as much free space as all your repos combined take up.
If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them. If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them.
...@@ -113,4 +113,4 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o ...@@ -113,4 +113,4 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o
### Common UI problems with IE ### Common UI problems with IE
If you experience UI issues with Internet Explorer, please make sure that you have the `Compatibility View` mode disabled. If you experience UI issues with Internet Explorer, please make sure that you have the `Compatibility View` mode disabled.
\ No newline at end of file
...@@ -6,16 +6,14 @@ This is the directory structure you will end up with following the instructions ...@@ -6,16 +6,14 @@ This is the directory structure you will end up with following the instructions
| |-- git | |-- git
| |-- .ssh | |-- .ssh
| |-- gitlab | |-- gitlab
| |-- gitlab-satellites
| |-- gitlab-shell | |-- gitlab-shell
| |-- repositories | |-- repositories
* `/home/git/.ssh` - contains openssh settings. Specifically the `authorized_keys` file managed by gitlab-shell. * `/home/git/.ssh` - contains openssh settings. Specifically the `authorized_keys` file managed by gitlab-shell.
* `/home/git/gitlab` - GitLab core software. * `/home/git/gitlab` - GitLab core software.
* `/home/git/gitlab-satellites` - checked out repositories for merge requests and file editing from web UI. This can be treated as a temporary files directory.
* `/home/git/gitlab-shell` - Core add-on component of GitLab. Maintains SSH cloning and other functionality. * `/home/git/gitlab-shell` - Core add-on component of GitLab. Maintains SSH cloning and other functionality.
* `/home/git/repositories` - bare repositories for all projects organized by namespace. This is where the git repositories which are pushed/pulled are maintained for all projects. **This area is critical data for projects. [Keep a backup](../raketasks/backup_restore.md)** * `/home/git/repositories` - bare repositories for all projects organized by namespace. This is where the git repositories which are pushed/pulled are maintained for all projects. **This area is critical data for projects. [Keep a backup](../raketasks/backup_restore.md)**
*Note: the default locations for gitlab-satellites and repositories can be configured in `config/gitlab.yml` of GitLab and `config.yml` of gitlab-shell.* *Note: the default locations for repositories can be configured in `config/gitlab.yml` of GitLab and `config.yml` of gitlab-shell.*
To see a more in-depth overview see the [GitLab architecture doc](../development/architecture.md). To see a more in-depth overview see the [GitLab architecture doc](../development/architecture.md).
...@@ -51,16 +51,6 @@ December 03, 2014 13:20 -> ERROR -> Command failed [1]: /usr/bin/git --git-dir=/ ...@@ -51,16 +51,6 @@ December 03, 2014 13:20 -> ERROR -> Command failed [1]: /usr/bin/git --git-dir=/
error: failed to push some refs to '/Users/vsizov/gitlab-development-kit/repositories/gitlabhq/gitlab_git.git' error: failed to push some refs to '/Users/vsizov/gitlab-development-kit/repositories/gitlabhq/gitlab_git.git'
``` ```
#### satellites.log
This file lives in `/var/log/gitlab/gitlab-rails/satellites.log` for omnibus package or in `/home/git/gitlab/log/satellites.log` for installations from the source.
In some cases GitLab should perform write actions to git repository, for example when it is needed to merge the merge request or edit a file with online editor. If something went wrong you can look into this file to find out what exactly happened.
```
October 07, 2014 11:36: Failed to create satellite for Chesley Weimann III / project1817
October 07, 2014 11:36: PID: 1872: git clone /Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/repositories/conrad6841/gitlabhq.git /Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/conrad6841/gitlabhq
October 07, 2014 11:36: PID: 1872: -> fatal: repository '/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/repositories/conrad6841/gitlabhq.git' does not exist
```
#### sidekiq.log #### sidekiq.log
This file lives in `/var/log/gitlab/gitlab-rails/sidekiq.log` for omnibus package or in `/home/git/gitlab/log/sidekiq.log` for installations from the source. This file lives in `/var/log/gitlab/gitlab-rails/sidekiq.log` for omnibus package or in `/home/git/gitlab/log/sidekiq.log` for installations from the source.
...@@ -99,4 +89,4 @@ W, [2015-02-13T07:16:01.313000 #9094] WARN -- : Unicorn::WorkerKiller send SIGQ ...@@ -99,4 +89,4 @@ W, [2015-02-13T07:16:01.313000 #9094] WARN -- : Unicorn::WorkerKiller send SIGQ
I, [2015-02-13T07:16:01.530733 #9047] INFO -- : reaped #<Process::Status: pid 9094 exit 0> worker=1 I, [2015-02-13T07:16:01.530733 #9047] INFO -- : reaped #<Process::Status: pid 9094 exit 0> worker=1
I, [2015-02-13T07:16:01.534501 #13379] INFO -- : worker=1 spawned pid=13379 I, [2015-02-13T07:16:01.534501 #13379] INFO -- : worker=1 spawned pid=13379
I, [2015-02-13T07:16:01.534848 #13379] INFO -- : worker=1 ready I, [2015-02-13T07:16:01.534848 #13379] INFO -- : worker=1 ready
``` ```
\ No newline at end of file
...@@ -6,6 +6,9 @@ If a user is both in a project group and in the project itself, the highest perm ...@@ -6,6 +6,9 @@ If a user is both in a project group and in the project itself, the highest perm
If a user is a GitLab administrator they receive all permissions. If a user is a GitLab administrator they receive all permissions.
To add or import a user, you can follow the [project users and members
documentation](doc/workflow/add-user/add-user.md).
## Project ## Project
| Action | Guest | Reporter | Developer | Master | Owner | | Action | Guest | Reporter | Developer | Master | Owner |
......
...@@ -7,3 +7,4 @@ ...@@ -7,3 +7,4 @@
- [User management](user_management.md) - [User management](user_management.md)
- [Web hooks](web_hooks.md) - [Web hooks](web_hooks.md)
- [Import](import.md) of git repositories in bulk - [Import](import.md) of git repositories in bulk
- [Rebuild authorized_keys file](http://doc.gitlab.com/ce/raketasks/maintenance.html#rebuild-authorized_keys-file) task for administrators
\ No newline at end of file
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
A backup creates an archive file that contains the database, all repositories and all attachments. A backup creates an archive file that contains the database, all repositories and all attachments.
This archive will be saved in backup_path (see `config/gitlab.yml`). This archive will be saved in backup_path (see `config/gitlab.yml`).
The filename will be `[TIMESTAMP]_gitlab_backup.tar`. This timestamp can be used to restore an specific backup. The filename will be `[TIMESTAMP]_gitlab_backup.tar`. This timestamp can be used to restore an specific backup.
You can only restore a backup to exactly the same version of GitLab that you created it on, for example 7.2.1. You can only restore a backup to exactly the same version of GitLab that you created it on, for example 7.2.1. The best way to migrate your repositories from one server to another is through backup restore.
You need to keep a separate copy of `/etc/gitlab/gitlab-secrets.json` You need to keep a separate copy of `/etc/gitlab/gitlab-secrets.json`
(for omnibus packages) or `/home/git/gitlab/.secret` (for installations (for omnibus packages) or `/home/git/gitlab/.secret` (for installations
...@@ -148,6 +148,23 @@ with the name of your bucket: ...@@ -148,6 +148,23 @@ with the name of your bucket:
} }
``` ```
## Backup archive permissions
The backup archives created by GitLab (123456_gitlab_backup.tar) will have owner/group git:git and 0600 permissions by default.
This is meant to avoid other system users reading GitLab's data.
If you need the backup archives to have different permissions you can use the 'archive_permissions' setting.
```
# In /etc/gitlab/gitlab.rb, for omnibus packages
gitlab_rails['backup_archive_permissions'] = 0644 # Makes the backup archives world-readable
```
```
# In gitlab.yml, for installations from source:
backup:
archive_permissions: 0644 # Makes the backup archives world-readable
```
## Storing configuration files ## Storing configuration files
Please be informed that a backup does not store your configuration Please be informed that a backup does not store your configuration
...@@ -232,7 +249,7 @@ Deleting tmp directories...[DONE] ...@@ -232,7 +249,7 @@ Deleting tmp directories...[DONE]
We will assume that you have installed GitLab from an omnibus package and run We will assume that you have installed GitLab from an omnibus package and run
`sudo gitlab-ctl reconfigure` at least once. `sudo gitlab-ctl reconfigure` at least once.
First make sure your backup tar file is in `/var/opt/gitlab/backups`. First make sure your backup tar file is in `/var/opt/gitlab/backups` (or wherever `gitlab_rails['backup_path']` points to).
```shell ```shell
sudo cp 1393513186_gitlab_backup.tar /var/opt/gitlab/backups/ sudo cp 1393513186_gitlab_backup.tar /var/opt/gitlab/backups/
......
...@@ -105,24 +105,11 @@ Log directory writable? ... yes ...@@ -105,24 +105,11 @@ Log directory writable? ... yes
Tmp directory writable? ... yes Tmp directory writable? ... yes
Init script exists? ... yes Init script exists? ... yes
Init script up-to-date? ... yes Init script up-to-date? ... yes
Projects have satellites? ... yes
Redis version >= 2.0.0? ... yes Redis version >= 2.0.0? ... yes
Checking GitLab ... Finished Checking GitLab ... Finished
``` ```
## (Re-)Create satellite repositories
This will create satellite repositories for all your projects.
If necessary, remove the `repo_satellites` directory and rerun the commands below.
```
sudo -u git -H mkdir -p /home/git/gitlab-satellites
sudo -u git -H bundle exec rake gitlab:satellites:create RAILS_ENV=production
sudo chmod u+rwx,g=rx,o-rwx /home/git/gitlab-satellites
```
## Rebuild authorized_keys file ## Rebuild authorized_keys file
In some case it is necessary to rebuild the `authorized_keys` file. In some case it is necessary to rebuild the `authorized_keys` file.
......
...@@ -81,6 +81,7 @@ workday to quickly fix any issues. ...@@ -81,6 +81,7 @@ workday to quickly fix any issues.
- [ ] Merge CE stable into EE stable (#LINK) - [ ] Merge CE stable into EE stable (#LINK)
- [ ] Create the 'x.y.0' tag with the [release tools](https://dev.gitlab.org/gitlab/release-tools) (#LINK) - [ ] Create the 'x.y.0' tag with the [release tools](https://dev.gitlab.org/gitlab/release-tools) (#LINK)
- [ ] Create the 'x.y.0' version on version.gitlab.com
- [ ] Try to do before 11AM CET: Create and push omnibus tags for x.y.0 (will auto-release the packages) (#LINK) - [ ] Try to do before 11AM CET: Create and push omnibus tags for x.y.0 (will auto-release the packages) (#LINK)
- [ ] Try to do before 12AM CET: Publish the release blog post (#LINK) - [ ] Try to do before 12AM CET: Publish the release blog post (#LINK)
- [ ] Tweet about the release (blog post) (#LINK) - [ ] Tweet about the release (blog post) (#LINK)
......
...@@ -52,5 +52,6 @@ bundle exec rake release["x.x.x"] ...@@ -52,5 +52,6 @@ bundle exec rake release["x.x.x"]
1. Create and publish a blog post, see [patch release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/patch_release_blog_template.md) 1. Create and publish a blog post, see [patch release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/patch_release_blog_template.md)
1. Send tweets about the release from `@gitlab`, tweet should include the most important feature that the release is addressing and link to the blog post 1. Send tweets about the release from `@gitlab`, tweet should include the most important feature that the release is addressing and link to the blog post
1. Note in the 'GitLab X.X regressions' issue that the patch was published (CE only) 1. Note in the 'GitLab X.X regressions' issue that the patch was published (CE only)
1. Create the 'x.y.0' version on version.gitlab.com
1. [Create new AMIs](https://dev.gitlab.org/gitlab/AMI/blob/master/README.md) 1. [Create new AMIs](https://dev.gitlab.org/gitlab/AMI/blob/master/README.md)
1. Create a new patch release issue for the next potential release 1. Create a new patch release issue for the next potential release
\ No newline at end of file
# Migrating GitLab from MySQL to Postgres # Migrating GitLab from MySQL to Postgres
*Make sure you view this [guide from the `master` branch](../../../master/doc/update/mysql_to_postgresql.md) for the most up to date instructions.* *Make sure you view this [guide from the `master` branch](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/mysql_to_postgresql.md#migrating-gitlab-from-mysql-to-postgres) for the most up to date instructions.*
If you are replacing MySQL with Postgres while keeping GitLab on the same server all you need to do is to export from MySQL, convert the resulting SQL file, and import it into Postgres. If you are also moving GitLab to another server, or if you are switching to omnibus-gitlab, you may want to use a GitLab backup file. The second part of this documents explains the procedure to do this. If you are replacing MySQL with Postgres while keeping GitLab on the same server all you need to do is to export from MySQL, convert the resulting SQL file, and import it into Postgres. If you are also moving GitLab to another server, or if you are switching to omnibus-gitlab, you may want to use a GitLab backup file. The second part of this documents explains the procedure to do this.
...@@ -70,5 +70,5 @@ sudo -u git -H gzip db/database.sql ...@@ -70,5 +70,5 @@ sudo -u git -H gzip db/database.sql
sudo -u git -H tar rf TIMESTAMP_gitlab_backup.tar db/database.sql.gz sudo -u git -H tar rf TIMESTAMP_gitlab_backup.tar db/database.sql.gz
# Done! TIMESTAMP_gitlab_backup.tar can now be restored into a Postgres GitLab # Done! TIMESTAMP_gitlab_backup.tar can now be restored into a Postgres GitLab
# installation. Remember to recreate the indexes after the import. # installation.
``` ```
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
- [Notification emails](notifications.md) - [Notification emails](notifications.md)
- [Project Features](project_features.md) - [Project Features](project_features.md)
- [Project forking workflow](forking_workflow.md) - [Project forking workflow](forking_workflow.md)
- [Project users](add-user/add-user.md)
- [Protected branches](protected_branches.md) - [Protected branches](protected_branches.md)
- [Web Editor](web_editor.md) - [Web Editor](web_editor.md)
- ["Work In Progress" Merge Requests](wip_merge_requests.md) - ["Work In Progress" Merge Requests](wip_merge_requests.md)
# Project users
You can manage the groups and users and their access levels in all of your projects. You can also personalize the access level you give each user, per project.
Here's how to add or import users to your projects.
You should have 'master' or 'owner' permissions to add or import a new user
to your project.
To add or import a user, go to your project and click on "Members" on the left side of your screen:
![Members](images/members.png)
Select "Add members" or "Import members" on the right side of your screen:
![Add or Import](images/add-members.png)
If you are adding a user, select the user and the [permission level](doc/permissions/permissions.md) that you'd like to
give the user:
![Add or Import](images/new-member.png)
If you are importing a user, follow the steps to select the project where you'd like to import the user from:
![Add or Import](images/select-project.png)
...@@ -16,6 +16,6 @@ class Spinach::Features::DashboardHelp < Spinach::FeatureSteps ...@@ -16,6 +16,6 @@ class Spinach::Features::DashboardHelp < Spinach::FeatureSteps
end end
step 'Header "Rebuild project satellites" should have correct ids and links' do step 'Header "Rebuild project satellites" should have correct ids and links' do
header_should_have_correct_id_and_link(2, '(Re-)Create satellite repositories', 're-create-satellite-repositories', '.documentation') header_should_have_correct_id_and_link(2, 'Check GitLab configuration', 'check-gitlab-configuration', '.documentation')
end end
end end
...@@ -66,7 +66,7 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps ...@@ -66,7 +66,7 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
def authored_merge_request def authored_merge_request
@authored_merge_request ||= create :merge_request, @authored_merge_request ||= create :merge_request,
source_branch: 'simple_merge_request', source_branch: 'markdown',
author: current_user, author: current_user,
target_project: project, target_project: project,
source_project: project source_project: project
...@@ -74,14 +74,14 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps ...@@ -74,14 +74,14 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
def other_merge_request def other_merge_request
@other_merge_request ||= create :merge_request, @other_merge_request ||= create :merge_request,
source_branch: '2_3_notes_fix', source_branch: 'fix',
target_project: project, target_project: project,
source_project: project source_project: project
end end
def authored_merge_request_from_fork def authored_merge_request_from_fork
@authored_merge_request_from_fork ||= create :merge_request, @authored_merge_request_from_fork ||= create :merge_request,
source_branch: 'basic_page', source_branch: 'feature_conflict',
author: current_user, author: current_user,
target_project: public_project, target_project: public_project,
source_project: forked_project source_project: forked_project
...@@ -89,7 +89,7 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps ...@@ -89,7 +89,7 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
def assigned_merge_request_from_fork def assigned_merge_request_from_fork
@assigned_merge_request_from_fork ||= create :merge_request, @assigned_merge_request_from_fork ||= create :merge_request,
source_branch: 'basic_page_fix', source_branch: 'markdown',
assignee: current_user, assignee: current_user,
target_project: public_project, target_project: public_project,
source_project: forked_project source_project: forked_project
......
...@@ -9,7 +9,6 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps ...@@ -9,7 +9,6 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
@project = Project.find_by(name: "Shop") @project = Project.find_by(name: "Shop")
@project ||= create(:project, name: "Shop") @project ||= create(:project, name: "Shop")
@project.team << [@user, :reporter] @project.team << [@user, :reporter]
@project.ensure_satellite_exists
end end
step 'I have a project forked off of "Shop" called "Forked Shop"' do step 'I have a project forked off of "Shop" called "Forked Shop"' do
......
...@@ -198,15 +198,10 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -198,15 +198,10 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end end
step 'merge request "Bug NS-05" is mergeable' do step 'merge request "Bug NS-05" is mergeable' do
merge_request.project.satellite.create
merge_request.mark_as_mergeable merge_request.mark_as_mergeable
end end
step 'I accept this merge request' do step 'I accept this merge request' do
Gitlab::Satellite::MergeAction.any_instance.stub(
merge!: true,
)
page.within '.mr-state-widget' do page.within '.mr-state-widget' do
click_button "Accept Merge Request" click_button "Accept Merge Request"
end end
......
...@@ -6,6 +6,10 @@ module API ...@@ -6,6 +6,10 @@ module API
class UserBasic < UserSafe class UserBasic < UserSafe
expose :id, :state, :avatar_url expose :id, :state, :avatar_url
expose :web_url do |user, options|
Rails.application.routes.url_helpers.user_url(user)
end
end end
class User < UserBasic class User < UserBasic
...@@ -31,6 +35,10 @@ module API ...@@ -31,6 +35,10 @@ module API
expose :private_token expose :private_token
end end
class Email < Grape::Entity
expose :id, :email
end
class Hook < Grape::Entity class Hook < Grape::Entity
expose :id, :url, :created_at expose :id, :url, :created_at
end end
...@@ -59,6 +67,7 @@ module API ...@@ -59,6 +67,7 @@ module API
expose :namespace expose :namespace
expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ | project, options | project.forked? } expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ | project, options | project.forked? }
expose :avatar_url expose :avatar_url
expose :star_count, :forks_count
end end
class ProjectMember < UserBasic class ProjectMember < UserBasic
...@@ -69,6 +78,11 @@ module API ...@@ -69,6 +78,11 @@ module API
class Group < Grape::Entity class Group < Grape::Entity
expose :id, :name, :path, :description expose :id, :name, :path, :description
expose :avatar_url
expose :web_url do |group, options|
Rails.application.routes.url_helpers.group_url(group)
end
end end
class GroupDetail < Group class GroupDetail < Group
......
...@@ -198,7 +198,11 @@ module API ...@@ -198,7 +198,11 @@ module API
if merge_request.open? && !merge_request.work_in_progress? if merge_request.open? && !merge_request.work_in_progress?
if merge_request.can_be_merged? if merge_request.can_be_merged?
merge_request.automerge!(current_user, params[:merge_commit_message] || merge_request.merge_commit_message) commit_message = params[:merge_commit_message] || merge_request.merge_commit_message
::MergeRequests::MergeService.new(merge_request.target_project, current_user).
execute(merge_request, commit_message)
present merge_request, with: Entities::MergeRequest present merge_request, with: Entities::MergeRequest
else else
render_api_error!('Branch cannot be merged', 405) render_api_error!('Branch cannot be merged', 405)
......
...@@ -131,11 +131,11 @@ module API ...@@ -131,11 +131,11 @@ module API
# Add ssh key to a specified user. Only available to admin users. # Add ssh key to a specified user. Only available to admin users.
# #
# Parameters: # Parameters:
# id (required) - The ID of a user # id (required) - The ID of a user
# key (required) - New SSH Key # key (required) - New SSH Key
# title (required) - New SSH Key's title # title (required) - New SSH Key's title
# Example Request: # Example Request:
# POST /users/:id/keys # POST /users/:id/keys
post ":id/keys" do post ":id/keys" do
authenticated_as_admin! authenticated_as_admin!
required_attributes! [:title, :key] required_attributes! [:title, :key]
...@@ -153,9 +153,9 @@ module API ...@@ -153,9 +153,9 @@ module API
# Get ssh keys of a specified user. Only available to admin users. # Get ssh keys of a specified user. Only available to admin users.
# #
# Parameters: # Parameters:
# uid (required) - The ID of a user # uid (required) - The ID of a user
# Example Request: # Example Request:
# GET /users/:uid/keys # GET /users/:uid/keys
get ':uid/keys' do get ':uid/keys' do
authenticated_as_admin! authenticated_as_admin!
user = User.find_by(id: params[:uid]) user = User.find_by(id: params[:uid])
...@@ -185,6 +185,65 @@ module API ...@@ -185,6 +185,65 @@ module API
end end
end end
# Add email to a specified user. Only available to admin users.
#
# Parameters:
# id (required) - The ID of a user
# email (required) - Email address
# Example Request:
# POST /users/:id/emails
post ":id/emails" do
authenticated_as_admin!
required_attributes! [:email]
user = User.find(params[:id])
attrs = attributes_for_keys [:email]
email = user.emails.new attrs
if email.save
NotificationService.new.new_email(email)
present email, with: Entities::Email
else
render_validation_error!(email)
end
end
# Get emails of a specified user. Only available to admin users.
#
# Parameters:
# uid (required) - The ID of a user
# Example Request:
# GET /users/:uid/emails
get ':uid/emails' do
authenticated_as_admin!
user = User.find_by(id: params[:uid])
not_found!('User') unless user
present user.emails, with: Entities::Email
end
# Delete existing email of a specified user. Only available to admin
# users.
#
# Parameters:
# uid (required) - The ID of a user
# id (required) - Email ID
# Example Request:
# DELETE /users/:uid/emails/:id
delete ':uid/emails/:id' do
authenticated_as_admin!
user = User.find_by(id: params[:uid])
not_found!('User') unless user
begin
email = user.emails.find params[:id]
email.destroy
user.update_secondary_emails!
rescue ActiveRecord::RecordNotFound
not_found!('Email')
end
end
# Delete user. Available only for admin # Delete user. Available only for admin
# #
# Example Request: # Example Request:
...@@ -289,6 +348,58 @@ module API ...@@ -289,6 +348,58 @@ module API
rescue rescue
end end
end end
# Get currently authenticated user's emails
#
# Example Request:
# GET /user/emails
get "emails" do
present current_user.emails, with: Entities::Email
end
# Get single email owned by currently authenticated user
#
# Example Request:
# GET /user/emails/:id
get "emails/:id" do
email = current_user.emails.find params[:id]
present email, with: Entities::Email
end
# Add new email to currently authenticated user
#
# Parameters:
# email (required) - Email address
# Example Request:
# POST /user/emails
post "emails" do
required_attributes! [:email]
attrs = attributes_for_keys [:email]
email = current_user.emails.new attrs
if email.save
NotificationService.new.new_email(email)
present email, with: Entities::Email
else
render_validation_error!(email)
end
end
# Delete existing email of currently authenticated user
#
# Parameters:
# id (required) - EMail ID
# Example Request:
# DELETE /user/emails/:id
delete "emails/:id" do
begin
email = current_user.emails.find params[:id]
email.destroy
current_user.update_secondary_emails!
rescue
end
end
end end
end end
end end
...@@ -7,7 +7,11 @@ module Backup ...@@ -7,7 +7,11 @@ module Backup
def initialize def initialize
@config = YAML.load_file(File.join(Rails.root,'config','database.yml'))[Rails.env] @config = YAML.load_file(File.join(Rails.root,'config','database.yml'))[Rails.env]
@db_dir = File.join(Gitlab.config.backup.path, 'db') @db_dir = File.join(Gitlab.config.backup.path, 'db')
FileUtils.mkdir_p(@db_dir) unless Dir.exists?(@db_dir) FileUtils.rm_rf(@db_dir)
# Ensure the parent dir of @db_dir exists
FileUtils.mkdir_p(Gitlab.config.backup.path)
# Fail if somebody raced to create @db_dir before us
FileUtils.mkdir(@db_dir, mode: 0700)
end end
def dump def dump
...@@ -25,7 +29,6 @@ module Backup ...@@ -25,7 +29,6 @@ module Backup
abort 'Backup failed' unless success abort 'Backup failed' unless success
$progress.print 'Compressing database ... ' $progress.print 'Compressing database ... '
FileUtils.rm_f db_file_name_gz
success = system('gzip', db_file_name) success = system('gzip', db_file_name)
report_success(success) report_success(success)
abort 'Backup failed: compress error' unless success abort 'Backup failed: compress error' unless success
......
...@@ -16,18 +16,16 @@ module Backup ...@@ -16,18 +16,16 @@ module Backup
file << s.to_yaml.gsub(/^---\n/,'') file << s.to_yaml.gsub(/^---\n/,'')
end end
FileUtils.chmod(0700, folders_to_backup)
# create archive # create archive
$progress.print "Creating backup archive: #{tar_file} ... " $progress.print "Creating backup archive: #{tar_file} ... "
orig_umask = File.umask(0077) # Set file permissions on open to prevent chmod races.
if Kernel.system('tar', '-cf', tar_file, *backup_contents) tar_system_options = {out: [tar_file, 'w', Gitlab.config.backup.archive_permissions]}
if Kernel.system('tar', '-cf', '-', *backup_contents, tar_system_options)
$progress.puts "done".green $progress.puts "done".green
else else
puts "creating archive #{tar_file} failed".red puts "creating archive #{tar_file} failed".red
abort 'Backup failed' abort 'Backup failed'
end end
File.umask(orig_umask)
upload(tar_file) upload(tar_file)
end end
......
...@@ -130,7 +130,10 @@ module Backup ...@@ -130,7 +130,10 @@ module Backup
def prepare def prepare
FileUtils.rm_rf(backup_repos_path) FileUtils.rm_rf(backup_repos_path)
FileUtils.mkdir_p(backup_repos_path) # Ensure the parent dir of backup_repos_path exists
FileUtils.mkdir_p(Gitlab.config.backup.path)
# Fail if somebody raced to create backup_repos_path before us
FileUtils.mkdir(backup_repos_path, mode: 0700)
end end
def silent def silent
......
...@@ -10,7 +10,11 @@ module Backup ...@@ -10,7 +10,11 @@ module Backup
# Copy uploads from public/uploads to backup/uploads # Copy uploads from public/uploads to backup/uploads
def dump def dump
FileUtils.mkdir_p(backup_uploads_dir) FileUtils.rm_rf(backup_uploads_dir)
# Ensure the parent dir of backup_uploads_dir exists
FileUtils.mkdir_p(Gitlab.config.backup.path)
# Fail if somebody raced to create backup_uploads_dir before us
FileUtils.mkdir(backup_uploads_dir, mode: 0700)
FileUtils.cp_r(app_uploads_dir, backup_dir) FileUtils.cp_r(app_uploads_dir, backup_dir)
end end
......
...@@ -94,7 +94,7 @@ module ExtractsPath ...@@ -94,7 +94,7 @@ module ExtractsPath
@options = params.select {|key, value| allowed_options.include?(key) && !value.blank? } @options = params.select {|key, value| allowed_options.include?(key) && !value.blank? }
@options = HashWithIndifferentAccess.new(@options) @options = HashWithIndifferentAccess.new(@options)
@id = get_id @id = Addressable::URI.unescape(get_id)
@ref, @path = extract_ref(@id) @ref, @path = extract_ref(@id)
@repo = @project.repository @repo = @project.repository
if @options[:extended_sha1].blank? if @options[:extended_sha1].blank?
......
require 'gitlab/git' require 'gitlab/git'
module Gitlab module Gitlab
autoload :Satellite, 'gitlab/satellite/satellite'
end end
...@@ -217,20 +217,6 @@ module Gitlab ...@@ -217,20 +217,6 @@ module Gitlab
FileUtils.mv(full_path(old_name), full_path(new_name)) FileUtils.mv(full_path(old_name), full_path(new_name))
end end
# Remove GitLab Satellites for provided path (namespace or repo dir)
#
# Ex.
# rm_satellites("gitlab")
#
# rm_satellites("gitlab/gitlab-ci.git")
#
def rm_satellites(path)
raise ArgumentError.new("Path can't be blank") if path.blank?
satellites_path = File.join(Gitlab.config.satellites.path, path)
FileUtils.rm_r(satellites_path, force: true)
end
def url_to_repo(path) def url_to_repo(path)
Gitlab.config.gitlab_shell.ssh_path_prefix + "#{path}.git" Gitlab.config.gitlab_shell.ssh_path_prefix + "#{path}.git"
end end
......
...@@ -98,15 +98,25 @@ module Gitlab ...@@ -98,15 +98,25 @@ module Gitlab
# #
# Returns a String # Returns a String
def path_type(path) def path_type(path)
if repository.tree(current_sha, path).entries.any? unescaped_path = Addressable::URI.unescape(path)
if tree?(unescaped_path)
'tree' 'tree'
elsif repository.blob_at(current_sha, path).try(:image?) elsif image?(unescaped_path)
'raw' 'raw'
else else
'blob' 'blob'
end end
end end
def tree?(path)
repository.tree(current_sha, path).entries.any?
end
def image?(path)
repository.blob_at(current_sha, path).try(:image?)
end
def current_sha def current_sha
context[:commit].try(:id) || context[:commit].try(:id) ||
ref ? repository.commit(ref).try(:sha) : repository.head_commit.sha ref ? repository.commit(ref).try(:sha) : repository.head_commit.sha
......
module Gitlab
module Satellite
class Action
DEFAULT_OPTIONS = { git_timeout: Gitlab.config.satellites.timeout.seconds }
attr_accessor :options, :project, :user
def initialize(user, project, options = {})
@options = DEFAULT_OPTIONS.merge(options)
@project = project
@user = user
end
protected
# * Sets a 30s timeout for Git
# * Locks the satellite repo
# * Yields the prepared satellite repo
def in_locked_and_timed_satellite
Gitlab::ShellEnv.set_env(user)
Grit::Git.with_timeout(options[:git_timeout]) do
project.satellite.lock do
return yield project.satellite.repo
end
end
rescue Errno::ENOMEM => ex
return handle_exception(ex)
rescue Grit::Git::GitTimeout => ex
return handle_exception(ex)
ensure
Gitlab::ShellEnv.reset_env
end
# * Recreates the satellite
# * Sets up Git variables for the user
#
# Note: use this within #in_locked_and_timed_satellite
def prepare_satellite!(repo)
project.satellite.clear_and_update!
if user
repo.config['user.name'] = user.name
repo.config['user.email'] = user.email
end
end
def default_options(options = {})
{ raise: true, timeout: true }.merge(options)
end
def handle_exception(exception)
Gitlab::GitLogger.error(exception.message)
false
end
end
end
end
module Gitlab
module Satellite
class BranchesWithoutParent < StandardError; end
class CompareAction < Action
def initialize(user, target_project, target_branch, source_project, source_branch)
super user, target_project
@target_project, @target_branch = target_project, target_branch
@source_project, @source_branch = source_project, source_branch
end
# Compare 2 repositories and return Gitlab::CompareResult object
def result
in_locked_and_timed_satellite do |target_repo|
prepare_satellite!(target_repo)
update_satellite_source_and_target!(target_repo)
Gitlab::CompareResult.new(compare(target_repo))
end
rescue Grit::Git::CommandFailed => ex
raise BranchesWithoutParent
end
private
# Assumes a satellite exists that is a fresh clone of the projects repo, prepares satellite for diffs
def update_satellite_source_and_target!(target_repo)
target_repo.remote_add('source', @source_project.repository.path_to_repo)
target_repo.remote_fetch('source')
rescue Grit::Git::CommandFailed => ex
handle_exception(ex)
end
def compare(repo)
@compare ||= Gitlab::Git::Compare.new(
Gitlab::Git::Repository.new(repo.path),
"origin/#{@target_branch}",
"source/#{@source_branch}"
)
end
end
end
end
module Gitlab
module Satellite
class Logger < Gitlab::Logger
def self.file_name
'satellites.log'
end
def format_message(severity, timestamp, progname, msg)
"#{timestamp.to_s(:long)}: #{msg}\n"
end
end
end
end
module Gitlab
module Satellite
# GitLab server-side merge
class MergeAction < Action
attr_accessor :merge_request
def initialize(user, merge_request)
super user, merge_request.target_project
@merge_request = merge_request
end
# Checks if a merge request can be executed without user interaction
def can_be_merged?
in_locked_and_timed_satellite do |merge_repo|
prepare_satellite!(merge_repo)
merge_in_satellite!(merge_repo)
end
end
# Merges the source branch into the target branch in the satellite and
# pushes it back to the repository.
# It also removes the source branch if requested in the merge request (and this is permitted by the merge request).
#
# Returns false if the merge produced conflicts
# Returns false if pushing from the satellite to the repository failed or was rejected
# Returns true otherwise
def merge!(merge_commit_message = nil)
in_locked_and_timed_satellite do |merge_repo|
prepare_satellite!(merge_repo)
if merge_in_satellite!(merge_repo, merge_commit_message)
# push merge back to bare repo
# will raise CommandFailed when push fails
merge_repo.git.push(default_options, :origin, merge_request.target_branch)
# remove source branch
if merge_request.remove_source_branch?
# will raise CommandFailed when push fails
merge_repo.git.push(default_options, :origin, ":#{merge_request.source_branch}")
end
# merge, push and branch removal successful
true
end
end
rescue Grit::Git::CommandFailed => ex
handle_exception(ex)
end
def diff_in_satellite
in_locked_and_timed_satellite do |merge_repo|
prepare_satellite!(merge_repo)
update_satellite_source_and_target!(merge_repo)
# Only show what is new in the source branch compared to the target branch, not the other way around.
# The line below with merge_base is equivalent to diff with three dots (git diff branch1...branch2)
# From the git documentation: "git diff A...B" is equivalent to "git diff $(git-merge-base A B) B"
common_commit = merge_repo.git.native(:merge_base, default_options, ["origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}"]).strip
merge_repo.git.native(:diff, default_options, common_commit, "source/#{merge_request.source_branch}")
end
rescue Grit::Git::CommandFailed => ex
handle_exception(ex)
end
def diffs_between_satellite
in_locked_and_timed_satellite do |merge_repo|
prepare_satellite!(merge_repo)
update_satellite_source_and_target!(merge_repo)
if merge_request.for_fork?
repository = Gitlab::Git::Repository.new(merge_repo.path)
diffs = Gitlab::Git::Diff.between(
repository,
"source/#{merge_request.source_branch}",
"origin/#{merge_request.target_branch}"
)
else
raise "Attempt to determine diffs between for a non forked merge request in satellite MergeRequest.id:[#{merge_request.id}]"
end
return diffs
end
rescue Grit::Git::CommandFailed => ex
handle_exception(ex)
end
# Get commit as an email patch
def format_patch
in_locked_and_timed_satellite do |merge_repo|
prepare_satellite!(merge_repo)
update_satellite_source_and_target!(merge_repo)
patch = merge_repo.git.format_patch(default_options({ stdout: true }), "origin/#{merge_request.target_branch}..source/#{merge_request.source_branch}")
end
rescue Grit::Git::CommandFailed => ex
handle_exception(ex)
end
# Retrieve an array of commits between the source and the target
def commits_between
in_locked_and_timed_satellite do |merge_repo|
prepare_satellite!(merge_repo)
update_satellite_source_and_target!(merge_repo)
if merge_request.for_fork?
repository = Gitlab::Git::Repository.new(merge_repo.path)
commits = Gitlab::Git::Commit.between(
repository,
"origin/#{merge_request.target_branch}",
"source/#{merge_request.source_branch}"
)
else
raise "Attempt to determine commits between for a non forked merge request in satellite MergeRequest.id:[#{merge_request.id}]"
end
return commits
end
rescue Grit::Git::CommandFailed => ex
handle_exception(ex)
end
private
# Merges the source_branch into the target_branch in the satellite.
#
# Note: it will clear out the satellite before doing anything
#
# Returns false if the merge produced conflicts
# Returns true otherwise
def merge_in_satellite!(repo, message = nil)
update_satellite_source_and_target!(repo)
message ||= "Merge branch '#{merge_request.source_branch}' into '#{merge_request.target_branch}'"
# merge the source branch into the satellite
# will raise CommandFailed when merge fails
repo.git.merge(default_options({ no_ff: true }), "-m#{message}", "source/#{merge_request.source_branch}")
rescue Grit::Git::CommandFailed => ex
handle_exception(ex)
end
# Assumes a satellite exists that is a fresh clone of the projects repo, prepares satellite for merges, diffs etc
def update_satellite_source_and_target!(repo)
repo.remote_add('source', merge_request.source_project.repository.path_to_repo)
repo.remote_fetch('source')
repo.git.checkout(default_options({ b: true }), merge_request.target_branch, "origin/#{merge_request.target_branch}")
rescue Grit::Git::CommandFailed => ex
handle_exception(ex)
end
end
end
end
module Gitlab
module Satellite
autoload :DeleteFileAction, 'gitlab/satellite/files/delete_file_action'
autoload :EditFileAction, 'gitlab/satellite/files/edit_file_action'
autoload :FileAction, 'gitlab/satellite/files/file_action'
autoload :NewFileAction, 'gitlab/satellite/files/new_file_action'
class CheckoutFailed < StandardError; end
class CommitFailed < StandardError; end
class PushFailed < StandardError; end
class Satellite
include Gitlab::Popen
PARKING_BRANCH = "__parking_branch"
attr_accessor :project
def initialize(project)
@project = project
end
def log(message)
Gitlab::Satellite::Logger.error(message)
end
def clear_and_update!
project.ensure_satellite_exists
@repo = nil
clear_working_dir!
delete_heads!
remove_remotes!
update_from_source!
end
def create
output, status = popen(%W(git clone -- #{project.repository.path_to_repo} #{path}),
Gitlab.config.satellites.path)
log("PID: #{project.id}: git clone #{project.repository.path_to_repo} #{path}")
log("PID: #{project.id}: -> #{output}")
if status.zero?
true
else
log("Failed to create satellite for #{project.name_with_namespace}")
false
end
end
def exists?
File.exists? path
end
# * Locks the satellite
# * Changes the current directory to the satellite's working dir
# * Yields
def lock
project.ensure_satellite_exists
File.open(lock_file, "w+") do |f|
begin
f.flock File::LOCK_EX
yield
ensure
f.flock File::LOCK_UN
end
end
end
def lock_file
create_locks_dir unless File.exists?(lock_files_dir)
File.join(lock_files_dir, "satellite_#{project.id}.lock")
end
def path
File.join(Gitlab.config.satellites.path, project.path_with_namespace)
end
def repo
project.ensure_satellite_exists
@repo ||= Grit::Repo.new(path)
end
def destroy
FileUtils.rm_rf(path)
end
private
# Clear the working directory
def clear_working_dir!
repo.git.reset(hard: true)
repo.git.clean(f: true, d: true, x: true)
end
# Deletes all branches except the parking branch
#
# This ensures we have no name clashes or issues updating branches when
# working with the satellite.
def delete_heads!
heads = repo.heads.map(&:name)
# update or create the parking branch
repo.git.checkout(default_options({ B: true }), PARKING_BRANCH)
# remove the parking branch from the list of heads ...
heads.delete(PARKING_BRANCH)
# ... and delete all others
heads.each { |head| repo.git.branch(default_options({ D: true }), head) }
end
# Deletes all remotes except origin
#
# This ensures we have no remote name clashes or issues updating branches when
# working with the satellite.
def remove_remotes!
remotes = repo.git.remote.split(' ')
remotes.delete('origin')
remotes.each { |name| repo.git.remote(default_options,'rm', name)}
end
# Updates the satellite from bare repo
#
# Note: this will only update remote branches (i.e. origin/*)
def update_from_source!
repo.git.remote(default_options, 'set-url', :origin, project.repository.path_to_repo)
repo.git.fetch(default_options, :origin)
end
def default_options(options = {})
{ raise: true, timeout: true }.merge(options)
end
# Create directory for storing
# satellites lock files
def create_locks_dir
FileUtils.mkdir_p(lock_files_dir)
end
def lock_files_dir
@lock_files_dir ||= File.join(Gitlab.config.satellites.path, "tmp")
end
end
end
end
...@@ -47,7 +47,7 @@ module Rouge ...@@ -47,7 +47,7 @@ module Rouge
@lineanchors = lineanchors @lineanchors = lineanchors
@lineanchorsid = lineanchorsid @lineanchorsid = lineanchorsid
@anchorlinenos = anchorlinenos @anchorlinenos = anchorlinenos
@inline_theme = Theme.find(@inline_theme).new if @inline_theme.is_a?(String) @inline_theme = Theme.find(inline_theme).new if inline_theme.is_a?(String)
end end
def render(tokens) def render(tokens)
...@@ -148,6 +148,12 @@ module Rouge ...@@ -148,6 +148,12 @@ module Rouge
end end
end end
def wrap_values(val, element)
lines = val.split("\n")
lines = lines.map{ |x| "<span #{element}>#{x}</span>" }
lines.join("\n")
end
def span(tok, val) def span(tok, val)
# http://stackoverflow.com/a/1600584/2587286 # http://stackoverflow.com/a/1600584/2587286
val = CGI.escapeHTML(val) val = CGI.escapeHTML(val)
...@@ -155,11 +161,13 @@ module Rouge ...@@ -155,11 +161,13 @@ module Rouge
if tok.shortname.empty? if tok.shortname.empty?
val val
else else
# In the case of multi-line values (e.g. comments), we need to apply
# styling to each line since span elements are inline.
if @inline_theme if @inline_theme
rules = @inline_theme.style_for(tok).rendered_rules rules = @inline_theme.style_for(tok).rendered_rules
"<span style=\"#{rules.to_a.join(';')}\">#{val}</span>" wrap_values(val, "style=\"#{rules.to_a.join(';')}\"")
else else
"<span class=\"#{tok.shortname}\">#{val}</span>" wrap_values(val, "class=\"#{tok.shortname}\"")
end end
end end
end end
......
...@@ -25,7 +25,6 @@ namespace :gitlab do ...@@ -25,7 +25,6 @@ namespace :gitlab do
check_init_script_exists check_init_script_exists
check_init_script_up_to_date check_init_script_up_to_date
check_projects_have_namespace check_projects_have_namespace
check_satellites_exist
check_redis_version check_redis_version
check_ruby_version check_ruby_version
check_git_version check_git_version
...@@ -238,37 +237,6 @@ namespace :gitlab do ...@@ -238,37 +237,6 @@ namespace :gitlab do
end end
end end
def check_satellites_exist
print "Projects have satellites? ... "
unless Project.count > 0
puts "can't check, you have no projects".magenta
return
end
puts ""
Project.find_each(batch_size: 100) do |project|
print sanitized_message(project)
if project.satellite.exists?
puts "yes".green
elsif project.empty_repo?
puts "can't create, repository is empty".magenta
else
puts "no".red
try_fixing_it(
sudo_gitlab("bundle exec rake gitlab:satellites:create RAILS_ENV=production"),
"If necessary, remove the tmp/repo_satellites directory ...",
"... and rerun the above command"
)
for_more_information(
"doc/raketasks/maintenance.md "
)
fix_and_rerun
end
end
end
def check_log_writable def check_log_writable
print "Log directory writable? ... " print "Log directory writable? ... "
...@@ -339,7 +307,6 @@ namespace :gitlab do ...@@ -339,7 +307,6 @@ namespace :gitlab do
check_repo_base_is_not_symlink check_repo_base_is_not_symlink
check_repo_base_user_and_group check_repo_base_user_and_group
check_repo_base_permissions check_repo_base_permissions
check_satellites_permissions
check_repos_hooks_directory_is_link check_repos_hooks_directory_is_link
check_gitlab_shell_self_test check_gitlab_shell_self_test
...@@ -417,29 +384,6 @@ namespace :gitlab do ...@@ -417,29 +384,6 @@ namespace :gitlab do
end end
end end
def check_satellites_permissions
print "Satellites access is drwxr-x---? ... "
satellites_path = Gitlab.config.satellites.path
unless File.exists?(satellites_path)
puts "can't check because of previous errors".magenta
return
end
if File.stat(satellites_path).mode.to_s(8).ends_with?("0750")
puts "yes".green
else
puts "no".red
try_fixing_it(
"sudo chmod u+rwx,g=rx,o-rwx #{satellites_path}",
)
for_more_information(
see_installation_guide_section "GitLab"
)
fix_and_rerun
end
end
def check_repo_base_user_and_group def check_repo_base_user_and_group
gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user
gitlab_shell_owner_group = Gitlab.config.gitlab_shell.owner_group gitlab_shell_owner_group = Gitlab.config.gitlab_shell.owner_group
...@@ -485,7 +429,8 @@ namespace :gitlab do ...@@ -485,7 +429,8 @@ namespace :gitlab do
if project.empty_repo? if project.empty_repo?
puts "repository is empty".magenta puts "repository is empty".magenta
elsif File.realpath(project_hook_directory) == File.realpath(gitlab_shell_hooks_path) elsif File.directory?(project_hook_directory) && File.directory?(gitlab_shell_hooks_path) &&
(File.realpath(project_hook_directory) == File.realpath(gitlab_shell_hooks_path))
puts 'ok'.green puts 'ok'.green
else else
puts "wrong or missing hooks".red puts "wrong or missing hooks".red
...@@ -754,7 +699,7 @@ namespace :gitlab do ...@@ -754,7 +699,7 @@ namespace :gitlab do
print "Ruby version >= #{required_version} ? ... " print "Ruby version >= #{required_version} ? ... "
if current_version.valid? && required_version <= current_version if current_version.valid? && required_version <= current_version
puts "yes (#{current_version})".green puts "yes (#{current_version})".green
else else
puts "no".red puts "no".red
try_fixing_it( try_fixing_it(
...@@ -772,7 +717,7 @@ namespace :gitlab do ...@@ -772,7 +717,7 @@ namespace :gitlab do
print "Git version >= #{required_version} ? ... " print "Git version >= #{required_version} ? ... "
if current_version.valid? && required_version <= current_version if current_version.valid? && required_version <= current_version
puts "yes (#{current_version})".green puts "yes (#{current_version})".green
else else
puts "no".red puts "no".red
try_fixing_it( try_fixing_it(
...@@ -806,4 +751,3 @@ namespace :gitlab do ...@@ -806,4 +751,3 @@ namespace :gitlab do
end end
end end
end end
namespace :gitlab do
namespace :satellites do
desc "GitLab | Create satellite repos"
task create: :environment do
create_satellites
end
end
def create_satellites
warn_user_is_not_gitlab
print "Creating satellites for ..."
unless Project.count > 0
puts "skipping, because you have no projects".magenta
return
end
puts ""
Project.find_each(batch_size: 100) do |project|
print "#{project.name_with_namespace.yellow} ... "
unless project.repo_exists?
puts "skipping, because the repo is empty".magenta
next
end
if project.satellite.exists?
puts "exists already".green
else
print "\n... "
if project.satellite.create
puts "created".green
else
puts "error".red
end
end
end
end
end
...@@ -37,6 +37,20 @@ describe Admin::UsersController do ...@@ -37,6 +37,20 @@ describe Admin::UsersController do
end end
end end
describe 'PUT confirm/:id' do
let(:user) { create(:user, confirmed_at: nil) }
before do
request.env["HTTP_REFERER"] = "/"
end
it 'confirms user' do
put :confirm, id: user.username
user.reload
expect(user.confirmed?).to be_truthy
end
end
describe 'PATCH disable_two_factor' do describe 'PATCH disable_two_factor' do
let(:user) { create(:user) } let(:user) { create(:user) }
......
...@@ -54,6 +54,13 @@ describe Projects::BranchesController do ...@@ -54,6 +54,13 @@ describe Projects::BranchesController do
let(:ref) { "<script>alert('ref');</script>" } let(:ref) { "<script>alert('ref');</script>" }
it { is_expected.to render_template('new') } it { is_expected.to render_template('new') }
end end
context "valid branch name with encoded slashes" do
let(:branch) { "feature%2Ftest" }
let(:ref) { "<script>alert('ref');</script>" }
it { is_expected.to render_template('new') }
it { project.repository.branch_names.include?('feature/test')}
end
end end
describe "POST destroy" do describe "POST destroy" do
...@@ -74,6 +81,19 @@ describe Projects::BranchesController do ...@@ -74,6 +81,19 @@ describe Projects::BranchesController do
it { expect(subject).to render_template('destroy') } it { expect(subject).to render_template('destroy') }
end end
context "valid branch name with unencoded slashes" do
let(:branch) { "improve/awesome" }
it { expect(response.status).to eq(200) }
it { expect(subject).to render_template('destroy') }
end
context "valid branch name with encoded slashes" do
let(:branch) { "improve%2Fawesome" }
it { expect(response.status).to eq(200) }
it { expect(subject).to render_template('destroy') }
end
context "invalid branch name, valid ref" do context "invalid branch name, valid ref" do
let(:branch) { "no-branch" } let(:branch) { "no-branch" }
......
...@@ -48,6 +48,17 @@ describe Profiles::KeysController do ...@@ -48,6 +48,17 @@ describe Profiles::KeysController do
expect(response.body).not_to eq("") expect(response.body).not_to eq("")
expect(response.body).to eq(user.all_ssh_keys.join("\n")) expect(response.body).to eq(user.all_ssh_keys.join("\n"))
# Unique part of key 1
expect(response.body).to match(/PWx6WM4lhHNedGfBpPJNPpZ/)
# Key 2
expect(response.body).to match(/AQDmTillFzNTrrGgwaCKaSj/)
end
it "should not render the comment of the key" do
get :get_keys, username: user.username
expect(response.body).not_to match(/dummy@gitlab.com/)
end end
it "should respond with text/plain content type" do it "should respond with text/plain content type" do
......
...@@ -100,7 +100,7 @@ FactoryGirl.define do ...@@ -100,7 +100,7 @@ FactoryGirl.define do
factory :key do factory :key do
title title
key do key do
"ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0= dummy@gitlab.com"
end end
factory :deploy_key, class: 'DeployKey' do factory :deploy_key, class: 'DeployKey' do
......
...@@ -21,12 +21,13 @@ ...@@ -21,12 +21,13 @@
# import_url :string(255) # import_url :string(255)
# visibility_level :integer default(0), not null # visibility_level :integer default(0), not null
# archived :boolean default(FALSE), not null # archived :boolean default(FALSE), not null
# avatar :string(255)
# import_status :string(255) # import_status :string(255)
# repository_size :float default(0.0) # repository_size :float default(0.0)
# star_count :integer default(0), not null # star_count :integer default(0), not null
# import_type :string(255) # import_type :string(255)
# import_source :string(255) # import_source :string(255)
# avatar :string(255) # commit_count :integer default(0)
# #
FactoryGirl.define do FactoryGirl.define do
......
...@@ -17,410 +17,215 @@ require 'erb' ...@@ -17,410 +17,215 @@ require 'erb'
# -> Post-process HTML # -> Post-process HTML
# -> `gfm_with_options` helper # -> `gfm_with_options` helper
# -> HTML::Pipeline # -> HTML::Pipeline
# -> Sanitize # -> SanitizationFilter
# -> RelativeLink # -> Other filters, depending on pipeline
# -> Emoji
# -> Table of Contents
# -> Autolinks
# -> Rinku (http, https, ftp)
# -> Other schemes
# -> ExternalLink
# -> References
# -> TaskList
# -> `html_safe` # -> `html_safe`
# -> Template # -> Template
# #
# See the MarkdownFeature class for setup details. # See the MarkdownFeature class for setup details.
describe 'GitLab Markdown', feature: true do describe 'GitLab Markdown', feature: true do
include ActionView::Helpers::TagHelper
include ActionView::Helpers::UrlHelper
include Capybara::Node::Matchers include Capybara::Node::Matchers
include GitlabMarkdownHelper include GitlabMarkdownHelper
include MarkdownMatchers
# `markdown` calls these two methods # Sometimes it can be useful to see the parsed output of the Markdown document
def current_user # for debugging. Call this method to write the output to
@feat.user # `tmp/capybara/<filename>.html`.
end def write_markdown(filename = 'markdown_spec')
File.open(Rails.root.join("tmp/capybara/#{filename}.html"), 'w') do |file|
def user_color_scheme_class file.puts @html
:white end
end
# Let's only parse this thing once
before(:all) do
@feat = MarkdownFeature.new
# `markdown` expects a `@project` variable
@project = @feat.project
@md = markdown(@feat.raw_markdown)
@doc = Nokogiri::HTML::DocumentFragment.parse(@md)
end
after(:all) do
@feat.teardown
end end
# Given a header ID, goes to that element's parent (the header itself), then def doc(html = @html)
# its next sibling element (the body). Nokogiri::HTML::DocumentFragment.parse(html)
def get_section(id)
@doc.at_css("##{id}").parent.next_element
end end
# Sometimes it can be useful to see the parsed output of the Markdown document # Shared behavior that all pipelines should exhibit
# for debugging. Uncomment this block to write the output to shared_examples 'all pipelines' do
# tmp/capybara/markdown_spec.html. describe 'Redcarpet extensions' do
#
# it 'writes to a file' do
# File.open(Rails.root.join('tmp/capybara/markdown_spec.html'), 'w') do |file|
# file.puts @md
# end
# end
describe 'Markdown' do
describe 'No Intra Emphasis' do
it 'does not parse emphasis inside of words' do it 'does not parse emphasis inside of words' do
body = get_section('no-intra-emphasis') expect(doc.to_html).not_to match('foo<em>bar</em>baz')
expect(body.to_html).not_to match('foo<em>bar</em>baz')
end end
end
describe 'Tables' do
it 'parses table Markdown' do it 'parses table Markdown' do
body = get_section('tables') aggregate_failures do
expect(body).to have_selector('th:contains("Header")') expect(doc).to have_selector('th:contains("Header")')
expect(body).to have_selector('th:contains("Row")') expect(doc).to have_selector('th:contains("Row")')
expect(body).to have_selector('th:contains("Example")') expect(doc).to have_selector('th:contains("Example")')
end
end end
it 'allows Markdown in tables' do it 'allows Markdown in tables' do
expect(@doc.at_css('td:contains("Baz")').children.to_html). expect(doc.at_css('td:contains("Baz")').children.to_html).
to eq '<strong>Baz</strong>' to eq '<strong>Baz</strong>'
end end
end
describe 'Fenced Code Blocks' do
it 'parses fenced code blocks' do it 'parses fenced code blocks' do
expect(@doc).to have_selector('pre.code.highlight.white.c') aggregate_failures do
expect(@doc).to have_selector('pre.code.highlight.white.python') expect(doc).to have_selector('pre.code.highlight.white.c')
expect(doc).to have_selector('pre.code.highlight.white.python')
end
end end
end
describe 'Strikethrough' do
it 'parses strikethroughs' do it 'parses strikethroughs' do
expect(@doc).to have_selector(%{del:contains("and this text doesn't")}) expect(doc).to have_selector(%{del:contains("and this text doesn't")})
end end
end
describe 'Superscript' do
it 'parses superscript' do it 'parses superscript' do
body = get_section('superscript') expect(doc).to have_selector('sup', count: 2)
expect(body.to_html).to match('1<sup>st</sup>')
expect(body.to_html).to match('2<sup>nd</sup>')
end end
end end
end
describe 'HTML::Pipeline' do
describe 'SanitizationFilter' do describe 'SanitizationFilter' do
it 'uses a permissive whitelist' do it 'permits b elements' do
expect(@doc).to have_selector('b:contains("b tag")') expect(doc).to have_selector('b:contains("b tag")')
expect(@doc).to have_selector('em:contains("em tag")')
expect(@doc).to have_selector('code:contains("code tag")')
expect(@doc).to have_selector('kbd:contains("s")')
expect(@doc).to have_selector('strike:contains(Emoji)')
expect(@doc).to have_selector('img[src*="smile.png"]')
expect(@doc).to have_selector('br')
expect(@doc).to have_selector('hr')
end end
it 'permits span elements' do it 'permits em elements' do
expect(@doc).to have_selector('span:contains("span tag")') expect(doc).to have_selector('em:contains("em tag")')
end end
it 'permits table alignment' do it 'permits code elements' do
expect(@doc.at_css('th:contains("Header")')['style']).to eq 'text-align: center' expect(doc).to have_selector('code:contains("code tag")')
expect(@doc.at_css('th:contains("Row")')['style']).to eq 'text-align: right'
expect(@doc.at_css('th:contains("Example")')['style']).to eq 'text-align: left'
expect(@doc.at_css('td:contains("Foo")')['style']).to eq 'text-align: center'
expect(@doc.at_css('td:contains("Bar")')['style']).to eq 'text-align: right'
expect(@doc.at_css('td:contains("Baz")')['style']).to eq 'text-align: left'
end end
it 'removes `rel` attribute from links' do it 'permits kbd elements' do
body = get_section('sanitizationfilter') expect(doc).to have_selector('kbd:contains("s")')
expect(body).not_to have_selector('a[rel="bookmark"]')
end end
it "removes `href` from `a` elements if it's fishy" do it 'permits strike elements' do
expect(@doc).not_to have_selector('a[href*="javascript"]') expect(doc).to have_selector('strike:contains(Emoji)')
end end
end
describe 'Escaping' do it 'permits img elements' do
let(:table) { @doc.css('table').last.at_css('tbody') } expect(doc).to have_selector('img[src*="smile.png"]')
it 'escapes non-tag angle brackets' do
expect(table.at_xpath('.//tr[1]/td[3]').inner_html).to eq '1 &lt; 3 &amp; 5'
end end
end
describe 'Edge Cases' do
it 'allows markup inside link elements' do
expect(@doc.at_css('a[href="#link-emphasis"]').to_html).
to eq %{<a href="#link-emphasis"><em>text</em></a>}
expect(@doc.at_css('a[href="#link-strong"]').to_html).
to eq %{<a href="#link-strong"><strong>text</strong></a>}
expect(@doc.at_css('a[href="#link-code"]').to_html). it 'permits br elements' do
to eq %{<a href="#link-code"><code>text</code></a>} expect(doc).to have_selector('br')
end end
end
describe 'EmojiFilter' do it 'permits hr elements' do
it 'parses Emoji' do expect(doc).to have_selector('hr')
expect(@doc).to have_selector('img.emoji', count: 10)
end end
end
describe 'TableOfContentsFilter' do it 'permits span elements' do
it 'creates anchors inside header elements' do expect(doc).to have_selector('span:contains("span tag")')
expect(@doc).to have_selector('h1 a#gitlab-markdown')
expect(@doc).to have_selector('h2 a#markdown')
expect(@doc).to have_selector('h3 a#autolinkfilter')
end end
end
describe 'AutolinkFilter' do it 'permits style attribute in th elements' do
let(:list) { get_section('autolinkfilter').next_element } aggregate_failures do
expect(doc.at_css('th:contains("Header")')['style']).to eq 'text-align: center'
def item(index) expect(doc.at_css('th:contains("Row")')['style']).to eq 'text-align: right'
list.at_css("li:nth-child(#{index})") expect(doc.at_css('th:contains("Example")')['style']).to eq 'text-align: left'
end
end end
it 'autolinks http://' do it 'permits style attribute in td elements' do
expect(item(1).children.first.name).to eq 'a' aggregate_failures do
expect(item(1).children.first['href']).to eq 'http://about.gitlab.com/' expect(doc.at_css('td:contains("Foo")')['style']).to eq 'text-align: center'
expect(doc.at_css('td:contains("Bar")')['style']).to eq 'text-align: right'
expect(doc.at_css('td:contains("Baz")')['style']).to eq 'text-align: left'
end
end end
it 'autolinks https://' do it 'removes `rel` attribute from links' do
expect(item(2).children.first.name).to eq 'a' expect(doc).not_to have_selector('a[rel="bookmark"]')
expect(item(2).children.first['href']).to eq 'https://google.com/'
end end
it 'autolinks ftp://' do it "removes `href` from `a` elements if it's fishy" do
expect(item(3).children.first.name).to eq 'a' expect(doc).not_to have_selector('a[href*="javascript"]')
expect(item(3).children.first['href']).to eq 'ftp://ftp.us.debian.org/debian/'
end end
end
it 'autolinks smb://' do describe 'Escaping' do
expect(item(4).children.first.name).to eq 'a' it 'escapes non-tag angle brackets' do
expect(item(4).children.first['href']).to eq 'smb://foo/bar/baz' table = doc.css('table').last.at_css('tbody')
expect(table.at_xpath('.//tr[1]/td[3]').inner_html).to eq '1 &lt; 3 &amp; 5'
end end
end
it 'autolinks irc://' do describe 'Edge Cases' do
expect(item(5).children.first.name).to eq 'a' it 'allows markup inside link elements' do
expect(item(5).children.first['href']).to eq 'irc://irc.freenode.net/git' aggregate_failures do
end expect(doc.at_css('a[href="#link-emphasis"]').to_html).
to eq %{<a href="#link-emphasis"><em>text</em></a>}
it 'autolinks short, invalid URLs' do expect(doc.at_css('a[href="#link-strong"]').to_html).
expect(item(6).children.first.name).to eq 'a' to eq %{<a href="#link-strong"><strong>text</strong></a>}
expect(item(6).children.first['href']).to eq 'http://localhost:3000'
end
%w(code a kbd).each do |elem| expect(doc.at_css('a[href="#link-code"]').to_html).
it "ignores links inside '#{elem}' element" do to eq %{<a href="#link-code"><code>text</code></a>}
body = get_section('autolinkfilter')
expect(body).not_to have_selector("#{elem} a")
end end
end end
end end
describe 'ExternalLinkFilter' do describe 'ExternalLinkFilter' do
let(:links) { get_section('externallinkfilter').next_element }
it 'adds nofollow to external link' do it 'adds nofollow to external link' do
expect(links.css('a').first.to_html).to match 'nofollow' link = doc.at_css('a:contains("Google")')
expect(link.attr('rel')).to match 'nofollow'
end end
it 'ignores internal link' do it 'ignores internal link' do
expect(links.css('a').last.to_html).not_to match 'nofollow' link = doc.at_css('a:contains("GitLab Root")')
expect(link.attr('rel')).not_to match 'nofollow'
end end
end end
end
describe 'ReferenceFilter' do context 'default pipeline' do
it 'handles references in headers' do before(:all) do
header = @doc.at_css('#reference-filters-eg-1').parent @feat = MarkdownFeature.new
expect(header.css('a').size).to eq 2
end
it "handles references in Markdown" do
body = get_section('reference-filters-eg-1')
expect(body).to have_selector('em a.gfm-merge_request', count: 1)
end
it 'parses user references' do
body = get_section('userreferencefilter')
expect(body).to have_selector('a.gfm.gfm-project_member', count: 3)
end
it 'parses issue references' do
body = get_section('issuereferencefilter')
expect(body).to have_selector('a.gfm.gfm-issue', count: 2)
end
it 'parses merge request references' do
body = get_section('mergerequestreferencefilter')
expect(body).to have_selector('a.gfm.gfm-merge_request', count: 2)
end
it 'parses snippet references' do # `gfm_with_options` depends on a `@project` variable
body = get_section('snippetreferencefilter') @project = @feat.project
expect(body).to have_selector('a.gfm.gfm-snippet', count: 2)
end
it 'parses commit range references' do @html = markdown(@feat.raw_markdown)
body = get_section('commitrangereferencefilter') end
expect(body).to have_selector('a.gfm.gfm-commit_range', count: 2)
end
it 'parses commit references' do it_behaves_like 'all pipelines'
body = get_section('commitreferencefilter')
expect(body).to have_selector('a.gfm.gfm-commit', count: 2)
end
it 'parses label references' do it 'includes RelativeLinkFilter' do
body = get_section('labelreferencefilter') expect(doc).to parse_relative_links
expect(body).to have_selector('a.gfm.gfm-label', count: 3)
end
end end
describe 'Task Lists' do it 'includes EmojiFilter' do
it 'generates task lists' do expect(doc).to parse_emoji
body = get_section('task-lists')
expect(body).to have_selector('ul.task-list', count: 2)
expect(body).to have_selector('li.task-list-item', count: 7)
expect(body).to have_selector('input[checked]', count: 3)
end
end end
end
end
# This is a helper class used by the GitLab Markdown feature spec
#
# Because the feature spec only cares about the output of the Markdown, and the
# test setup and teardown and parsing is fairly expensive, we only want to do it
# once. Unfortunately RSpec will not let you access `let`s in a `before(:all)`
# block, so we fake it by encapsulating all the shared setup in this class.
#
# The class renders `spec/fixtures/markdown.md.erb` using ERB, allowing for
# reference to the factory-created objects.
class MarkdownFeature
include FactoryGirl::Syntax::Methods
def initialize
DatabaseCleaner.start
end
def teardown
DatabaseCleaner.clean
end
def user
@user ||= create(:user)
end
def group it 'includes TableOfContentsFilter' do
unless @group expect(doc).to create_header_links
@group = create(:group)
@group.add_user(user, Gitlab::Access::DEVELOPER)
end end
@group it 'includes AutolinkFilter' do
end expect(doc).to create_autolinks
# Direct references ----------------------------------------------------------
def project
@project ||= create(:project)
end
def issue
@issue ||= create(:issue, project: project)
end
def merge_request
@merge_request ||= create(:merge_request, :simple, source_project: project)
end
def snippet
@snippet ||= create(:project_snippet, project: project)
end
def commit
@commit ||= project.commit
end
def commit_range
unless @commit_range
commit2 = project.commit('HEAD~3')
@commit_range = CommitRange.new("#{commit.id}...#{commit2.id}", project)
end end
@commit_range it 'includes all reference filters' do
end aggregate_failures do
expect(doc).to reference_users
def simple_label expect(doc).to reference_issues
@simple_label ||= create(:label, name: 'gfm', project: project) expect(doc).to reference_merge_requests
end expect(doc).to reference_snippets
expect(doc).to reference_commit_ranges
def label expect(doc).to reference_commits
@label ||= create(:label, name: 'awaiting feedback', project: project) expect(doc).to reference_labels
end end
# Cross-references -----------------------------------------------------------
def xproject
unless @xproject
namespace = create(:namespace, name: 'cross-reference')
@xproject = create(:project, namespace: namespace)
@xproject.team << [user, :developer]
end end
@xproject it 'includes TaskListFilter' do
end expect(doc).to parse_task_lists
def xissue
@xissue ||= create(:issue, project: xproject)
end
def xmerge_request
@xmerge_request ||= create(:merge_request, :simple, source_project: xproject)
end
def xsnippet
@xsnippet ||= create(:project_snippet, project: xproject)
end
def xcommit
@xcommit ||= xproject.commit
end
def xcommit_range
unless @xcommit_range
xcommit2 = xproject.commit('HEAD~2')
@xcommit_range = CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject)
end end
end
@xcommit_range # `markdown` calls these two methods
def current_user
@feat.user
end end
def raw_markdown def user_color_scheme_class
fixture = Rails.root.join('spec/fixtures/markdown.md.erb') :white
ERB.new(File.read(fixture)).result(binding)
end end
end end
...@@ -100,6 +100,13 @@ Markdown should be usable inside a link. Let's try! ...@@ -100,6 +100,13 @@ Markdown should be usable inside a link. Let's try!
- [**text**](#link-strong) - [**text**](#link-strong)
- [`text`](#link-code) - [`text`](#link-code)
### RelativeLinkFilter
Linking to a file relative to this project's repository should work.
[Relative Link](doc/README.md)
![Relative Image](app/assets/images/touch-icon-ipad.png)
### EmojiFilter ### EmojiFilter
Because life would be :zzz: without Emoji, right? :rocket: Because life would be :zzz: without Emoji, right? :rocket:
...@@ -123,9 +130,9 @@ These are all plain text that should get turned into links: ...@@ -123,9 +130,9 @@ These are all plain text that should get turned into links:
But it shouldn't autolink text inside certain tags: But it shouldn't autolink text inside certain tags:
- <code>http://about.gitlab.com/</code> - <code>http://code.gitlab.com/</code>
- <a>http://about.gitlab.com/</a> - <a>http://a.gitlab.com/</a>
- <kbd>http://about.gitlab.com/</kbd> - <kbd>http://kbd.gitlab.com/</kbd>
### ExternalLinkFilter ### ExternalLinkFilter
......
...@@ -6,6 +6,14 @@ describe BlobHelper do ...@@ -6,6 +6,14 @@ describe BlobHelper do
let(:no_context_content) { ":type \"assem\"))" } let(:no_context_content) { ":type \"assem\"))" }
let(:blob_content) { "(make-pathname :defaults name\n#{no_context_content}" } let(:blob_content) { "(make-pathname :defaults name\n#{no_context_content}" }
let(:split_content) { blob_content.split("\n") } let(:split_content) { blob_content.split("\n") }
let(:multiline_content) do
%q(
def test(input):
"""This is line 1 of a multi-line comment.
This is line 2.
"""
)
end
it 'should return plaintext for unknown lexer context' do it 'should return plaintext for unknown lexer context' do
result = highlight(blob_name, no_context_content, nowrap: true, continue: false) result = highlight(blob_name, no_context_content, nowrap: true, continue: false)
...@@ -29,5 +37,15 @@ describe BlobHelper do ...@@ -29,5 +37,15 @@ describe BlobHelper do
result = split_content.map{ |content| highlight(blob_name, content, nowrap: true, continue: true) } result = split_content.map{ |content| highlight(blob_name, content, nowrap: true, continue: true) }
expect(result).to eq(expected) expect(result).to eq(expected)
end end
it 'should highlight multi-line comments' do
result = highlight(blob_name, multiline_content, nowrap: true, continue: false)
html = Nokogiri::HTML(result)
lines = html.search('.s')
expect(lines.count).to eq(3)
expect(lines[0].text).to eq('"""This is line 1 of a multi-line comment.')
expect(lines[1].text).to eq(' This is line 2.')
expect(lines[2].text).to eq(' """')
end
end end
end end
...@@ -29,6 +29,16 @@ describe ExtractsPath do ...@@ -29,6 +29,16 @@ describe ExtractsPath do
assign_ref_vars assign_ref_vars
expect(@logs_path).to eq("/#{@project.path_with_namespace}/refs/#{ref}/logs_tree/files/ruby/popen.rb") expect(@logs_path).to eq("/#{@project.path_with_namespace}/refs/#{ref}/logs_tree/files/ruby/popen.rb")
end end
context 'escaped sequences in ref' do
let(:ref) { "improve%2Fawesome" }
it "id should have no escape sequences" do
assign_ref_vars
expect(@ref).to eq('improve/awesome')
expect(@logs_path).to eq("/#{@project.path_with_namespace}/refs/#{ref}/logs_tree/files/ruby/popen.rb")
end
end
end end
describe '#extract_ref' do describe '#extract_ref' do
......
# encoding: UTF-8
require 'spec_helper' require 'spec_helper'
module Gitlab::Markdown module Gitlab::Markdown
...@@ -101,6 +103,20 @@ module Gitlab::Markdown ...@@ -101,6 +103,20 @@ module Gitlab::Markdown
expect(doc.at_css('a')['href']).to eq 'http://example.com' expect(doc.at_css('a')['href']).to eq 'http://example.com'
end end
it 'supports Unicode filenames' do
path = 'files/images/한글.png'
escaped = Addressable::URI.escape(path)
# Stub these methods so the file doesn't actually need to be in the repo
allow_any_instance_of(described_class).to receive(:file_exists?).
and_return(true)
allow_any_instance_of(described_class).
to receive(:image?).with(path).and_return(true)
doc = filter(image(escaped))
expect(doc.at_css('img')['src']).to match '/raw/'
end
context 'when requested path is a file in the repo' do context 'when requested path is a file in the repo' do
let(:requested_path) { 'doc/api/README.md' } let(:requested_path) { 'doc/api/README.md' }
include_examples :relative_to_requested include_examples :relative_to_requested
......
...@@ -36,7 +36,6 @@ describe 'Gitlab::NoteDataBuilder' do ...@@ -36,7 +36,6 @@ describe 'Gitlab::NoteDataBuilder' do
let(:note) { create(:note_on_issue, noteable_id: issue.id) } let(:note) { create(:note_on_issue, noteable_id: issue.id) }
it 'returns the note and issue-specific data' do it 'returns the note and issue-specific data' do
data[:issue]["updated_at"] = fixed_time
expect(data).to have_key(:issue) expect(data).to have_key(:issue)
expect(data[:issue]).to eq(issue.hook_attrs) expect(data[:issue]).to eq(issue.hook_attrs)
end end
...@@ -47,7 +46,6 @@ describe 'Gitlab::NoteDataBuilder' do ...@@ -47,7 +46,6 @@ describe 'Gitlab::NoteDataBuilder' do
let(:note) { create(:note_on_merge_request, noteable_id: merge_request.id) } let(:note) { create(:note_on_merge_request, noteable_id: merge_request.id) }
it 'returns the note and merge request data' do it 'returns the note and merge request data' do
data[:merge_request]["updated_at"] = fixed_time
expect(data).to have_key(:merge_request) expect(data).to have_key(:merge_request)
expect(data[:merge_request]).to eq(merge_request.hook_attrs) expect(data[:merge_request]).to eq(merge_request.hook_attrs)
end end
...@@ -58,7 +56,6 @@ describe 'Gitlab::NoteDataBuilder' do ...@@ -58,7 +56,6 @@ describe 'Gitlab::NoteDataBuilder' do
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) }
it 'returns the note and merge request diff data' do it 'returns the note and merge request diff data' do
data[:merge_request]["updated_at"] = fixed_time
expect(data).to have_key(:merge_request) expect(data).to have_key(:merge_request)
expect(data[:merge_request]).to eq(merge_request.hook_attrs) expect(data[:merge_request]).to eq(merge_request.hook_attrs)
end end
...@@ -69,7 +66,6 @@ describe 'Gitlab::NoteDataBuilder' do ...@@ -69,7 +66,6 @@ describe 'Gitlab::NoteDataBuilder' do
let!(:note) { create(:note_on_project_snippet, noteable_id: snippet.id) } let!(:note) { create(:note_on_project_snippet, noteable_id: snippet.id) }
it 'returns the note and project snippet data' do it 'returns the note and project snippet data' do
data[:snippet]["updated_at"] = fixed_time
expect(data).to have_key(:snippet) expect(data).to have_key(:snippet)
expect(data[:snippet]).to eq(snippet.hook_attrs) expect(data[:snippet]).to eq(snippet.hook_attrs)
end end
......
...@@ -25,7 +25,7 @@ describe Gitlab::ReferenceExtractor do ...@@ -25,7 +25,7 @@ describe Gitlab::ReferenceExtractor do
project.team << [@u_bar, :guest] project.team << [@u_bar, :guest]
subject.analyze(%Q{ subject.analyze(%Q{
Inline code: `@foo` Inline code: `@foo`
Code block: Code block:
...@@ -33,7 +33,7 @@ describe Gitlab::ReferenceExtractor do ...@@ -33,7 +33,7 @@ describe Gitlab::ReferenceExtractor do
@bar @bar
``` ```
Quote: Quote:
> @offteam > @offteam
}) })
...@@ -49,8 +49,8 @@ describe Gitlab::ReferenceExtractor do ...@@ -49,8 +49,8 @@ describe Gitlab::ReferenceExtractor do
end end
it 'accesses valid merge requests' do it 'accesses valid merge requests' do
@m0 = create(:merge_request, source_project: project, target_project: project, source_branch: 'aaa') @m0 = create(:merge_request, source_project: project, target_project: project, source_branch: 'markdown')
@m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'bbb') @m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'feature_conflict')
subject.analyze("!999, !#{@m1.iid}, and !#{@m0.iid}.") subject.analyze("!999, !#{@m1.iid}, and !#{@m0.iid}.")
expect(subject.merge_requests).to eq([@m1, @m0]) expect(subject.merge_requests).to eq([@m1, @m0])
......
require 'spec_helper'
describe 'Gitlab::Satellite::Action' do
let(:project) { create(:project) }
let(:user) { create(:user) }
describe '#prepare_satellite!' do
it 'should be able to fetch timeout from conf' do
expect(Gitlab::Satellite::Action::DEFAULT_OPTIONS[:git_timeout]).to eq(30.seconds)
end
it 'create a repository with a parking branch and one remote: origin' do
repo = project.satellite.repo
#now lets dirty it up
starting_remote_count = repo.git.list_remotes.size
expect(starting_remote_count).to be >= 1
#kind of hookey way to add a second remote
origin_uri = repo.git.remote({ v: true }).split(" ")[1]
repo.git.remote({ raise: true }, 'add', 'another-remote', origin_uri)
repo.git.branch({ raise: true }, 'a-new-branch')
expect(repo.heads.size).to be > (starting_remote_count)
expect(repo.git.remote().split(" ").size).to be > (starting_remote_count)
repo.git.config({}, "user.name", "#{user.name} -- foo")
repo.git.config({}, "user.email", "#{user.email} -- foo")
expect(repo.config['user.name']).to eq("#{user.name} -- foo")
expect(repo.config['user.email']).to eq("#{user.email} -- foo")
#These must happen in the context of the satellite directory...
satellite_action = Gitlab::Satellite::Action.new(user, project)
project.satellite.lock do
#Now clean it up, use send to get around prepare_satellite! being protected
satellite_action.send(:prepare_satellite!, repo)
end
#verify it's clean
heads = repo.heads.map(&:name)
expect(heads.size).to eq(1)
expect(heads.include?(Gitlab::Satellite::Satellite::PARKING_BRANCH)).to eq(true)
remotes = repo.git.remote().split(' ')
expect(remotes.size).to eq(1)
expect(remotes.include?('origin')).to eq(true)
expect(repo.config['user.name']).to eq(user.name)
expect(repo.config['user.email']).to eq(user.email)
end
end
describe '#in_locked_and_timed_satellite' do
it 'should make use of a lockfile' do
repo = project.satellite.repo
called = false
#set assumptions
FileUtils.rm_f(project.satellite.lock_file)
expect(File.exists?(project.satellite.lock_file)).to be_falsey
satellite_action = Gitlab::Satellite::Action.new(user, project)
satellite_action.send(:in_locked_and_timed_satellite) do |sat_repo|
expect(repo).to eq(sat_repo)
expect(File.exists? project.satellite.lock_file).to be_truthy
called = true
end
expect(called).to be_truthy
end
it 'should be able to use the satellite after locking' do
repo = project.satellite.repo
called = false
# Set base assumptions
if File.exists? project.satellite.lock_file
expect(FileLockStatusChecker.new(project.satellite.lock_file).flocked?).to be_falsey
end
satellite_action = Gitlab::Satellite::Action.new(user, project)
satellite_action.send(:in_locked_and_timed_satellite) do |sat_repo|
called = true
expect(repo).to eq(sat_repo)
expect(File.exists? project.satellite.lock_file).to be_truthy
expect(FileLockStatusChecker.new(project.satellite.lock_file).flocked?).to be_truthy
end
expect(called).to be_truthy
expect(FileLockStatusChecker.new(project.satellite.lock_file).flocked?).to be_falsey
end
class FileLockStatusChecker < File
def flocked?(&block)
status = flock LOCK_EX|LOCK_NB
case status
when false
return true
when 0
begin
block ? block.call : false
ensure
flock LOCK_UN
end
else
raise SystemCallError, status
end
end
end
end
end
require 'spec_helper'
describe 'Gitlab::Satellite::MergeAction' do
include RepoHelpers
let(:project) { create(:project, namespace: create(:group)) }
let(:fork_project) { create(:project, namespace: create(:group), forked_from_project: project) }
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:merge_request_fork) { create(:merge_request, source_project: fork_project, target_project: project) }
let(:merge_request_with_conflict) { create(:merge_request, :conflict, source_project: project, target_project: project) }
let(:merge_request_fork_with_conflict) { create(:merge_request, :conflict, source_project: project, target_project: project) }
describe '#commits_between' do
def verify_commits(commits, first_commit_sha, last_commit_sha)
commits.each { |commit| expect(commit.class).to eq(Gitlab::Git::Commit) }
expect(commits.first.id).to eq(first_commit_sha)
expect(commits.last.id).to eq(last_commit_sha)
end
context 'on fork' do
it 'should get proper commits between' do
commits = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).commits_between
verify_commits(commits, sample_compare.commits.first, sample_compare.commits.last)
end
end
context 'between branches' do
it 'should raise exception -- not expected to be used by non forks' do
expect { Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).commits_between }.to raise_error(RuntimeError)
end
end
end
describe '#format_patch' do
def verify_content(patch)
sample_compare.commits.each do |commit|
expect(patch.include?(commit)).to be_truthy
end
end
context 'on fork' do
it 'should build a format patch' do
patch = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).format_patch
verify_content(patch)
end
end
context 'between branches' do
it 'should build a format patch' do
patch = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request).format_patch
verify_content(patch)
end
end
end
describe '#diffs_between_satellite tested against diff_in_satellite' do
def is_a_matching_diff(diff, diffs)
diff_count = diff.scan('diff --git').size
expect(diff_count).to be >= 1
expect(diffs.size).to eq(diff_count)
diffs.each do |a_diff|
expect(a_diff.class).to eq(Gitlab::Git::Diff)
expect(diff.include? a_diff.diff).to be_truthy
end
end
context 'on fork' do
it 'should get proper diffs' do
diffs = Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).diffs_between_satellite
diff = Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request_fork).diff_in_satellite
is_a_matching_diff(diff, diffs)
end
end
context 'between branches' do
it 'should get proper diffs' do
expect{ Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).diffs_between_satellite }.to raise_error(RuntimeError)
end
end
end
describe '#can_be_merged?' do
context 'on fork' do
it do
expect(Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).can_be_merged?).to be_truthy
end
it do
expect(Gitlab::Satellite::MergeAction.new(merge_request_fork_with_conflict.author, merge_request_fork_with_conflict).can_be_merged?).to be_falsey
end
end
context 'between branches' do
it do
expect(Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).can_be_merged?).to be_truthy
end
it do
expect(Gitlab::Satellite::MergeAction.new(merge_request_with_conflict.author, merge_request_with_conflict).can_be_merged?).to be_falsey
end
end
end
end
...@@ -14,11 +14,14 @@ ...@@ -14,11 +14,14 @@
# default_branch_protection :integer default(2) # default_branch_protection :integer default(2)
# twitter_sharing_enabled :boolean default(TRUE) # twitter_sharing_enabled :boolean default(TRUE)
# restricted_visibility_levels :text # restricted_visibility_levels :text
# version_check_enabled :boolean default(TRUE)
# max_attachment_size :integer default(10), not null # max_attachment_size :integer default(10), not null
# session_expire_delay :integer default(10080), not null
# default_project_visibility :integer # default_project_visibility :integer
# default_snippet_visibility :integer # default_snippet_visibility :integer
# restricted_signup_domains :text # restricted_signup_domains :text
# user_oauth_applications :boolean default(TRUE)
# after_sign_out_path :string(255)
# session_expire_delay :integer default(10080), not null
# #
require 'spec_helper' require 'spec_helper'
......
...@@ -32,6 +32,13 @@ describe Key do ...@@ -32,6 +32,13 @@ describe Key do
describe "Methods" do describe "Methods" do
it { is_expected.to respond_to :projects } it { is_expected.to respond_to :projects }
it { is_expected.to respond_to :publishable_key }
describe "#publishable_keys" do
it 'strips all personal information' do
expect(build(:key).publishable_key).not_to match(/dummy@gitlab/)
end
end
end end
context "validation of uniqueness" do context "validation of uniqueness" do
......
...@@ -165,7 +165,7 @@ describe MergeRequest do ...@@ -165,7 +165,7 @@ describe MergeRequest do
end end
it_behaves_like 'an editable mentionable' do it_behaves_like 'an editable mentionable' do
subject { create(:merge_request, source_project: project) } subject { create(:merge_request) }
let(:backref_text) { "merge request #{subject.to_reference}" } let(:backref_text) { "merge request #{subject.to_reference}" }
let(:set_mentionable_text) { ->(txt){ subject.description = txt } } let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
......
...@@ -22,7 +22,7 @@ require 'spec_helper' ...@@ -22,7 +22,7 @@ require 'spec_helper'
describe Note do describe Note do
describe 'associations' do describe 'associations' do
it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:noteable).touch(true) } it { is_expected.to belong_to(:noteable) }
it { is_expected.to belong_to(:author).class_name('User') } it { is_expected.to belong_to(:author).class_name('User') }
end end
......
...@@ -67,7 +67,7 @@ describe SlackService do ...@@ -67,7 +67,7 @@ describe SlackService do
opts = { opts = {
title: 'Awesome merge_request', title: 'Awesome merge_request',
description: 'please fix', description: 'please fix',
source_branch: 'stable', source_branch: 'feature',
target_branch: 'master' target_branch: 'master'
} }
merge_service = MergeRequests::CreateService.new(project, merge_service = MergeRequests::CreateService.new(project,
......
...@@ -21,12 +21,13 @@ ...@@ -21,12 +21,13 @@
# import_url :string(255) # import_url :string(255)
# visibility_level :integer default(0), not null # visibility_level :integer default(0), not null
# archived :boolean default(FALSE), not null # archived :boolean default(FALSE), not null
# avatar :string(255)
# import_status :string(255) # import_status :string(255)
# repository_size :float default(0.0) # repository_size :float default(0.0)
# star_count :integer default(0), not null # star_count :integer default(0), not null
# import_type :string(255) # import_type :string(255)
# import_source :string(255) # import_source :string(255)
# avatar :string(255) # commit_count :integer default(0)
# #
require 'spec_helper' require 'spec_helper'
...@@ -90,7 +91,6 @@ describe Project do ...@@ -90,7 +91,6 @@ describe Project do
describe 'Respond to' do describe 'Respond to' do
it { is_expected.to respond_to(:url_to_repo) } it { is_expected.to respond_to(:url_to_repo) }
it { is_expected.to respond_to(:repo_exists?) } it { is_expected.to respond_to(:repo_exists?) }
it { is_expected.to respond_to(:satellite) }
it { is_expected.to respond_to(:update_merge_requests) } it { is_expected.to respond_to(:update_merge_requests) }
it { is_expected.to respond_to(:execute_hooks) } it { is_expected.to respond_to(:execute_hooks) }
it { is_expected.to respond_to(:name_with_namespace) } it { is_expected.to respond_to(:name_with_namespace) }
...@@ -111,14 +111,20 @@ describe Project do ...@@ -111,14 +111,20 @@ describe Project do
expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git') expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git')
end end
it 'returns the full web URL for this repo' do describe "#web_url" do
project = Project.new(path: 'somewhere') let(:project) { create(:empty_project, path: "somewhere") }
expect(project.web_url).to eq("#{Gitlab.config.gitlab.url}/somewhere")
it 'returns the full web URL for this repo' do
expect(project.web_url).to eq("#{Gitlab.config.gitlab.url}/#{project.namespace.path}/somewhere")
end
end end
it 'returns the web URL without the protocol for this repo' do describe "#web_url_without_protocol" do
project = Project.new(path: 'somewhere') let(:project) { create(:empty_project, path: "somewhere") }
expect(project.web_url_without_protocol).to eq("#{Gitlab.config.gitlab.url.split('://')[1]}/somewhere")
it 'returns the web URL without the protocol for this repo' do
expect(project.web_url_without_protocol).to eq("#{Gitlab.config.gitlab.url.split('://')[1]}/#{project.namespace.path}/somewhere")
end
end end
describe 'last_activity methods' do describe 'last_activity methods' do
......
...@@ -36,13 +36,13 @@ describe Repository do ...@@ -36,13 +36,13 @@ describe Repository do
describe :can_be_merged? do describe :can_be_merged? do
context 'mergeable branches' do context 'mergeable branches' do
subject { repository.can_be_merged?('feature', 'master') } subject { repository.can_be_merged?('0b4bc9a49b562e85de7cc9e834518ea6828729b9', 'master') }
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
end end
context 'non-mergeable branches' do context 'non-mergeable branches' do
subject { repository.can_be_merged?('feature_conflict', 'feature') } subject { repository.can_be_merged?('bb5206fee213d983da88c47f9cf4cc6caf9c66dc', 'feature') }
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
end end
......
...@@ -57,6 +57,7 @@ ...@@ -57,6 +57,7 @@
# otp_backup_codes :text # otp_backup_codes :text
# public_email :string(255) default(""), not null # public_email :string(255) default(""), not null
# dashboard :integer default(0) # dashboard :integer default(0)
# project_view :integer default(0)
# #
require 'spec_helper' require 'spec_helper'
...@@ -183,6 +184,19 @@ describe User do ...@@ -183,6 +184,19 @@ describe User do
it { is_expected.to respond_to(:private_token) } it { is_expected.to respond_to(:private_token) }
end end
describe '#confirm' do
let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: 'test@gitlab.com') }
it 'returns unconfirmed' do
expect(user.confirmed?).to be_falsey
end
it 'confirms a user' do
user.confirm!
expect(user.confirmed?).to be_truthy
end
end
describe '#to_reference' do describe '#to_reference' do
let(:user) { create(:user) } let(:user) { create(:user) }
......
...@@ -110,7 +110,7 @@ describe API::API, api: true do ...@@ -110,7 +110,7 @@ describe API::API, api: true do
expect(response.status).to eq(400) expect(response.status).to eq(400)
end end
it "should return a 400 if satellite fails to create file" do it "should return a 400 if fails to create file" do
allow_any_instance_of(Repository).to receive(:remove_file).and_return(false) allow_any_instance_of(Repository).to receive(:remove_file).and_return(false)
delete api("/projects/#{project.id}/repository/files", user), valid_params delete api("/projects/#{project.id}/repository/files", user), valid_params
......
...@@ -148,7 +148,7 @@ describe API::API, api: true do ...@@ -148,7 +148,7 @@ describe API::API, api: true do
it "should return merge_request" do it "should return merge_request" do
post api("/projects/#{project.id}/merge_requests", user), post api("/projects/#{project.id}/merge_requests", user),
title: 'Test merge_request', title: 'Test merge_request',
source_branch: 'stable', source_branch: 'feature_conflict',
target_branch: 'master', target_branch: 'master',
author: user, author: user,
labels: 'label, label2' labels: 'label, label2'
...@@ -171,20 +171,20 @@ describe API::API, api: true do ...@@ -171,20 +171,20 @@ describe API::API, api: true do
it "should return 400 when target_branch is missing" do it "should return 400 when target_branch is missing" do
post api("/projects/#{project.id}/merge_requests", user), post api("/projects/#{project.id}/merge_requests", user),
title: "Test merge_request", source_branch: "stable", author: user title: "Test merge_request", source_branch: "markdown", author: user
expect(response.status).to eq(400) expect(response.status).to eq(400)
end end
it "should return 400 when title is missing" do it "should return 400 when title is missing" do
post api("/projects/#{project.id}/merge_requests", user), post api("/projects/#{project.id}/merge_requests", user),
target_branch: 'master', source_branch: 'stable' target_branch: 'master', source_branch: 'markdown'
expect(response.status).to eq(400) expect(response.status).to eq(400)
end end
it 'should return 400 on invalid label names' do it 'should return 400 on invalid label names' do
post api("/projects/#{project.id}/merge_requests", user), post api("/projects/#{project.id}/merge_requests", user),
title: 'Test merge_request', title: 'Test merge_request',
source_branch: 'stable', source_branch: 'markdown',
target_branch: 'master', target_branch: 'master',
author: user, author: user,
labels: 'label, ?' labels: 'label, ?'
...@@ -198,7 +198,7 @@ describe API::API, api: true do ...@@ -198,7 +198,7 @@ describe API::API, api: true do
before do before do
post api("/projects/#{project.id}/merge_requests", user), post api("/projects/#{project.id}/merge_requests", user),
title: 'Test merge_request', title: 'Test merge_request',
source_branch: 'stable', source_branch: 'feature_conflict',
target_branch: 'master', target_branch: 'master',
author: user author: user
@mr = MergeRequest.all.last @mr = MergeRequest.all.last
...@@ -208,7 +208,7 @@ describe API::API, api: true do ...@@ -208,7 +208,7 @@ describe API::API, api: true do
expect do expect do
post api("/projects/#{project.id}/merge_requests", user), post api("/projects/#{project.id}/merge_requests", user),
title: 'New test merge_request', title: 'New test merge_request',
source_branch: 'stable', source_branch: 'feature_conflict',
target_branch: 'master', target_branch: 'master',
author: user author: user
end.to change { MergeRequest.count }.by(0) end.to change { MergeRequest.count }.by(0)
...@@ -228,7 +228,8 @@ describe API::API, api: true do ...@@ -228,7 +228,8 @@ describe API::API, api: true do
it "should return merge_request" do it "should return merge_request" do
post api("/projects/#{fork_project.id}/merge_requests", user2), post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user2, target_project_id: project.id, description: 'Test description for Test merge_request' title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master",
author: user2, target_project_id: project.id, description: 'Test description for Test merge_request'
expect(response.status).to eq(201) expect(response.status).to eq(201)
expect(json_response['title']).to eq('Test merge_request') expect(json_response['title']).to eq('Test merge_request')
expect(json_response['description']).to eq('Test description for Test merge_request') expect(json_response['description']).to eq('Test description for Test merge_request')
...@@ -258,7 +259,7 @@ describe API::API, api: true do ...@@ -258,7 +259,7 @@ describe API::API, api: true do
it "should return 400 when title is missing" do it "should return 400 when title is missing" do
post api("/projects/#{fork_project.id}/merge_requests", user2), post api("/projects/#{fork_project.id}/merge_requests", user2),
target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: project.id target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: project.id
expect(response.status).to eq(400) expect(response.status).to eq(400)
end end
...@@ -267,7 +268,7 @@ describe API::API, api: true do ...@@ -267,7 +268,7 @@ describe API::API, api: true do
post api("/projects/#{project.id}/merge_requests", user), post api("/projects/#{project.id}/merge_requests", user),
title: 'Test merge_request', title: 'Test merge_request',
target_branch: 'master', target_branch: 'master',
source_branch: 'stable', source_branch: 'markdown',
author: user, author: user,
target_project_id: fork_project.id target_project_id: fork_project.id
expect(response.status).to eq(422) expect(response.status).to eq(422)
...@@ -277,7 +278,7 @@ describe API::API, api: true do ...@@ -277,7 +278,7 @@ describe API::API, api: true do
post api("/projects/#{fork_project.id}/merge_requests", user2), post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', title: 'Test merge_request',
target_branch: 'master', target_branch: 'master',
source_branch: 'stable', source_branch: 'markdown',
author: user2, author: user2,
target_project_id: unrelated_project.id target_project_id: unrelated_project.id
expect(response.status).to eq(422) expect(response.status).to eq(422)
...@@ -286,7 +287,7 @@ describe API::API, api: true do ...@@ -286,7 +287,7 @@ describe API::API, api: true do
it "should return 201 when target_branch is specified and for the same project" do it "should return 201 when target_branch is specified and for the same project" do
post api("/projects/#{fork_project.id}/merge_requests", user2), post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: fork_project.id title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: fork_project.id
expect(response.status).to eq(201) expect(response.status).to eq(201)
end end
end end
...@@ -302,9 +303,6 @@ describe API::API, api: true do ...@@ -302,9 +303,6 @@ describe API::API, api: true do
describe "PUT /projects/:id/merge_request/:merge_request_id/merge" do describe "PUT /projects/:id/merge_request/:merge_request_id/merge" do
it "should return merge_request in case of success" do it "should return merge_request in case of success" do
allow_any_instance_of(MergeRequest).
to receive_messages(can_be_merged?: true, automerge!: true)
put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user) put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
expect(response.status).to eq(200) expect(response.status).to eq(200)
......
...@@ -6,6 +6,7 @@ describe API::API, api: true do ...@@ -6,6 +6,7 @@ describe API::API, api: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
let(:key) { create(:key, user: user) } let(:key) { create(:key, user: user) }
let(:email) { create(:email, user: user) }
describe "GET /users" do describe "GET /users" do
context "when unauthenticated" do context "when unauthenticated" do
...@@ -384,6 +385,87 @@ describe API::API, api: true do ...@@ -384,6 +385,87 @@ describe API::API, api: true do
end end
end end
describe "POST /users/:id/emails" do
before { admin }
it "should not create invalid email" do
post api("/users/#{user.id}/emails", admin), {}
expect(response.status).to eq(400)
expect(json_response['message']).to eq('400 (Bad request) "email" not given')
end
it "should create email" do
email_attrs = attributes_for :email
expect do
post api("/users/#{user.id}/emails", admin), email_attrs
end.to change{ user.emails.count }.by(1)
end
end
describe 'GET /user/:uid/emails' do
before { admin }
context 'when unauthenticated' do
it 'should return authentication error' do
get api("/users/#{user.id}/emails")
expect(response.status).to eq(401)
end
end
context 'when authenticated' do
it 'should return 404 for non-existing user' do
get api('/users/999999/emails', admin)
expect(response.status).to eq(404)
expect(json_response['message']).to eq('404 User Not Found')
end
it 'should return array of emails' do
user.emails << email
user.save
get api("/users/#{user.id}/emails", admin)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.first['email']).to eq(email.email)
end
end
end
describe 'DELETE /user/:uid/emails/:id' do
before { admin }
context 'when unauthenticated' do
it 'should return authentication error' do
delete api("/users/#{user.id}/emails/42")
expect(response.status).to eq(401)
end
end
context 'when authenticated' do
it 'should delete existing email' do
user.emails << email
user.save
expect do
delete api("/users/#{user.id}/emails/#{email.id}", admin)
end.to change { user.emails.count }.by(-1)
expect(response.status).to eq(200)
end
it 'should return 404 error if user not found' do
user.emails << email
user.save
delete api("/users/999999/emails/#{email.id}", admin)
expect(response.status).to eq(404)
expect(json_response['message']).to eq('404 User Not Found')
end
it 'should return 404 error if email not foud' do
delete api("/users/#{user.id}/emails/42", admin)
expect(response.status).to eq(404)
expect(json_response['message']).to eq('404 Email Not Found')
end
end
end
describe "DELETE /users/:id" do describe "DELETE /users/:id" do
before { admin } before { admin }
...@@ -528,6 +610,95 @@ describe API::API, api: true do ...@@ -528,6 +610,95 @@ describe API::API, api: true do
end end
end end
describe "GET /user/emails" do
context "when unauthenticated" do
it "should return authentication error" do
get api("/user/emails")
expect(response.status).to eq(401)
end
end
context "when authenticated" do
it "should return array of emails" do
user.emails << email
user.save
get api("/user/emails", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.first["email"]).to eq(email.email)
end
end
end
describe "GET /user/emails/:id" do
it "should return single email" do
user.emails << email
user.save
get api("/user/emails/#{email.id}", user)
expect(response.status).to eq(200)
expect(json_response["email"]).to eq(email.email)
end
it "should return 404 Not Found within invalid ID" do
get api("/user/emails/42", user)
expect(response.status).to eq(404)
expect(json_response['message']).to eq('404 Not found')
end
it "should return 404 error if admin accesses user's email" do
user.emails << email
user.save
admin
get api("/user/emails/#{email.id}", admin)
expect(response.status).to eq(404)
expect(json_response['message']).to eq('404 Not found')
end
end
describe "POST /user/emails" do
it "should create email" do
email_attrs = attributes_for :email
expect do
post api("/user/emails", user), email_attrs
end.to change{ user.emails.count }.by(1)
expect(response.status).to eq(201)
end
it "should return a 401 error if unauthorized" do
post api("/user/emails"), email: 'some email'
expect(response.status).to eq(401)
end
it "should not create email with invalid email" do
post api("/user/emails", user), {}
expect(response.status).to eq(400)
expect(json_response['message']).to eq('400 (Bad request) "email" not given')
end
end
describe "DELETE /user/emails/:id" do
it "should delete existed email" do
user.emails << email
user.save
expect do
delete api("/user/emails/#{email.id}", user)
end.to change{user.emails.count}.by(-1)
expect(response.status).to eq(200)
end
it "should return success if email ID not found" do
delete api("/user/emails/42", user)
expect(response.status).to eq(200)
end
it "should return 401 error if unauthorized" do
user.emails << email
user.save
delete api("/user/emails/#{email.id}")
expect(response.status).to eq(401)
end
end
describe 'PUT /user/:id/block' do describe 'PUT /user/:id/block' do
before { admin } before { admin }
it 'should block existing user' do it 'should block existing user' do
......
...@@ -210,8 +210,8 @@ end ...@@ -210,8 +210,8 @@ end
# diffs_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/diffs(.:format) projects/merge_requests#diffs # diffs_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/diffs(.:format) projects/merge_requests#diffs
# commits_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/commits(.:format) projects/merge_requests#commits # commits_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/commits(.:format) projects/merge_requests#commits
# automerge_namespace_project_merge_request POST /:namespace_id/:project_id/merge_requests/:id/automerge(.:format) projects/merge_requests#automerge # merge_namespace_project_merge_request POST /:namespace_id/:project_id/merge_requests/:id/merge(.:format) projects/merge_requests#merge
# automerge_check_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/automerge_check(.:format) projects/merge_requests#automerge_check # merge_check_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/merge_check(.:format) projects/merge_requests#merge_check
# ci_status_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/ci_status(.:format) projects/merge_requests#ci_status # ci_status_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/ci_status(.:format) projects/merge_requests#ci_status
# toggle_subscription_namespace_project_merge_request POST /:namespace_id/:project_id/merge_requests/:id/toggle_subscription(.:format) projects/merge_requests#toggle_subscription # toggle_subscription_namespace_project_merge_request POST /:namespace_id/:project_id/merge_requests/:id/toggle_subscription(.:format) projects/merge_requests#toggle_subscription
# branch_from_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/branch_from(.:format) projects/merge_requests#branch_from # branch_from_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/branch_from(.:format) projects/merge_requests#branch_from
...@@ -233,15 +233,15 @@ describe Projects::MergeRequestsController, 'routing' do ...@@ -233,15 +233,15 @@ describe Projects::MergeRequestsController, 'routing' do
expect(get('/gitlab/gitlabhq/merge_requests/1/commits')).to route_to('projects/merge_requests#commits', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') expect(get('/gitlab/gitlabhq/merge_requests/1/commits')).to route_to('projects/merge_requests#commits', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
end end
it 'to #automerge' do it 'to #merge' do
expect(post('/gitlab/gitlabhq/merge_requests/1/automerge')).to route_to( expect(post('/gitlab/gitlabhq/merge_requests/1/merge')).to route_to(
'projects/merge_requests#automerge', 'projects/merge_requests#merge',
namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1' namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1'
) )
end end
it 'to #automerge_check' do it 'to #merge_check' do
expect(get('/gitlab/gitlabhq/merge_requests/1/automerge_check')).to route_to('projects/merge_requests#automerge_check', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') expect(get('/gitlab/gitlabhq/merge_requests/1/merge_check')).to route_to('projects/merge_requests#merge_check', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
end end
it 'to #branch_from' do it 'to #branch_from' do
......
...@@ -10,7 +10,7 @@ describe MergeRequests::CreateService do ...@@ -10,7 +10,7 @@ describe MergeRequests::CreateService do
{ {
title: 'Awesome merge_request', title: 'Awesome merge_request',
description: 'please fix', description: 'please fix',
source_branch: 'stable', source_branch: 'feature',
target_branch: 'master' target_branch: 'master'
} }
end end
......
...@@ -24,11 +24,6 @@ describe MergeRequests::MergeService do ...@@ -24,11 +24,6 @@ describe MergeRequests::MergeService do
it { expect(merge_request).to be_valid } it { expect(merge_request).to be_valid }
it { expect(merge_request).to be_merged } it { expect(merge_request).to be_merged }
it 'should execute hooks with merge action' do
expect(service).to have_received(:execute_hooks).
with(merge_request, 'merge')
end
it 'should send email to user2 about merge of new merge_request' do it 'should send email to user2 about merge of new merge_request' do
email = ActionMailer::Base.deliveries.last email = ActionMailer::Base.deliveries.last
expect(email.to.first).to eq(user2.email) expect(email.to.first).to eq(user2.email)
......
# This is a helper class used by the GitLab Markdown feature spec
#
# Because the feature spec only cares about the output of the Markdown, and the
# test setup and teardown and parsing is fairly expensive, we only want to do it
# once. Unfortunately RSpec will not let you access `let`s in a `before(:all)`
# block, so we fake it by encapsulating all the shared setup in this class.
#
# The class renders `spec/fixtures/markdown.md.erb` using ERB, allowing for
# reference to the factory-created objects.
class MarkdownFeature
include FactoryGirl::Syntax::Methods
def user
@user ||= create(:user)
end
def group
unless @group
@group = create(:group)
@group.add_user(user, Gitlab::Access::DEVELOPER)
end
@group
end
# Direct references ----------------------------------------------------------
def project
@project ||= create(:project)
end
def issue
@issue ||= create(:issue, project: project)
end
def merge_request
@merge_request ||= create(:merge_request, :simple, source_project: project)
end
def snippet
@snippet ||= create(:project_snippet, project: project)
end
def commit
@commit ||= project.commit
end
def commit_range
unless @commit_range
commit2 = project.commit('HEAD~3')
@commit_range = CommitRange.new("#{commit.id}...#{commit2.id}", project)
end
@commit_range
end
def simple_label
@simple_label ||= create(:label, name: 'gfm', project: project)
end
def label
@label ||= create(:label, name: 'awaiting feedback', project: project)
end
# Cross-references -----------------------------------------------------------
def xproject
unless @xproject
namespace = create(:namespace, name: 'cross-reference')
@xproject = create(:project, namespace: namespace)
@xproject.team << [user, :developer]
end
@xproject
end
def xissue
@xissue ||= create(:issue, project: xproject)
end
def xmerge_request
@xmerge_request ||= create(:merge_request, :simple, source_project: xproject)
end
def xsnippet
@xsnippet ||= create(:project_snippet, project: xproject)
end
def xcommit
@xcommit ||= xproject.commit
end
def xcommit_range
unless @xcommit_range
xcommit2 = xproject.commit('HEAD~2')
@xcommit_range = CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject)
end
@xcommit_range
end
def raw_markdown
fixture = Rails.root.join('spec/fixtures/markdown.md.erb')
ERB.new(File.read(fixture)).result(binding)
end
end
# MarkdownMatchers
#
# Custom matchers for our custom HTML::Pipeline filters. These are used to test
# that specific filters are or are not used by our defined pipelines.
#
# Must be included manually.
module MarkdownMatchers
extend RSpec::Matchers::DSL
include Capybara::Node::Matchers
# RelativeLinkFilter
matcher :parse_relative_links do
set_default_markdown_messages
match do |actual|
link = actual.at_css('a:contains("Relative Link")')
image = actual.at_css('img[alt="Relative Image"]')
expect(link['href']).to end_with('master/doc/README.md')
expect(image['src']).to end_with('master/app/assets/images/touch-icon-ipad.png')
end
end
# EmojiFilter
matcher :parse_emoji do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('img.emoji', count: 10)
end
end
# TableOfContentsFilter
matcher :create_header_links do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('h1 a#gitlab-markdown')
expect(actual).to have_selector('h2 a#markdown')
expect(actual).to have_selector('h3 a#autolinkfilter')
end
end
# AutolinkFilter
matcher :create_autolinks do
def have_autolink(link)
have_link(link, href: link)
end
set_default_markdown_messages
match do |actual|
expect(actual).to have_autolink('http://about.gitlab.com/')
expect(actual).to have_autolink('https://google.com/')
expect(actual).to have_autolink('ftp://ftp.us.debian.org/debian/')
expect(actual).to have_autolink('smb://foo/bar/baz')
expect(actual).to have_autolink('irc://irc.freenode.net/git')
expect(actual).to have_autolink('http://localhost:3000')
%w(code a kbd).each do |elem|
expect(body).not_to have_selector("#{elem} a")
end
end
end
# UserReferenceFilter
matcher :reference_users do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-project_member', count: 3)
end
end
# IssueReferenceFilter
matcher :reference_issues do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-issue', count: 3)
end
end
# MergeRequestReferenceFilter
matcher :reference_merge_requests do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-merge_request', count: 3)
expect(actual).to have_selector('em a.gfm-merge_request')
end
end
# SnippetReferenceFilter
matcher :reference_snippets do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-snippet', count: 2)
end
end
# CommitRangeReferenceFilter
matcher :reference_commit_ranges do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-commit_range', count: 2)
end
end
# CommitReferenceFilter
matcher :reference_commits do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-commit', count: 2)
end
end
# LabelReferenceFilter
matcher :reference_labels do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-label', count: 3)
end
end
# TaskListFilter
matcher :parse_task_lists do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('ul.task-list', count: 2)
expect(actual).to have_selector('li.task-list-item', count: 7)
expect(actual).to have_selector('input[checked]', count: 3)
end
end
end
# Monkeypatch the matcher DSL so that we can reduce some noisy duplication for
# setting the failure messages for these matchers
module RSpec::Matchers::DSL::Macros
def set_default_markdown_messages
failure_message do
# expected to parse emoji, but didn't
"expected to #{description}, but didn't"
end
failure_message_when_negated do
# expected not to parse task lists, but did
"expected not to #{description}, but did"
end
end
end
...@@ -9,7 +9,7 @@ def common_mentionable_setup ...@@ -9,7 +9,7 @@ def common_mentionable_setup
let(:author) { subject.author } let(:author) { subject.author }
let(:mentioned_issue) { create(:issue, project: project) } let(:mentioned_issue) { create(:issue, project: project) }
let(:mentioned_mr) { create(:merge_request, :simple, source_project: project) } let!(:mentioned_mr) { create(:merge_request, :simple, source_project: project) }
let(:mentioned_commit) { project.commit } let(:mentioned_commit) { project.commit }
let(:ext_proj) { create(:project, :public) } let(:ext_proj) { create(:project, :public) }
......
...@@ -35,6 +35,7 @@ module TestEnv ...@@ -35,6 +35,7 @@ module TestEnv
clean_test_path clean_test_path
FileUtils.mkdir_p(repos_path) FileUtils.mkdir_p(repos_path)
FileUtils.mkdir_p(backup_path)
# Setup GitLab shell for test instance # Setup GitLab shell for test instance
setup_gitlab_shell setup_gitlab_shell
...@@ -127,6 +128,10 @@ module TestEnv ...@@ -127,6 +128,10 @@ module TestEnv
Gitlab.config.gitlab_shell.repos_path Gitlab.config.gitlab_shell.repos_path
end end
def backup_path
Gitlab.config.backup.path
end
def copy_forked_repo_with_submodules(project) def copy_forked_repo_with_submodules(project)
base_repo_path = File.expand_path(forked_repo_path_bare) base_repo_path = File.expand_path(forked_repo_path_bare)
target_repo_path = File.expand_path(repos_path + "/#{project.namespace.path}/#{project.path}.git") target_repo_path = File.expand_path(repos_path + "/#{project.namespace.path}/#{project.path}.git")
......
...@@ -15,6 +15,12 @@ describe 'gitlab:app namespace rake task' do ...@@ -15,6 +15,12 @@ describe 'gitlab:app namespace rake task' do
Rake.application.invoke_task task_name Rake.application.invoke_task task_name
end end
def reenable_backup_sub_tasks
%w{db repo uploads}.each do |subtask|
Rake::Task["gitlab:backup:#{subtask}:create"].reenable
end
end
describe 'backup_restore' do describe 'backup_restore' do
before do before do
# avoid writing task output to spec progress # avoid writing task output to spec progress
...@@ -60,26 +66,47 @@ describe 'gitlab:app namespace rake task' do ...@@ -60,26 +66,47 @@ describe 'gitlab:app namespace rake task' do
Dir.glob(File.join(Gitlab.config.backup.path, '*_gitlab_backup.tar')) Dir.glob(File.join(Gitlab.config.backup.path, '*_gitlab_backup.tar'))
end end
before :all do def create_backup
# Record the existing backup tars so we don't touch them FileUtils.rm tars_glob
existing_tars = tars_glob
# Redirect STDOUT and run the rake task # Redirect STDOUT and run the rake task
orig_stdout = $stdout orig_stdout = $stdout
$stdout = StringIO.new $stdout = StringIO.new
reenable_backup_sub_tasks
run_rake_task('gitlab:backup:create') run_rake_task('gitlab:backup:create')
reenable_backup_sub_tasks
$stdout = orig_stdout $stdout = orig_stdout
@backup_tar = (tars_glob - existing_tars).first @backup_tar = tars_glob.first
end end
after :all do before do
create_backup
end
after do
FileUtils.rm(@backup_tar) FileUtils.rm(@backup_tar)
end end
it 'should set correct permissions on the tar file' do context 'archive file permissions' do
expect(File.exist?(@backup_tar)).to be_truthy it 'should set correct permissions on the tar file' do
expect(File::Stat.new(@backup_tar).mode.to_s(8)).to eq('100600') expect(File.exist?(@backup_tar)).to be_truthy
expect(File::Stat.new(@backup_tar).mode.to_s(8)).to eq('100600')
end
context 'with custom archive_permissions' do
before do
allow(Gitlab.config.backup).to receive(:archive_permissions).and_return(0651)
# We created a backup in a before(:all) so it got the default permissions.
# We now need to do some work to create a _new_ backup file using our stub.
FileUtils.rm(@backup_tar)
create_backup
end
it 'uses the custom permissions' do
expect(File::Stat.new(@backup_tar).mode.to_s(8)).to eq('100651')
end
end
end end
it 'should set correct permissions on the tar contents' do it 'should set correct permissions on the tar contents' do
...@@ -110,12 +137,9 @@ describe 'gitlab:app namespace rake task' do ...@@ -110,12 +137,9 @@ describe 'gitlab:app namespace rake task' do
before :all do before :all do
@origin_cd = Dir.pwd @origin_cd = Dir.pwd
Rake::Task["gitlab:backup:db:create"].reenable reenable_backup_sub_tasks
Rake::Task["gitlab:backup:repo:create"].reenable
Rake::Task["gitlab:backup:uploads:create"].reenable
# Record the existing backup tars so we don't touch them FileUtils.rm tars_glob
existing_tars = tars_glob
# Redirect STDOUT and run the rake task # Redirect STDOUT and run the rake task
orig_stdout = $stdout orig_stdout = $stdout
...@@ -124,7 +148,7 @@ describe 'gitlab:app namespace rake task' do ...@@ -124,7 +148,7 @@ describe 'gitlab:app namespace rake task' do
run_rake_task('gitlab:backup:create') run_rake_task('gitlab:backup:create')
$stdout = orig_stdout $stdout = orig_stdout
@backup_tar = (tars_glob - existing_tars).first @backup_tar = tars_glob.first
end end
after :all do after :all do
......
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