Merge branch 'master' into merge-if-green

# Conflicts:
#	app/views/projects/merge_requests/widget/_heading.html.haml
#	app/views/projects/merge_requests/widget/open/_accept.html.haml
This commit is contained in:
Douwe Maan 2015-12-08 22:34:03 +01:00
commit 8fb49a4b70
79 changed files with 956 additions and 262 deletions

View file

@ -3,9 +3,12 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.3.0 (unreleased)
- Merge when build succeeds (Zeger-Jan van de Weg)
- Bump gollum-lib to 4.1.0 (Stan Hu)
- Fix broken group avatar upload under "New group" (Stan Hu)
- Update project repositorize size and commit count during import:repos task (Stan Hu)
- Fix API setting of 'public' attribute to false will make a project private (Stan Hu)
- Handle and report SSL errors in Web hook test (Stan Hu)
- Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
- Add rake tasks for git repository maintainance (Zeger-Jan van de Weg)
- Fix 500 error when update group member permission
- Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera)
- Recognize issue/MR/snippet/commit links as references
@ -20,6 +23,8 @@ v 8.3.0 (unreleased)
- Fix 500 error when creating a merge request that removes a submodule
- Run custom Git hooks when branch is created or deleted.
- Fix bug when simultaneously accepting multiple MRs results in MRs that are of "merged" status, but not merged to the target branch
- Add languages page to graphs
- Block LDAP user when they are no longer found in the LDAP server
v 8.2.3
- Fix application settings cache not expiring after changes (Stan Hu)

View file

@ -2,6 +2,8 @@
groups_path: "/api/:version/groups.json"
group_path: "/api/:version/groups/:id.json"
namespaces_path: "/api/:version/namespaces.json"
group_projects_path: "/api/:version/groups/:id/projects.json"
projects_path: "/api/:version/projects.json"
group: (group_id, callback) ->
url = Api.buildUrl(Api.group_path)
@ -44,6 +46,35 @@
).done (namespaces) ->
callback(namespaces)
# Return projects list. Filtered by query
projects: (query, callback) ->
url = Api.buildUrl(Api.projects_path)
$.ajax(
url: url
data:
private_token: gon.api_token
search: query
per_page: 20
dataType: "json"
).done (projects) ->
callback(projects)
# Return group projects list. Filtered by query
groupProjects: (group_id, query, callback) ->
url = Api.buildUrl(Api.group_projects_path)
url = url.replace(':id', group_id)
$.ajax(
url: url
data:
private_token: gon.api_token
search: query
per_page: 20
dataType: "json"
).done (projects) ->
callback(projects)
buildUrl: (url) ->
url = gon.relative_url_root + url if gon.relative_url_root?
return url.replace(':version', gon.api_version)

View file

@ -83,7 +83,7 @@ class Dispatcher
when 'projects:project_members:index'
new ProjectMembers()
new UsersSelect()
when 'groups:new', 'groups:edit', 'admin:groups:edit'
when 'groups:new', 'groups:edit', 'admin:groups:edit', 'admin:groups:new'
new GroupAvatar()
when 'projects:tree:show'
new TreeView()

View file

@ -3,7 +3,7 @@ class @NewCommitForm
@newBranch = form.find('.js-new-branch')
@originalBranch = form.find('.js-original-branch')
@createMergeRequest = form.find('.js-create-merge-request')
@createMergeRequestFormGroup = form.find('.js-create-merge-request-form-group')
@createMergeRequestContainer = form.find('.js-create-merge-request-container')
@renderDestination()
@newBranch.keyup @renderDestination
@ -12,10 +12,10 @@ class @NewCommitForm
different = @newBranch.val() != @originalBranch.val()
if different
@createMergeRequestFormGroup.show()
@createMergeRequestContainer.show()
@createMergeRequest.prop('checked', true) unless @wasDifferent
else
@createMergeRequestFormGroup.hide()
@createMergeRequestContainer.hide()
@createMergeRequest.prop('checked', false)
@wasDifferent = different

View file

@ -0,0 +1,39 @@
class @ProjectSelect
constructor: ->
$('.ajax-project-select').each (i, select) ->
@groupId = $(select).data('group-id')
@includeGroups = $(select).data('include-groups')
placeholder = "Search for project"
placeholder += " or group" if @includeGroups
$(select).select2
placeholder: placeholder
minimumInputLength: 0
query: (query) =>
finalCallback = (projects) ->
data = { results: projects }
query.callback(data)
if @includeGroups
projectsCallback = (projects) ->
groupsCallback = (groups) ->
data = groups.concat(projects)
finalCallback(data)
Api.groups query.term, false, groupsCallback
else
projectsCallback = finalCallback
if @groupId
Api.groupProjects @groupId, query.term, projectsCallback
else
Api.projects query.term, projectsCallback
id: (project) ->
project.web_url
text: (project) ->
project.name_with_namespace || project.name
dropdownCssClass: "ajax-project-dropdown"

View file

@ -7,8 +7,8 @@
/* Common styles for all types */
.bs-callout {
margin: 20px 0;
padding: 20px;
margin: $gl-padding 0;
padding: $gl-padding;
border-left: 3px solid $border-color;
color: $text-color;
background: $background-color;
@ -42,4 +42,3 @@
border-color: #5cA64d;
color: #3c763d;
}

View file

@ -333,7 +333,7 @@ table {
}
.well {
margin-bottom: 0;
margin-bottom: $gl-padding;
}
.search_box {
@ -379,9 +379,8 @@ table {
text-align: center;
margin-top: 5px;
margin-bottom: $gl-padding;
height: 56px;
height: auto;
margin-top: -$gl-padding;
padding-top: $gl-padding;
&.no-bottom {
margin-bottom: 0;
@ -390,6 +389,18 @@ table {
&.no-top {
margin-top: 0;
}
li a {
display: inline-block;
padding-top: $gl-padding;
padding-bottom: 11px;
margin-bottom: -1px;
}
&.bottom-border {
border-bottom: 1px solid $border-color;
height: 57px;
}
}
.center-middle-menu {
@ -437,3 +448,16 @@ table {
.alert, .progress {
margin-bottom: $gl-padding;
}
.new-project-item-select-holder {
display: inline-block;
position: relative;
.new-project-item-select {
position: absolute;
top: 0;
right: 0;
width: 250px !important;
visibility: hidden;
}
}

View file

@ -21,7 +21,6 @@
position: relative;
background: $background-color;
border-bottom: 1px solid $border-color;
text-shadow: 0 1px 1px #fff;
margin: 0;
text-align: left;
padding: 10px $gl-padding;

View file

@ -72,13 +72,6 @@
}
}
ol, ul {
&.styled {
li {
padding: 2px;
}
}
}
/** light list with border-bottom between li **/
ul.bordered-list {

View file

@ -82,9 +82,6 @@
}
.center-top-menu {
height: 45px;
margin-bottom: 30px;
li a {
font-size: 14px;
padding: 19px 10px;

View file

@ -1,9 +1,11 @@
.panel {
margin-bottom: $gl-padding;
.panel-heading {
padding: 10px $gl-padding;
padding: 7px $gl-padding;
line-height: 42px !important;
}
.panel-body {
padding: $gl-padding;

View file

@ -220,6 +220,7 @@ pre {
.monospace {
font-family: $monospace_font;
font-size: 90%;
}
code {

View file

@ -67,9 +67,4 @@
color: #3084bb !important;
}
}
.build-top-menu {
margin-top: 0;
margin-bottom: 2px;
}
}

View file

@ -138,7 +138,7 @@
font-family: $monospace_font;
font-weight: bold;
overflow: hidden;
font-size: 14px;
font-size: 90%;
margin: 0 3px;
}

View file

@ -5,12 +5,6 @@
}
}
.btn-build-token {
float: left;
padding: 6px 20px;
margin-right: 12px;
}
.profile-avatar-form-option {
hr {
margin: 10px 0;

View file

@ -1,9 +1,6 @@
.gitlab-ui-dev-kit {
> h2 {
font-size: 27px;
border-bottom: 1px solid #CCC;
color: #666;
margin: 30px 0;
margin: 35px 0 20px;
font-weight: bold;
}
}

View file

@ -28,7 +28,7 @@ class Projects::ApplicationController < ApplicationController
private
def ci_enabled
def builds_enabled
return render_404 unless @project.builds_enabled?
end

View file

@ -5,7 +5,7 @@ class Projects::GraphsController < Projects::ApplicationController
before_action :require_non_empty_project
before_action :assign_ref_vars
before_action :authorize_download_code!
before_action :ci_enabled, only: :ci
before_action :builds_enabled, only: :ci
def show
respond_to do |format|
@ -34,6 +34,26 @@ class Projects::GraphsController < Projects::ApplicationController
@charts[:build_times] = Ci::Charts::BuildTime.new(ci_project)
end
def languages
@languages = Linguist::Repository.new(@repository.rugged, @repository.rugged.head.target_id).languages
total = @languages.map(&:last).sum
@languages = @languages.map do |language|
name, share = language
color = Digest::SHA256.hexdigest(name)[0...6]
{
value: (share.to_f * 100 / total).round(2),
label: name,
color: "##{color}",
highlight: "##{color}"
}
end
@languages.sort! do |x, y|
y[:value] <=> x[:value]
end
end
private
def fetch_graph

View file

@ -278,6 +278,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request_diff = @merge_request.merge_request_diff
@ci_commit = @merge_request.ci_commit
if @merge_request.locked_long_ago?
@merge_request.unlock_mr
@merge_request.close

View file

@ -8,6 +8,10 @@ module CiStatusHelper
ci_icon_for_status(ci_commit.status)
end
def ci_status_label(ci_commit)
ci_label_for_status(ci_commit.status)
end
def ci_status_color(ci_commit)
case ci_commit.status
when 'success'
@ -23,7 +27,15 @@ module CiStatusHelper
def ci_status_with_icon(status)
content_tag :span, class: "ci-status ci-#{status}" do
ci_icon_for_status(status) + '&nbsp;'.html_safe + status
ci_icon_for_status(status) + '&nbsp;'.html_safe + ci_label_for_status(status)
end
end
def ci_label_for_status(status)
if status == 'success'
'passed'
else
status
end
end
@ -46,7 +58,7 @@ module CiStatusHelper
def render_ci_status(ci_commit)
link_to ci_status_path(ci_commit),
class: "c#{ci_status_color(ci_commit)}",
title: "Build status: #{ci_commit.status}",
title: "Build status: #{ci_status_label(ci_commit)}",
data: { toggle: 'tooltip', placement: 'left' } do
ci_status_icon(ci_commit)
end

View file

@ -4,7 +4,8 @@ module PageLayoutHelper
@page_title.push(*titles.compact) if titles.any?
@page_title.join(" | ")
# Segments are seperated by middot
@page_title.join(" \u00b7 ")
end
def header_title(title = nil, title_url = nil)

View file

@ -48,6 +48,19 @@ module SelectsHelper
select2_tag(id, opts)
end
def project_select_tag(id, opts = {})
opts[:class] ||= ''
opts[:class] << ' ajax-project-select'
unless opts.delete(:scope) == :all
if @group
opts['data-group-id'] = @group.id
end
end
hidden_field_tag(id, opts[:selected], opts)
end
def select2_tag(id, opts = {})
css_class = ''
css_class << 'multiselect ' if opts[:multiple]

View file

@ -207,7 +207,7 @@ module Ci
end
def ci_yaml_file
gl_project.repository.blob_at(sha, '.gitlab-ci.yml').data
@ci_yaml_file ||= gl_project.repository.blob_at(sha, '.gitlab-ci.yml').data
rescue
nil
end

View file

@ -2,7 +2,7 @@
= render 'shared/project_limit'
%ul.center-top-menu
= nav_link(path: ['projects#index', 'root#index']) do
= nav_link(page: [dashboard_projects_path, root_path]) do
= link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
Your Projects
= nav_link(page: starred_dashboard_projects_path) do

View file

@ -4,14 +4,20 @@
- if current_user
= auto_discovery_link_tag(:atom, issues_dashboard_url(format: :atom, private_token: current_user.private_token), title: "#{current_user.name} issues")
.project-issuable-filter
.controls
.pull-left
- if current_user
.hidden-xs.pull-left
= link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do
%i.fa.fa-rss
.append-bottom-20
.pull-right
- if current_user
.hidden-xs.pull-left.prepend-top-20
= link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: '' do
%i.fa.fa-rss
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/issuable/filter', type: :issues
= render 'shared/issues'
.gray-content-block.second-block
List all issues from all projects you have access to.
.prepend-top-default
= render 'shared/issues'

View file

@ -1,6 +1,14 @@
- page_title "Merge Requests"
- header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id)
.append-bottom-20
.project-issuable-filter
.controls
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
= render 'shared/issuable/filter', type: :merge_requests
= render 'shared/merge_requests'
.gray-content-block.second-block
List all merge requests from all projects you have access to.
.prepend-top-default
= render 'shared/merge_requests'

View file

@ -1,12 +1,14 @@
- page_title "Milestones"
- header_title "Milestones", dashboard_milestones_path
- header_title "Milestones", dashboard_milestones_path
.project-issuable-filter
.controls
= render 'shared/new_project_item_select', path: 'milestones/new', label: "New Milestone", include_groups: true
= render 'shared/milestones_filter'
= render 'shared/milestones_filter'
.gray-content-block
.oneline
List all milestones from all projects you have access to.
List all milestones from all projects you have access to.
.milestones
%ul.content-list

View file

@ -4,21 +4,24 @@
- if current_user
= auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues")
.project-issuable-filter
.controls
.pull-left
- if current_user
.hidden-xs.pull-left
= link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do
%i.fa.fa-rss
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/issuable/filter', type: :issues
= render 'shared/issuable/filter', type: :issues
.gray-content-block.second-block
.pull-right
- if current_user
.hidden-xs.pull-left
= link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token) do
%i.fa.fa-rss
%div
Only issues from
%strong #{@group.name}
group are listed here.
- if current_user
To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
Only issues from
%strong #{@group.name}
group are listed here.
- if current_user
To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
.prepend-top-default
= render 'shared/issues'

View file

@ -1,13 +1,18 @@
- page_title "Merge Requests"
- header_title group_title(@group, "Merge Requests", merge_requests_group_path(@group))
= render 'shared/issuable/filter', type: :merge_requests
.project-issuable-filter
.controls
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
= render 'shared/issuable/filter', type: :merge_requests
.gray-content-block.second-block
%div
Only merge requests from
%strong #{@group.name}
group are listed here.
- if current_user
To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
Only merge requests from
%strong #{@group.name}
group are listed here.
- if current_user
To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
.prepend-top-default
= render 'shared/merge_requests'

View file

@ -1,18 +1,22 @@
- page_title "Milestones"
- header_title group_title(@group, "Milestones", group_milestones_path(@group))
= render 'shared/milestones_filter'
.gray-content-block
- if can?(current_user, :admin_milestones, @group)
.pull-right
%span.pull-right.hidden-xs
= link_to new_group_milestone_path(@group), class: "btn btn-new" do
New Milestone
.project-issuable-filter
.controls
- if can?(current_user, :admin_milestones, @group)
.pull-right
%span.pull-right.hidden-xs
= link_to new_group_milestone_path(@group), class: "btn btn-new" do
= icon('plus')
New Milestone
= render 'shared/milestones_filter'
.gray-content-block
Only milestones from
%strong #{@group.name}
group are listed here.
.oneline
Only milestones from
%strong #{@group.name}
group are listed here.
.milestones
%ul.content-list
- if @milestones.blank?

View file

@ -31,11 +31,9 @@
%h2#blocks Blocks
%h3
%h4
%code .gray-content-block
.gray-content-block.middle-block
%h4 Normal block inside content
= lorem
@ -45,9 +43,28 @@
= lorem
%h4
%code .cover-block
%br
.cover-block
.avatar-holder
= image_tag avatar_icon('admin@example.com', 90), class: "avatar s90", alt: ''
.cover-title
John Smith
.cover-desc
= lorem
.cover-controls
= link_to '#', class: 'btn btn-gray' do
= icon('pencil')
&nbsp;
= link_to '#', class: 'btn btn-gray' do
= icon('rss')
%h2#lists Lists
%h3
%h4
%code .content-list
%ul.content-list
%li
@ -57,7 +74,7 @@
%li
One item
%h3
%h4
%code .well-list
%ul.well-list
%li
@ -67,7 +84,7 @@
%li
One item
%h3
%h4
%code .panel .well-list
.panel.panel-default
@ -80,7 +97,7 @@
%li
One item
%h3
%h4
%code .bordered-list
%ul.bordered-list
%li
@ -121,7 +138,7 @@
%h2#navs Navigation
%h3
%h4
%code .center-top-menu
.example
%ul.center-top-menu
@ -130,7 +147,7 @@
%li
%a Closed
%h3
%h4
%code .btn-group.btn-group-next
.example
%div.btn-group.btn-group-next
@ -138,7 +155,7 @@
%a.btn Closed
%h3
%h4
%code .nav.nav-tabs
.example
%ul.nav.nav-tabs
@ -204,7 +221,7 @@
%h2#forms Forms
%h3
%h4
%code form.horizontal-form
%form.form-horizontal
@ -226,7 +243,7 @@
.col-sm-offset-2.col-sm-10
%button.btn.btn-default{:type => "submit"} Sign in
%h3
%h4
%code form
%form
@ -243,7 +260,7 @@
%button.btn.btn-default{:type => "submit"} Sign in
%h2#file File
%h3
%h4
%code .file-holder
- blob = Snippet.new(content: "Wow\nSuch\nFile")
@ -254,13 +271,12 @@
.file-actions
.btn-group
%a.btn Edit
%a.btn Remove
%a.btn.btn-danger Remove
.file-contenta.code
= render 'shared/file_highlight', blob: blob
%h2#markdown Markdown
%h3
%h4
%code .md or .wiki and others
Markdown rendering has a bit different css and presented in next UI elements:

View file

@ -26,11 +26,11 @@
- else
%span You don`t have one yet. Click generate to fix it.
.form-actions
- if current_user.private_token
= f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default btn-build-token"
- else
= f.submit 'Generate', class: "btn btn-default btn-build-token"
.form-actions
- if current_user.private_token
= f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default"
- else
= f.submit 'Generate', class: "btn btn-default"
- unless current_user.ldap_user?
.panel.panel-default

View file

@ -9,7 +9,7 @@
$('#key_key').on('focusout', function(){
var title = $('#key_title'),
val = $('#key_key').val(),
comment = val.match(/^\S+ \S+ (.+)$/);
comment = val.match(/^\S+ \S+ (.+)\n?$/);
if( comment && comment.length > 1 && title.val() == '' ){
$('#key_title').val( comment[1] );

View file

@ -3,7 +3,7 @@
- if ci_commit
= link_to ci_status_path(ci_commit), class: "ci-status ci-#{ci_commit.status}" do
= ci_status_icon(ci_commit)
= ci_commit.status
= ci_status_label(ci_commit)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"

View file

@ -3,17 +3,17 @@
%div
= link_to namespace_project_tree_path(@project.namespace, @project, branch.name) do
%strong.str-truncated= branch.name
&nbsp;
- if branch.name == @repository.root_ref
%span.label.label-primary default
- elsif @repository.merged_to_root_ref? branch.name
%span.label.label-info.has_tooltip(title="Merged into #{@repository.root_ref}")
merged
&nbsp;
- if branch.name == @repository.root_ref
%span.label.label-primary default
- elsif @repository.merged_to_root_ref? branch.name
%span.label.label-info.has_tooltip(title="Merged into #{@repository.root_ref}")
merged
- if @project.protected_branch? branch.name
%span.label.label-success
%i.fa.fa-lock
protected
- if @project.protected_branch? branch.name
%span.label.label-success
%i.fa.fa-lock
protected
.controls.hidden-xs
- if create_mr_button?(@repository.root_ref, branch.name)
= link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-grouped btn-xs' do
@ -26,7 +26,7 @@
Compare
- if can_remove_branch?(@project, branch.name)
= link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row', method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?" }, remote: true do
= link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has_tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do
= icon("trash-o")
- if commit

View file

@ -3,10 +3,10 @@
.project-issuable-filter
.controls
- if @ci_project && current_user && can?(current_user, :manage_builds, @project)
- if @ci_project && can?(current_user, :manage_builds, @project)
.pull-left.hidden-xs
- if @all_builds.running_or_pending.any?
= link_to 'Cancel all', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
= link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
%ul.center-top-menu
%li{class: ('active' if @scope.nil?)}
@ -50,4 +50,3 @@
= render 'projects/commit_statuses/commit_status', commit_status: build, commit_sha: true, stage: true, allow_retry: true
= paginate @builds, theme: 'gitlab'

View file

@ -1,17 +1,16 @@
- page_title "#{@build.name} (#{@build.id})", "Builds"
- page_title "#{@build.name} (##{@build.id})", "Builds"
= render "header_title"
.build-page
.gray-content-block
.gray-content-block.top-block
Build ##{@build.id} for commit
%strong.monospace
= link_to @build.commit.short_sha, ci_status_path(@build.commit)
%strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit)
from
= link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref)
#up-build-trace
- if @commit.matrix_for_ref?(@build.ref)
%ul.center-top-menu.build-top-menu
%ul.center-top-menu.no-top.no-bottom
- @commit.latest_builds_for_ref(@build.ref).each do |build|
%li{class: ('active' if build == @build) }
= link_to namespace_project_build_path(@project.namespace, @project, build) do
@ -22,7 +21,6 @@
- else
= build.id
- if @build.retried?
%li.active
%a
@ -31,7 +29,7 @@
%i.fa.fa-warning
This build was retried.
.gray-content-block.second-block
.gray-content-block.middle-block
.build-head
.clearfix
= ci_status_with_icon(@build.status)
@ -140,7 +138,7 @@
%h4.title
Commit
.pull-right
%small
%small
= link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace"
%p
%span.attr-name Branch:
@ -162,7 +160,7 @@
- if @builds.present?
.build-widget
%h4.title #{pluralize(@builds.count(:id), "other build")} for
%h4.title #{pluralize(@builds.count(:id), "other build")} for
= succeed ":" do
= link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace"
%table.table.builds

View file

@ -20,7 +20,8 @@
%p
%span.light Commit
= link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
= link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace", data: { clipboard_text: @commit.id }
= clipboard_button
.commit-info-row
%span.light Authored by
%strong
@ -44,7 +45,7 @@
= link_to ci_status_path(@ci_commit), class: "ci-status ci-#{@ci_commit.status}" do
= ci_status_icon(@ci_commit)
build:
= @ci_commit.status
= ci_status_label(@ci_commit)
.commit-info-row.branches
%i.fa.fa-spinner.fa-spin

View file

@ -1,13 +1,18 @@
%tr.commit_status
%td.status
= ci_status_with_icon(commit_status.status)
- if commit_status.target_url
= link_to commit_status.target_url, class: "ci-status ci-#{commit_status.status}" do
= ci_icon_for_status(commit_status.status)
= commit_status.status
- else
= ci_status_with_icon(commit_status.status)
%td.commit_status-link
- if commit_status.target_url
= link_to commit_status.target_url do
%strong Build ##{commit_status.id}
%strong ##{commit_status.id}
- else
%strong Build ##{commit_status.id}
%strong ##{commit_status.id}
- if commit_status.show_warning?
%i.fa.fa-warning.text-warning

View file

@ -3,6 +3,8 @@
= link_to 'Contributors', namespace_project_graph_path
= nav_link(action: :commits) do
= link_to 'Commits', commits_namespace_project_graph_path
= nav_link(action: :languages) do
= link_to 'Languages', languages_namespace_project_graph_path
- if @project.builds_enabled?
= nav_link(action: :ci) do
= link_to ci_namespace_project_graph_path do

View file

@ -0,0 +1,32 @@
- page_title "Languages", "Graphs"
= render "header_title"
= render 'head'
.gray-content-block.append-bottom-default
.oneline
Programming languages used in this repository
.row
.col-md-8
%canvas#languages-chart{ height: 400 }
.col-md-4
%ul.bordered-list
- @languages.each do |language|
%li
%span{ style: "color: #{language[:color]}" }
= icon('circle')
&nbsp;
= language[:label]
.pull-right
= language[:value]
\%
:javascript
var data = #{@languages.to_json};
var ctx = $("#languages-chart").get(0).getContext("2d");
var options = {
scaleOverlay: true,
responsive: true,
maintainAspectRatio: false
}
var myPieChart = new Chart(ctx).Pie(data, options);

View file

@ -12,15 +12,12 @@
.col-md-9
.votes-holder.pull-right
#votes= render 'votes/votes_block', votable: @issue
.participants
%span= pluralize(@participants.count, 'participant')
- @participants.each do |participant|
= link_to_member(@project, participant, name: false, size: 24)
= render "shared/issuable/participants"
.col-md-3
.input-group.cross-project-reference
%span#cross-project-reference.slead.has_tooltip{title: 'Cross-project reference'}
= cross_project_reference(@project, @issue)
= clipboard_button(clipboard_target: '#cross-project-reference')
= clipboard_button(clipboard_target: 'span#cross-project-reference')
.row
%section.col-md-9

View file

@ -12,16 +12,16 @@
.col-md-9
.votes-holder.pull-right
#votes= render 'votes/votes_block', votable: @merge_request
= render "projects/merge_requests/show/participants"
= render "shared/issuable/participants"
.col-md-3
.input-group.cross-project-reference
%span#cross-project-reference.slead.has_tooltip{title: 'Cross-project reference'}
= cross_project_reference(@project, @merge_request)
= clipboard_button(clipboard_target: '#cross-project-reference')
= clipboard_button(clipboard_target: 'span#cross-project-reference')
.row
%section.col-md-9
= render "projects/notes/notes_with_form"
.voting_notes#notes= render "projects/notes/notes_with_form"
%aside.col-md-3
.issuable-affix
.context

View file

@ -20,11 +20,11 @@
.mr-compare.merge-request
%ul.merge-request-tabs.center-top-menu.no-top.no-bottom
%li.commits-tab
= link_to url_for(params), data: {target: '#commits', action: 'commits', toggle: 'tab'} do
= link_to url_for(params), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
Commits
%span.badge= @commits.size
%li.diffs-tab.active
= link_to url_for(params), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
= link_to url_for(params), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
Changes
%span.badge= @diffs.size

View file

@ -44,15 +44,15 @@
- if @commits.present?
%ul.merge-request-tabs.center-top-menu.no-top.no-bottom
%li.notes-tab
= link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#notes', action: 'notes', toggle: 'tab'} do
= link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do
Discussion
%span.badge= @merge_request.mr_and_commit_notes.user.count
%li.commits-tab
= link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#commits', action: 'commits', toggle: 'tab'} do
= link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
Commits
%span.badge= @commits.size
%li.diffs-tab
= link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
= link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
Changes
%span.badge= @merge_request.diffs.size

View file

@ -1,12 +1,12 @@
- if @merge_request.ci_commit
- status = @merge_request.ci_commit.status
- if @ci_commit
- status = @ci_commit.status
.mr-widget-heading
.ci_widget{class: "ci-#{status}"}
= ci_status_icon(@merge_request.ci_commit)
= ci_status_icon(@ci_commit)
%span CI build #{status}
for #{@merge_request.last_commit_short_sha}.
%span.ci-coverage
= link_to "View build details", ci_status_path(@merge_request.ci_commit)
= link_to "View build details", ci_status_path(@ci_commit)
- elsif @merge_request.has_ci?
- # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX

View file

@ -1,11 +1,11 @@
- status_class = @merge_request.ci_commit ? "ci-#{@merge_request.ci_commit.status}" : nil
- status_class = @ci_commit ? " ci-#{@ci_commit.status}" : nil
= 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
.accept-merge-holder.clearfix.js-toggle-container
.clearfix
.accept-action
- if @merge_request.ci_commit && @merge_request.ci_commit.active?
- if @ci_commit && @ci_commit.active?
%span.btn-group
= link_to "#", class: "btn btn-create merge_when_build_succeeds" do
Merge When Build Succeeds

View file

@ -1,7 +1,6 @@
%h4
Set by #{link_to_member(@project, @merge_request.merge_user, avatar: true)}
to be merged automatically when
#{link_to "the build", ci_status_path(@merge_request.ci_commit)} succeeds.
to be merged automatically when the build succeeds.
%div
- should_remove_source_branch = @merge_request.merge_params["should_remove_source_branch"].present?
%p

View file

@ -1,15 +1,18 @@
- page_title "Milestones"
= render "header_title"
= render 'shared/milestones_filter'
.gray-content-block
.pull-right
- if can? current_user, :admin_milestone, @project
.project-issuable-filter
.controls
- if can?(current_user, :admin_milestone, @project)
= link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "pull-right btn btn-new", title: "New Milestone" do
%i.fa.fa-plus
New Milestone
.oneline
Milestone allows you to group issues and set due date for it
= render 'shared/milestones_filter'
.gray-content-block
Milestone allows you to group issues and set due date for it
.milestones
%ul.content-list

View file

@ -1,3 +1,6 @@
.append-bottom-20
= render partial: 'shared/ref_switcher', locals: {destination: 'graph'}
.pull-right.visible-lg.light You can move around the graph by using the arrow keys.
.gray-content-block.top-block.append-bottom-default
.tree-ref-holder
= render partial: 'shared/ref_switcher', locals: {destination: 'graph'}
.oneline
You can move around the graph by using the arrow keys.

View file

@ -4,10 +4,11 @@
group members
%small
(#{members.count})
.pull-right
= link_to group_group_members_path(@group), class: 'btn' do
= icon('pencil-square-o')
Edit group members
- if can?(current_user, :admin_group_member, @group)
.pull-right
= link_to group_group_members_path(@group), class: 'btn' do
= icon('pencil-square-o')
Manage group members
%ul.content-list
- members.each do |member|
= render 'groups/group_members/group_member', member: member, show_controls: false

View file

@ -3,7 +3,7 @@
%p.light Keep stable branches secure and force developers to use Merge Requests
%hr
.well.append-bottom-20
.well
%p Protected branches are designed to
%ul
%li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"}

View file

@ -11,11 +11,17 @@
= strip_gpg_signature(tag.message)
.controls
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn-grouped btn' do
= icon("pencil")
- if can? current_user, :download_code, @project
- if can?(current_user, :download_code, @project)
= render 'projects/tags/download', ref: tag.name, project: @project
- if can?(current_user, :push_code, @project)
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn-grouped btn has_tooltip', title: "Edit release notes" do
= icon("pencil")
- if can?(current_user, :admin_project, @project)
= link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has_tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do
= icon("trash-o")
- if commit
= render 'projects/branches/commit', commit: commit, project: @project
- else

View file

@ -5,17 +5,17 @@
.gray-content-block
.pull-right
- if can?(current_user, :push_code, @project)
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn', title: 'Edit release notes' do
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn has_tooltip', title: 'Edit release notes' do
= icon("pencil")
= link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped', title: 'Browse source code' do
= link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has_tooltip', title: 'Browse files' do
= icon('files-o')
= link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped', title: 'Browse commits' do
= link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has_tooltip', title: 'Browse commits' do
= icon('history')
- if can? current_user, :download_code, @project
= render 'projects/tags/download', ref: @tag.name, project: @project
- if can?(current_user, :admin_project, @project)
.pull-right
= link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped', method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
= link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has_tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
%i.fa.fa-trash-o
.title
%strong= @tag.name

View file

@ -14,7 +14,7 @@
- if allowed_tree_edit?
%li
%span.dropdown
%a.dropdown-toggle.btn.add-to-tree{href: '#', "data-toggle" => "dropdown"}
%a.dropdown-toggle.btn.btn-sm.add-to-tree{href: '#', "data-toggle" => "dropdown"}
= icon('plus')
%ul.dropdown-menu
%li

View file

@ -6,12 +6,11 @@
.col-sm-10
= text_field_tag 'new_branch', @new_branch || @ref, required: true, class: "form-control js-new-branch"
.form-group.js-create-merge-request-form-group
.col-sm-offset-2.col-sm-10
.checkbox
- nonce = SecureRandom.hex
= label_tag "create_merge_request-#{nonce}" do
= check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
Start a <strong>new merge request</strong> with this commit
.js-create-merge-request-container
.checkbox
- nonce = SecureRandom.hex
= label_tag "create_merge_request-#{nonce}" do
= check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
Start a <strong>new merge request</strong> with these changes
= hidden_field_tag 'original_branch', @ref, class: 'js-original-branch'

View file

@ -0,0 +1,20 @@
- if @projects.any?
.prepend-left-10.new-project-item-select-holder
= project_select_tag :project_path, class: "new-project-item-select", data: { include_groups: local_assigns[:include_groups] }
%a.btn.btn-new.new-project-item-select-button
= icon('plus')
= local_assigns[:label]
%b.caret
:javascript
$('.new-project-item-select-button').on('click', function() {
$('.new-project-item-select').select2('open');
});
var relativePath = '#{local_assigns[:path]}';
$('.new-project-item-select').on('click', function() {
window.location = $(this).val() + '/' + relativePath;
});
new ProjectSelect()

View file

@ -1,4 +1,5 @@
.participants
%span #{@participants.count} participants
%span
= pluralize @participants.count, "participant"
- @participants.each do |participant|
= link_to_member(@project, participant, name: false, size: 24)

View file

@ -73,7 +73,7 @@
.user-calendar-activities
%ul.center-middle-menu
%ul.center-top-menu.no-top.no-bottom.bottom-border
%li.active
= link_to "#activity", 'data-toggle' => 'tab' do
Activity

54
bin/parallel-rsync-repos Executable file
View file

@ -0,0 +1,54 @@
#!/usr/bin/env bash
# this script should run as the 'git' user, not root, because 'root' should not
# own intermediate directories created by rsync.
#
# Example invocation:
# find /var/opt/gitlab/git-data/repositories -maxdepth 2 | \
# parallel-rsync-repos transfer-success.log /var/opt/gitlab/git-data/repositories /mnt/gitlab/repositories
#
# You can also rsync to a remote destination.
#
# parallel-rsync-repos transfer-success.log /var/opt/gitlab/git-data/repositories user@host:/mnt/gitlab/repositories
#
# If you need to pass extra options to rsync, set the RSYNC variable
#
# env RSYNC='rsync --rsh="foo bar"' parallel-rsync-repos transfer-success.log /src dest
#
LOGFILE=$1
SRC=$2
DEST=$3
if [ -z "$LOGFILE" ] || [ -z "$SRC" ] || [ -z "$DEST" ] ; then
echo "Usage: $0 LOGFILE SRC DEST"
exit 1
fi
if [ -z "$JOBS" ] ; then
JOBS=10
fi
if [ -z "$RSYNC" ] ; then
RSYNC=rsync
fi
if ! cd $SRC ; then
echo "cd $SRC failed"
exit 1
fi
rsyncjob() {
relative_dir="./${1#$SRC}"
if ! $RSYNC --delete --relative -a "$relative_dir" "$DEST" ; then
echo "rsync $1 failed"
return 1
fi
echo "$1" >> $LOGFILE
}
export LOGFILE SRC DEST RSYNC
export -f rsyncjob
parallel -j$JOBS --progress rsyncjob

View file

@ -16,7 +16,7 @@ OmniAuth.config.allowed_request_methods = [:post]
#In case of auto sign-in, the GET method is used (users don't get to click on a button)
OmniAuth.config.allowed_request_methods << :get if Gitlab.config.omniauth.auto_sign_in_with_provider.present?
OmniAuth.config.before_request_phase do |env|
OmniAuth::RequestForgeryProtection.new(env).call
OmniAuth::RequestForgeryProtection.call(env)
end
if Gitlab.config.omniauth.enabled

View file

@ -500,6 +500,7 @@ Rails.application.routes.draw do
member do
get :commits
get :ci
get :languages
end
end

View file

@ -1,6 +1,6 @@
# Groups
## List project groups
## List groups
Get a list of groups. (As user: my groups, as admin: all groups)
@ -21,6 +21,70 @@ GET /groups
You can search for groups by name or path, see below.
## List a group's projects
Get a list of projects in this group.
```
GET /groups/:id/projects
```
Parameters:
- `archived` (optional) - if passed, limit by archived status
- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
- `search` (optional) - Return list of authorized projects according to a search criteria
- `ci_enabled_first` - Return projects ordered by ci_enabled flag. Projects with enabled GitLab CI go first
```json
[
{
"id": 4,
"description": null,
"default_branch": "master",
"public": false,
"visibility_level": 0,
"ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git",
"http_url_to_repo": "http://example.com/diaspora/diaspora-client.git",
"web_url": "http://example.com/diaspora/diaspora-client",
"tag_list": [
"example",
"disapora client"
],
"owner": {
"id": 3,
"name": "Diaspora",
"created_at": "2013-09-30T13: 46: 02Z"
},
"name": "Diaspora Client",
"name_with_namespace": "Diaspora / Diaspora Client",
"path": "diaspora-client",
"path_with_namespace": "diaspora/diaspora-client",
"issues_enabled": true,
"merge_requests_enabled": true,
"builds_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
"created_at": "2013-09-30T13: 46: 02Z",
"last_activity_at": "2013-09-30T13: 46: 02Z",
"creator_id": 3,
"namespace": {
"created_at": "2013-09-30T13: 46: 02Z",
"description": "",
"id": 3,
"name": "Diaspora",
"owner_id": 1,
"path": "diaspora",
"updated_at": "2013-09-30T13: 46: 02Z"
},
"archived": false,
"avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png"
}
]
```
## Details of a group
Get all details of a group.
@ -186,7 +250,7 @@ To get more (up to 100), pass the following as an argument to the API call:
/groups?per_page=100
```
And to switch pages add:
And to switch pages add:
```
/groups?per_page=100&page=2
```
```

View file

@ -13,6 +13,12 @@ An LDAP user who is allowed to change their email on the LDAP server can [take o
We recommend against using GitLab LDAP integration if your LDAP users are allowed to change their 'mail', 'email' or 'userPrincipalName' attribute on the LDAP server.
If a user is deleted from the LDAP server, they will be blocked in GitLab as well.
Users will be immediately blocked from logging in. However, there is an LDAP check
cache time of one hour. The means users that are already logged in or are using Git
over SSH will still be able to access GitLab for up to one hour. Manually block
the user in the GitLab Admin area to immediately block all access.
## Configuring GitLab for LDAP integration
To enable GitLab LDAP integration you need to add your LDAP server settings in `/etc/gitlab/gitlab.rb` or `/home/git/gitlab/config/gitlab.yml`.
@ -192,4 +198,4 @@ Not supported by GitLab's configuration options.
When setting `method: ssl`, the underlying authentication method used by
`omniauth-ldap` is `simple_tls`. This method establishes TLS encryption with
the LDAP server before any LDAP-protocol data is exchanged but no validation of
the LDAP server's SSL certificate is performed.
the LDAP server's SSL certificate is performed.

View file

@ -0,0 +1,180 @@
# Moving repositories managed by GitLab
Sometimes you need to move all repositories managed by GitLab to
another filesystem or another server. In this document we will look
at some of the ways you can copy all your repositories from
`/var/opt/gitlab/git-data/repositories` to `/mnt/gitlab/repositories`.
We will look at three scenarios: the target directory is empty, the
target directory contains an outdated copy of the repositories, and
how to deal with thousands of repositories.
**Each of the approaches we list can/will overwrite data in the
target directory `/mnt/gitlab/repositories`. Do not mix up the
source and the target.**
## Target directory is empty: use a tar pipe
If the target directory `/mnt/gitlab/repositories` is empty the
simplest thing to do is to use a tar pipe. This method has low
overhead and tar is almost always already installed on your system.
However, it is not possible to resume an interrupted tar pipe: if
that happens then all data must be copied again.
```
# As the git user
tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\
tar -C /mnt/gitlab/repositories -xf -
```
If you want to see progress, replace `-xf` with `-xvf`.
### Tar pipe to another server
You can also use a tar pipe to copy data to another server. If your
'git' user has SSH access to the newserver as 'git@newserver', you
can pipe the data through SSH.
```
# As the git user
tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\
ssh git@newserver tar -C /mnt/gitlab/repositories -xf -
```
If you want to compress the data before it goes over the network
(which will cost you CPU cycles) you can replace `ssh` with `ssh -C`.
## The target directory contains an outdated copy of the repositories: use rsync
If the target directory already contains a partial / outdated copy
of the repositories it may be wasteful to copy all the data again
with tar. In this scenario it is better to use rsync. This utility
is either already installed on your system or easily installable
via apt, yum etc.
```
# As the 'git' user
rsync -a --delete /var/opt/gitlab/git-data/repositories/. \
/mnt/gitlab/repositories
```
The `/.` in the command above is very important, without it you can
easily get the wrong directory structure in the target directory.
If you want to see progress, replace `-a` with `-av`.
### Single rsync to another server
If the 'git' user on your source system has SSH access to the target
server you can send the repositories over the network with rsync.
```
# As the 'git' user
rsync -a --delete /var/opt/gitlab/git-data/repositories/. \
git@newserver:/mnt/gitlab/repositories
```
## Thousands of Git repositories: use one rsync per repository
Every time you start an rsync job it has to inspect all files in
the source directory, all files in the target directory, and then
decide what files to copy or not. If the source or target directory
has many contents this startup phase of rsync can become a burden
for your GitLab server. In cases like this you can make rsync's
life easier by dividing its work in smaller pieces, and sync one
repository at a time.
In addition to rsync we will use [GNU
Parallel](http://www.gnu.org/software/parallel/). This utility is
not included in GitLab so you need to install it yourself with apt
or yum. Also note that the GitLab scripts we used below were added
in GitLab 8.1.
** This process does not clean up repositories at the target location that no
longer exist at the source. ** If you start using your GitLab instance with
`/mnt/gitlab/repositories`, you need to run `gitlab-rake gitlab:cleanup:repos`
after switching to the new repository storage directory.
### Parallel rsync for all repositories known to GitLab
This will sync repositories with 10 rsync processes at a time. We keep
track of progress so that the transfer can be restarted if necessary.
First we create a new directory, owned by 'git', to hold transfer
logs. We assume the directory is empty before we start the transfer
procedure, and that we are the only ones writing files in it.
```
# Omnibus
sudo mkdir /var/opt/gitlab/transfer-logs
sudo chown git:git /var/opt/gitlab/transfer-logs
# Source
sudo -u git -H mkdir /home/git/transfer-logs
```
We seed the process with a list of the directories we want to copy.
```
# Omnibus
sudo -u git sh -c 'gitlab-rake gitlab:list_repos > /var/opt/gitlab/transfer-logs/all-repos-$(date +%s).txt'
# Source
cd /home/git/gitlab
sudo -u git -H sh -c 'bundle exec rake gitlab:list_repos > /home/git/transfer-logs/all-repos-$(date +%s).txt'
```
Now we can start the transfer. The command below is idempotent, and
the number of jobs done by GNU Parallel should converge to zero. If it
does not some repositories listed in all-repos-1234.txt may have been
deleted/renamed before they could be copied.
```
# Omnibus
sudo -u git sh -c '
cat /var/opt/gitlab/transfer-logs/* | sort | uniq -u |\
/usr/bin/env JOBS=10 \
/opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \
/var/opt/gitlab/transfer-logs/succes-$(date +%s).log \
/var/opt/gitlab/git-data/repositories \
/mnt/gitlab/repositories
'
# Source
cd /home/git/gitlab
sudo -u git -H sh -c '
cat /home/git/transfer-logs/* | sort | uniq -u |\
/usr/bin/env JOBS=10 \
bin/parallel-rsync-repos \
/home/git/transfer-logs/succes-$(date +%s).log \
/home/git/repositories \
/mnt/gitlab/repositories
`
```
### Parallel rsync only for repositories with recent activity
Suppose you have already done one sync that started after 2015-10-1 12:00 UTC.
Then you might only want to sync repositories that were changed via GitLab
_after_ that time. You can use the 'SINCE' variable to tell 'rake
gitlab:list_repos' to only print repositories with recent activity.
```
# Omnibus
sudo gitlab-rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\
sudo -u git \
/usr/bin/env JOBS=10 \
/opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \
succes-$(date +%s).log \
/var/opt/gitlab/git-data/repositories \
/mnt/gitlab/repositories
# Source
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\
sudo -u git -H \
/usr/bin/env JOBS=10 \
bin/parallel-rsync-repos \
succes-$(date +%s).log \
/home/git/repositories \
/mnt/gitlab/repositories
```

View file

@ -0,0 +1,30 @@
# Listing repository directories
You can print a list of all Git repositories on disk managed by
GitLab with the following command:
```
# Omnibus
sudo gitlab-rake gitlab:list_repos
# Source
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:list_repos RAILS_ENV=production
```
If you only want to list projects with recent activity you can pass
a date with the 'SINCE' environment variable. The time you specify
is parsed by the Rails [TimeZone#parse
function](http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html#method-i-parse).
```
# Omnibus
sudo gitlab-rake gitlab:list_repos SINCE='Sep 1 2015'
# Source
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:list_repos RAILS_ENV=production SINCE='Sep 1 2015'
```
Note that the projects listed are NOT sorted by activity; they use
the default ordering of the GitLab Rails application.

View file

@ -18,3 +18,8 @@ Feature: Project Graph
Given project "Shop" has CI enabled
When I visit project "Shop" CI graph page
Then page should have CI graphs
@javascript
Scenario: I should see project languages graphs
When I visit project "Shop" languages graph page
Then page should have languages graphs

View file

@ -14,6 +14,15 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps
visit commits_namespace_project_graph_path(project.namespace, project, "master")
end
step 'I visit project "Shop" languages graph page' do
visit languages_namespace_project_graph_path(project.namespace, project, "master")
end
step 'page should have languages graphs' do
expect(page).to have_content "Ruby 66.63 %"
expect(page).to have_content "JavaScript 22.96 %"
end
step 'page should have commits graphs' do
expect(page).to have_content "Commit statistics for master"
expect(page).to have_content "Commits per day of month"

View file

@ -65,6 +65,18 @@ module API
DestroyGroupService.new(group, current_user).execute
end
# Get a list of projects in this group
#
# Example Request:
# GET /groups/:id/projects
get ":id/projects" do
group = find_group(params[:id])
projects = group.projects
projects = filter_projects(projects)
projects = paginate projects
present projects, with: Entities::Project
end
# Transfer a project to the Group namespace
#
# Parameters:

View file

@ -37,13 +37,15 @@ module Gitlab
# Block user in GitLab if he/she was blocked in AD
if Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter)
user.block unless user.blocked?
user.block
false
else
user.activate if user.blocked? && !ldap_config.block_auto_created_users
true
end
else
# Block the user if they no longer exist in LDAP/AD
user.block
false
end
rescue

View file

@ -1,66 +1,21 @@
# Protects OmniAuth request phase against CSRF.
module OmniAuth
# Based on ActionController::RequestForgeryProtection.
class RequestForgeryProtection
def initialize(env)
@env = env
end
module RequestForgeryProtection
class Controller < ActionController::Base
protect_from_forgery with: :exception
def request
@request ||= ActionDispatch::Request.new(@env)
end
def session
request.session
end
def reset_session
request.reset_session
end
def params
request.params
end
def call
verify_authenticity_token
end
def verify_authenticity_token
if !verified_request?
Rails.logger.warn "Can't verify CSRF token authenticity" if Rails.logger
handle_unverified_request
def index
head :ok
end
end
private
def protect_against_forgery?
ApplicationController.allow_forgery_protection
def self.app
@app ||= Controller.action(:index)
end
def request_forgery_protection_token
ApplicationController.request_forgery_protection_token
end
def forgery_protection_strategy
ApplicationController.forgery_protection_strategy
end
def verified_request?
!protect_against_forgery? || request.get? || request.head? ||
form_authenticity_token == params[request_forgery_protection_token] ||
form_authenticity_token == request.headers['X-CSRF-Token']
end
def handle_unverified_request
forgery_protection_strategy.new(self).handle_unverified_request
end
# Sets the token value for the current session.
def form_authenticity_token
session[:_csrf_token] ||= SecureRandom.base64(32)
def self.call(env)
app.call(env)
end
end
end

55
lib/tasks/gitlab/git.rake Normal file
View file

@ -0,0 +1,55 @@
namespace :gitlab do
namespace :git do
desc "GitLab | Git | Repack"
task repack: :environment do
failures = perform_git_cmd(%W(git repack -a --quiet), "Repacking repo")
if failures.empty?
puts "Done".green
else
output_failures(failures)
end
end
desc "GitLab | Git | Run garbage collection on all repos"
task gc: :environment do
failures = perform_git_cmd(%W(git gc --auto --quiet), "Garbage Collecting")
if failures.empty?
puts "Done".green
else
output_failures(failures)
end
end
desc "GitLab | Git | Prune all repos"
task prune: :environment do
failures = perform_git_cmd(%W(git prune), "Git Prune")
if failures.empty?
puts "Done".green
else
output_failures(failures)
end
end
def perform_git_cmd(cmd, message)
puts "Starting #{message} on all repositories"
failures = []
all_repos do |repo|
if system(*cmd, chdir: repo)
puts "Performed #{message} at #{repo}"
else
failures << repo
end
end
failures
end
def output_failures(failures)
puts "The following repositories reported errors:".red
failures.each { |f| puts "- #{f}" }
end
end
end

View file

@ -64,6 +64,8 @@ namespace :gitlab do
if project.persisted?
puts " * Created #{project.name} (#{repo_path})".green
project.update_repository_size
project.update_commit_count
else
puts " * Failed trying to create #{project.name} (#{repo_path})".red
puts " Errors: #{project.errors.messages}".red

View file

@ -0,0 +1,17 @@
namespace :gitlab do
task list_repos: :environment do
scope = Project
if ENV['SINCE']
date = Time.parse(ENV['SINCE'])
warn "Listing repositories with activity or changes since #{date}"
project_ids = Project.where('last_activity_at > ? OR updated_at > ?', date, date).pluck(:id).sort
namespace_ids = Namespace.where(['updated_at > ?', date]).pluck(:id).sort
scope = scope.where('id IN (?) OR namespace_id in (?)', project_ids, namespace_ids)
end
scope.find_each do |project|
base = File.join(Gitlab.config.gitlab_shell.repos_path, project.path_with_namespace)
puts base + '.git'
puts base + '.wiki.git'
end
end
end

View file

@ -118,4 +118,12 @@ namespace :gitlab do
false
end
end
def all_repos
IO.popen(%W(find #{Gitlab.config.gitlab_shell.repos_path} -mindepth 2 -maxdepth 2 -type d -name *.git)) do |find|
find.each_line do |path|
yield path.chomp
end
end
end
end

View file

@ -19,7 +19,7 @@ describe "Builds" do
end
it { expect(page).to have_content 'Running' }
it { expect(page).to have_content 'Cancel all' }
it { expect(page).to have_content 'Cancel running' }
it { expect(page).to have_content @build.short_sha }
it { expect(page).to have_content @build.ref }
it { expect(page).to have_content @build.name }
@ -32,7 +32,7 @@ describe "Builds" do
end
it { expect(page).to have_content 'No builds to show' }
it { expect(page).to have_content 'Cancel all' }
it { expect(page).to have_content 'Cancel running' }
end
context "All builds" do
@ -45,7 +45,7 @@ describe "Builds" do
it { expect(page).to have_content @build.short_sha }
it { expect(page).to have_content @build.ref }
it { expect(page).to have_content @build.name }
it { expect(page).to_not have_content 'Cancel all' }
it { expect(page).to_not have_content 'Cancel running' }
end
end
@ -53,11 +53,11 @@ describe "Builds" do
before do
@build.run!
visit namespace_project_builds_path(@gl_project.namespace, @gl_project)
click_link "Cancel all"
click_link "Cancel running"
end
it { expect(page).to have_content 'No builds to show' }
it { expect(page).to_not have_content 'Cancel all' }
it { expect(page).to_not have_content 'Cancel running' }
end
describe "GET /:project/builds/:id" do

View file

@ -1,12 +1,12 @@
%ul.nav.nav-tabs.merge-request-tabs
%li.notes-tab
%a{href: '/foo/bar/merge_requests/1', data: {target: '#notes', action: 'notes', toggle: 'tab'}}
%a{href: '/foo/bar/merge_requests/1', data: {target: 'div#notes', action: 'notes', toggle: 'tab'}}
Discussion
%li.commits-tab
%a{href: '/foo/bar/merge_requests/1/commits', data: {target: '#commits', action: 'commits', toggle: 'tab'}}
%a{href: '/foo/bar/merge_requests/1/commits', data: {target: 'div#commits', action: 'commits', toggle: 'tab'}}
Commits
%li.diffs-tab
%a{href: '/foo/bar/merge_requests/1/diffs', data: {target: '#diffs', action: 'diffs', toggle: 'tab'}}
%a{href: '/foo/bar/merge_requests/1/diffs', data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'}}
Diffs
.tab-content

View file

@ -13,6 +13,11 @@ describe Gitlab::LDAP::Access do
end
it { is_expected.to be_falsey }
it 'should block user in GitLab' do
access.allowed?
expect(user).to be_blocked
end
end
context 'when the user is found' do

View file

@ -10,6 +10,8 @@ describe API::API, api: true do
let(:avatar_file_path) { File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') }
let!(:group1) { create(:group, avatar: File.open(avatar_file_path)) }
let!(:group2) { create(:group) }
let!(:project1) { create(:project, namespace: group1) }
let!(:project2) { create(:project, namespace: group2) }
before do
group1.add_owner(user1)
@ -67,7 +69,7 @@ describe API::API, api: true do
it "should return any existing group" do
get api("/groups/#{group2.id}", admin)
expect(response.status).to eq(200)
json_response['name'] == group2.name
expect(json_response['name']).to eq(group2.name)
end
it "should not return a non existing group" do
@ -80,7 +82,7 @@ describe API::API, api: true do
it 'should return any existing group' do
get api("/groups/#{group1.path}", admin)
expect(response.status).to eq(200)
json_response['name'] == group2.name
expect(json_response['name']).to eq(group1.name)
end
it 'should not return a non existing group' do
@ -95,6 +97,59 @@ describe API::API, api: true do
end
end
describe "GET /groups/:id/projects" do
context "when authenticated as user" do
it "should return the group's projects" do
get api("/groups/#{group1.id}/projects", user1)
expect(response.status).to eq(200)
expect(json_response.length).to eq(1)
expect(json_response.first['name']).to eq(project1.name)
end
it "should not return a non existing group" do
get api("/groups/1328/projects", user1)
expect(response.status).to eq(404)
end
it "should not return a group not attached to user1" do
get api("/groups/#{group2.id}/projects", user1)
expect(response.status).to eq(403)
end
end
context "when authenticated as admin" do
it "should return any existing group" do
get api("/groups/#{group2.id}/projects", admin)
expect(response.status).to eq(200)
expect(json_response.length).to eq(1)
expect(json_response.first['name']).to eq(project2.name)
end
it "should not return a non existing group" do
get api("/groups/1328/projects", admin)
expect(response.status).to eq(404)
end
end
context 'when using group path in URL' do
it 'should return any existing group' do
get api("/groups/#{group1.path}/projects", admin)
expect(response.status).to eq(200)
expect(json_response.first['name']).to eq(project1.name)
end
it 'should not return a non existing group' do
get api('/groups/unknown/projects', admin)
expect(response.status).to eq(404)
end
it 'should not return a group not attached to user1' do
get api("/groups/#{group2.path}/projects", user1)
expect(response.status).to eq(403)
end
end
end
describe "POST /groups" do
context "when authenticated as user without group permissions" do
it "should not create group" do