Commit 6c94a442 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into mr-builds

# Conflicts:
#	app/controllers/projects/merge_requests_controller.rb
#	app/views/projects/merge_requests/widget/_heading.html.haml
parents 5beacba0 e616739e
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.3.0 (unreleased) v 8.3.0 (unreleased)
- 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) - 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) - Handle and report SSL errors in Web hook test (Stan Hu)
- Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera) - 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 - Fix 500 error when update group member permission
- Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera) - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera)
- Recognize issue/MR/snippet/commit links as references - Recognize issue/MR/snippet/commit links as references
...@@ -18,6 +22,8 @@ v 8.3.0 (unreleased) ...@@ -18,6 +22,8 @@ v 8.3.0 (unreleased)
- Fix 500 error when creating a merge request that removes a submodule - Fix 500 error when creating a merge request that removes a submodule
- Run custom Git hooks when branch is created or deleted. - 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 - 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 v 8.2.3
- Fix application settings cache not expiring after changes (Stan Hu) - Fix application settings cache not expiring after changes (Stan Hu)
......
...@@ -52,7 +52,7 @@ gem "gitlab_git", '~> 7.2.20' ...@@ -52,7 +52,7 @@ gem "gitlab_git", '~> 7.2.20'
gem 'gitlab_omniauth-ldap', '~> 1.2.1', require: "omniauth-ldap" gem 'gitlab_omniauth-ldap', '~> 1.2.1', require: "omniauth-ldap"
# Git Wiki # Git Wiki
gem 'gollum-lib', '~> 4.0.2' gem 'gollum-lib', '~> 4.1.0'
# Language detection # Language detection
gem "github-linguist", "~> 4.7.0", require: "linguist" gem "github-linguist", "~> 4.7.0", require: "linguist"
......
...@@ -314,7 +314,7 @@ GEM ...@@ -314,7 +314,7 @@ GEM
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab_emoji (0.2.0) gitlab_emoji (0.2.0)
gemojione (~> 2.1) gemojione (~> 2.1)
gitlab_git (7.2.20) gitlab_git (7.2.21)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
...@@ -329,11 +329,11 @@ GEM ...@@ -329,11 +329,11 @@ GEM
activesupport (>= 4.1.0) activesupport (>= 4.1.0)
gollum-grit_adapter (1.0.0) gollum-grit_adapter (1.0.0)
gitlab-grit (~> 2.7, >= 2.7.1) gitlab-grit (~> 2.7, >= 2.7.1)
gollum-lib (4.0.3) gollum-lib (4.1.0)
github-markup (~> 1.3.3) github-markup (~> 1.3.3)
gollum-grit_adapter (~> 1.0) gollum-grit_adapter (~> 1.0)
nokogiri (~> 1.6.4) nokogiri (~> 1.6.4)
rouge (~> 1.10.1) rouge (~> 1.9)
sanitize (~> 2.1.0) sanitize (~> 2.1.0)
stringex (~> 2.5.1) stringex (~> 2.5.1)
gon (6.0.1) gon (6.0.1)
...@@ -884,7 +884,7 @@ DEPENDENCIES ...@@ -884,7 +884,7 @@ DEPENDENCIES
gitlab_git (~> 7.2.20) gitlab_git (~> 7.2.20)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1) gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.0.2) gollum-lib (~> 4.1.0)
gon (~> 6.0.1) gon (~> 6.0.1)
grape (~> 0.13.0) grape (~> 0.13.0)
grape-entity (~> 0.4.2) grape-entity (~> 0.4.2)
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
groups_path: "/api/:version/groups.json" groups_path: "/api/:version/groups.json"
group_path: "/api/:version/groups/:id.json" group_path: "/api/:version/groups/:id.json"
namespaces_path: "/api/:version/namespaces.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) -> group: (group_id, callback) ->
url = Api.buildUrl(Api.group_path) url = Api.buildUrl(Api.group_path)
...@@ -44,6 +46,35 @@ ...@@ -44,6 +46,35 @@
).done (namespaces) -> ).done (namespaces) ->
callback(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) -> buildUrl: (url) ->
url = gon.relative_url_root + url if gon.relative_url_root? url = gon.relative_url_root + url if gon.relative_url_root?
return url.replace(':version', gon.api_version) return url.replace(':version', gon.api_version)
...@@ -83,7 +83,7 @@ class Dispatcher ...@@ -83,7 +83,7 @@ class Dispatcher
when 'projects:project_members:index' when 'projects:project_members:index'
new ProjectMembers() new ProjectMembers()
new UsersSelect() new UsersSelect()
when 'groups:new', 'groups:edit', 'admin:groups:edit' when 'groups:new', 'groups:edit', 'admin:groups:edit', 'admin:groups:new'
new GroupAvatar() new GroupAvatar()
when 'projects:tree:show' when 'projects:tree:show'
new TreeView() new TreeView()
......
...@@ -3,7 +3,7 @@ class @NewCommitForm ...@@ -3,7 +3,7 @@ class @NewCommitForm
@newBranch = form.find('.js-new-branch') @newBranch = form.find('.js-new-branch')
@originalBranch = form.find('.js-original-branch') @originalBranch = form.find('.js-original-branch')
@createMergeRequest = form.find('.js-create-merge-request') @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() @renderDestination()
@newBranch.keyup @renderDestination @newBranch.keyup @renderDestination
...@@ -12,10 +12,10 @@ class @NewCommitForm ...@@ -12,10 +12,10 @@ class @NewCommitForm
different = @newBranch.val() != @originalBranch.val() different = @newBranch.val() != @originalBranch.val()
if different if different
@createMergeRequestFormGroup.show() @createMergeRequestContainer.show()
@createMergeRequest.prop('checked', true) unless @wasDifferent @createMergeRequest.prop('checked', true) unless @wasDifferent
else else
@createMergeRequestFormGroup.hide() @createMergeRequestContainer.hide()
@createMergeRequest.prop('checked', false) @createMergeRequest.prop('checked', false)
@wasDifferent = different @wasDifferent = different
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"
...@@ -7,8 +7,8 @@ ...@@ -7,8 +7,8 @@
/* Common styles for all types */ /* Common styles for all types */
.bs-callout { .bs-callout {
margin: 20px 0; margin: $gl-padding 0;
padding: 20px; padding: $gl-padding;
border-left: 3px solid $border-color; border-left: 3px solid $border-color;
color: $text-color; color: $text-color;
background: $background-color; background: $background-color;
...@@ -42,4 +42,3 @@ ...@@ -42,4 +42,3 @@
border-color: #5cA64d; border-color: #5cA64d;
color: #3c763d; color: #3c763d;
} }
...@@ -333,7 +333,7 @@ table { ...@@ -333,7 +333,7 @@ table {
} }
.well { .well {
margin-bottom: 0; margin-bottom: $gl-padding;
} }
.search_box { .search_box {
...@@ -379,9 +379,8 @@ table { ...@@ -379,9 +379,8 @@ table {
text-align: center; text-align: center;
margin-top: 5px; margin-top: 5px;
margin-bottom: $gl-padding; margin-bottom: $gl-padding;
height: 56px; height: auto;
margin-top: -$gl-padding; margin-top: -$gl-padding;
padding-top: $gl-padding;
&.no-bottom { &.no-bottom {
margin-bottom: 0; margin-bottom: 0;
...@@ -390,6 +389,18 @@ table { ...@@ -390,6 +389,18 @@ table {
&.no-top { &.no-top {
margin-top: 0; 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 { .center-middle-menu {
...@@ -437,3 +448,16 @@ table { ...@@ -437,3 +448,16 @@ table {
.alert, .progress { .alert, .progress {
margin-bottom: $gl-padding; 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;
}
}
...@@ -21,7 +21,6 @@ ...@@ -21,7 +21,6 @@
position: relative; position: relative;
background: $background-color; background: $background-color;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
text-shadow: 0 1px 1px #fff;
margin: 0; margin: 0;
text-align: left; text-align: left;
padding: 10px $gl-padding; padding: 10px $gl-padding;
......
...@@ -72,13 +72,6 @@ ...@@ -72,13 +72,6 @@
} }
} }
ol, ul {
&.styled {
li {
padding: 2px;
}
}
}
/** light list with border-bottom between li **/ /** light list with border-bottom between li **/
ul.bordered-list { ul.bordered-list {
......
...@@ -82,9 +82,6 @@ ...@@ -82,9 +82,6 @@
} }
.center-top-menu { .center-top-menu {
height: 45px;
margin-bottom: 30px;
li a { li a {
font-size: 14px; font-size: 14px;
padding: 19px 10px; padding: 19px 10px;
......
...@@ -2,8 +2,10 @@ ...@@ -2,8 +2,10 @@
margin-bottom: $gl-padding; margin-bottom: $gl-padding;
.panel-heading { .panel-heading {
padding: 10px $gl-padding; padding: 7px $gl-padding;
line-height: 42px !important;
} }
.panel-body { .panel-body {
padding: $gl-padding; padding: $gl-padding;
......
...@@ -220,6 +220,7 @@ pre { ...@@ -220,6 +220,7 @@ pre {
.monospace { .monospace {
font-family: $monospace_font; font-family: $monospace_font;
font-size: 90%;
} }
code { code {
......
...@@ -67,9 +67,4 @@ ...@@ -67,9 +67,4 @@
color: #3084bb !important; color: #3084bb !important;
} }
} }
.build-top-menu {
margin-top: 0;
margin-bottom: 2px;
}
} }
...@@ -140,7 +140,7 @@ ...@@ -140,7 +140,7 @@
font-family: $monospace_font; font-family: $monospace_font;
font-weight: bold; font-weight: bold;
overflow: hidden; overflow: hidden;
font-size: 14px; font-size: 90%;
margin: 0 3px; margin: 0 3px;
} }
......
...@@ -5,12 +5,6 @@ ...@@ -5,12 +5,6 @@
} }
} }
.btn-build-token {
float: left;
padding: 6px 20px;
margin-right: 12px;
}
.profile-avatar-form-option { .profile-avatar-form-option {
hr { hr {
margin: 10px 0; margin: 10px 0;
......
.gitlab-ui-dev-kit { .gitlab-ui-dev-kit {
> h2 { > h2 {
font-size: 27px; margin: 35px 0 20px;
border-bottom: 1px solid #CCC;
color: #666;
margin: 30px 0;
font-weight: bold; font-weight: bold;
} }
} }
...@@ -28,7 +28,7 @@ class Projects::ApplicationController < ApplicationController ...@@ -28,7 +28,7 @@ class Projects::ApplicationController < ApplicationController
private private
def ci_enabled def builds_enabled
return render_404 unless @project.builds_enabled? return render_404 unless @project.builds_enabled?
end end
......
...@@ -5,7 +5,7 @@ class Projects::GraphsController < Projects::ApplicationController ...@@ -5,7 +5,7 @@ class Projects::GraphsController < Projects::ApplicationController
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :assign_ref_vars before_action :assign_ref_vars
before_action :authorize_download_code! before_action :authorize_download_code!
before_action :ci_enabled, only: :ci before_action :builds_enabled, only: :ci
def show def show
respond_to do |format| respond_to do |format|
...@@ -34,6 +34,26 @@ class Projects::GraphsController < Projects::ApplicationController ...@@ -34,6 +34,26 @@ class Projects::GraphsController < Projects::ApplicationController
@charts[:build_times] = Ci::Charts::BuildTime.new(ci_project) @charts[:build_times] = Ci::Charts::BuildTime.new(ci_project)
end 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 private
def fetch_graph def fetch_graph
......
...@@ -10,15 +10,13 @@ class Projects::RawController < Projects::ApplicationController ...@@ -10,15 +10,13 @@ class Projects::RawController < Projects::ApplicationController
@blob = @repository.blob_at(@commit.id, @path) @blob = @repository.blob_at(@commit.id, @path)
if @blob if @blob
type = get_blob_type
headers['X-Content-Type-Options'] = 'nosniff' headers['X-Content-Type-Options'] = 'nosniff'
send_data( if @blob.lfs_pointer?
@blob.data, send_lfs_object
type: type, else
disposition: 'inline' stream_data
) end
else else
render_404 render_404
end end
...@@ -35,4 +33,33 @@ class Projects::RawController < Projects::ApplicationController ...@@ -35,4 +33,33 @@ class Projects::RawController < Projects::ApplicationController
'application/octet-stream' 'application/octet-stream'
end end
end end
def stream_data
type = get_blob_type
send_data(
@blob.data,
type: type,
disposition: 'inline'
)
end
def send_lfs_object
lfs_object = find_lfs_object
if lfs_object && lfs_object.project_allowed_access?(@project)
send_file lfs_object.file.path, filename: @blob.name, disposition: 'attachment'
else
render_404
end
end
def find_lfs_object
lfs_object = LfsObject.find_by_oid(@blob.lfs_oid)
if lfs_object && lfs_object.file.exists?
lfs_object
else
nil
end
end
end end
...@@ -30,7 +30,7 @@ module BlobHelper ...@@ -30,7 +30,7 @@ module BlobHelper
nil nil
end end
if blob && blob.text? if blob_viewable?(blob)
text = 'Edit' text = 'Edit'
after = options[:after] || '' after = options[:after] || ''
from_mr = options[:from_merge_request_id] from_mr = options[:from_merge_request_id]
...@@ -71,4 +71,16 @@ module BlobHelper ...@@ -71,4 +71,16 @@ module BlobHelper
def blob_icon(mode, name) def blob_icon(mode, name)
icon("#{file_type_icon_class('file', mode, name)} fw") icon("#{file_type_icon_class('file', mode, name)} fw")
end end
def blob_viewable?(blob)
blob && blob.text? && !blob.lfs_pointer?
end
def blob_size(blob)
if blob.lfs_pointer?
blob.lfs_size
else
blob.size
end
end
end end
...@@ -8,6 +8,10 @@ module CiStatusHelper ...@@ -8,6 +8,10 @@ module CiStatusHelper
ci_icon_for_status(ci_commit.status) ci_icon_for_status(ci_commit.status)
end end
def ci_status_label(ci_commit)
ci_label_for_status(ci_commit.status)
end
def ci_status_color(ci_commit) def ci_status_color(ci_commit)
case ci_commit.status case ci_commit.status
when 'success' when 'success'
...@@ -23,7 +27,15 @@ module CiStatusHelper ...@@ -23,7 +27,15 @@ module CiStatusHelper
def ci_status_with_icon(status) def ci_status_with_icon(status)
content_tag :span, class: "ci-status ci-#{status}" do 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
end end
...@@ -46,7 +58,7 @@ module CiStatusHelper ...@@ -46,7 +58,7 @@ module CiStatusHelper
def render_ci_status(ci_commit) def render_ci_status(ci_commit)
link_to ci_status_path(ci_commit), link_to ci_status_path(ci_commit),
class: "c#{ci_status_color(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 data: { toggle: 'tooltip', placement: 'left' } do
ci_status_icon(ci_commit) ci_status_icon(ci_commit)
end end
......
...@@ -4,7 +4,8 @@ module PageLayoutHelper ...@@ -4,7 +4,8 @@ module PageLayoutHelper
@page_title.push(*titles.compact) if titles.any? @page_title.push(*titles.compact) if titles.any?
@page_title.join(" | ") # Segments are seperated by middot
@page_title.join(" \u00b7 ")
end end
def header_title(title = nil, title_url = nil) def header_title(title = nil, title_url = nil)
......
...@@ -48,6 +48,19 @@ module SelectsHelper ...@@ -48,6 +48,19 @@ module SelectsHelper
select2_tag(id, opts) select2_tag(id, opts)
end 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 = {}) def select2_tag(id, opts = {})
css_class = '' css_class = ''
css_class << 'multiselect ' if opts[:multiple] css_class << 'multiselect ' if opts[:multiple]
......
...@@ -54,6 +54,10 @@ module TreeHelper ...@@ -54,6 +54,10 @@ module TreeHelper
::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref) ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
end end
def can_delete_or_replace?(blob)
allowed_tree_edit? && !blob.lfs_pointer?
end
def tree_breadcrumbs(tree, max_links = 2) def tree_breadcrumbs(tree, max_links = 2)
if @path.present? if @path.present?
part_path = "" part_path = ""
......
...@@ -199,7 +199,7 @@ module Ci ...@@ -199,7 +199,7 @@ module Ci
end end
def ci_yaml_file 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 rescue
nil nil
end end
......
...@@ -5,4 +5,16 @@ class LfsObject < ActiveRecord::Base ...@@ -5,4 +5,16 @@ class LfsObject < ActiveRecord::Base
validates :oid, presence: true, uniqueness: true validates :oid, presence: true, uniqueness: true
mount_uploader :file, LfsObjectUploader mount_uploader :file, LfsObjectUploader
def storage_project(project)
if project && project.forked?
storage_project(project.forked_from_project)
else
project
end
end
def project_allowed_access?(project)
projects.exists?(storage_project(project).id)
end
end end
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
= render 'shared/project_limit' = render 'shared/project_limit'
%ul.center-top-menu %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 = link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
Your Projects Your Projects
= nav_link(page: starred_dashboard_projects_path) do = nav_link(page: starred_dashboard_projects_path) do
......
...@@ -4,14 +4,20 @@ ...@@ -4,14 +4,20 @@
- if current_user - if current_user
= auto_discovery_link_tag(:atom, issues_dashboard_url(format: :atom, private_token: current_user.private_token), title: "#{current_user.name} issues") = auto_discovery_link_tag(:atom, issues_dashboard_url(format: :atom, private_token: current_user.private_token), title: "#{current_user.name} issues")
.project-issuable-filter
.append-bottom-20 .controls
.pull-right .pull-left
- if current_user - if current_user
.hidden-xs.pull-left.prepend-top-20 .hidden-xs.pull-left
= link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: '' do = link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do
%i.fa.fa-rss %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
= render 'shared/issues' .gray-content-block.second-block
List all issues from all projects you have access to.
.prepend-top-default
= render 'shared/issues'
- page_title "Merge Requests" - page_title "Merge Requests"
- header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id) - 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/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'
- page_title "Milestones" - 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 .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 .milestones
......
...@@ -4,16 +4,19 @@ ...@@ -4,16 +4,19 @@
- if current_user - if current_user
= auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues") = 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
= render 'shared/issuable/filter', type: :issues .pull-left
.gray-content-block.second-block
.pull-right
- if current_user - if current_user
.hidden-xs.pull-left .hidden-xs.pull-left
= link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token) do = link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do
%i.fa.fa-rss %i.fa.fa-rss
%div
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/issuable/filter', type: :issues
.gray-content-block.second-block
Only issues from Only issues from
%strong #{@group.name} %strong #{@group.name}
group are listed here. group are listed here.
......
- page_title "Merge Requests" - page_title "Merge Requests"
- header_title group_title(@group, "Merge Requests", merge_requests_group_path(@group)) - 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 .gray-content-block.second-block
%div
Only merge requests from Only merge requests from
%strong #{@group.name} %strong #{@group.name}
group are listed here. group are listed here.
- if current_user - if current_user
To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page. To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
.prepend-top-default .prepend-top-default
= render 'shared/merge_requests' = render 'shared/merge_requests'
- page_title "Milestones" - page_title "Milestones"
- header_title group_title(@group, "Milestones", group_milestones_path(@group)) - header_title group_title(@group, "Milestones", group_milestones_path(@group))
= render 'shared/milestones_filter' .project-issuable-filter
.gray-content-block .controls
- if can?(current_user, :admin_milestones, @group) - if can?(current_user, :admin_milestones, @group)
.pull-right .pull-right
%span.pull-right.hidden-xs %span.pull-right.hidden-xs
= link_to new_group_milestone_path(@group), class: "btn btn-new" do = link_to new_group_milestone_path(@group), class: "btn btn-new" do
= icon('plus')
New Milestone New Milestone
.oneline = render 'shared/milestones_filter'
.gray-content-block
Only milestones from Only milestones from
%strong #{@group.name} %strong #{@group.name}
group are listed here. group are listed here.
.milestones .milestones
%ul.content-list %ul.content-list
- if @milestones.blank? - if @milestones.blank?
......
...@@ -31,11 +31,9 @@ ...@@ -31,11 +31,9 @@
%h2#blocks Blocks %h2#blocks Blocks
%h3 %h4
%code .gray-content-block %code .gray-content-block
.gray-content-block.middle-block .gray-content-block.middle-block
%h4 Normal block inside content %h4 Normal block inside content
= lorem = lorem
...@@ -45,9 +43,28 @@ ...@@ -45,9 +43,28 @@
= lorem = 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 %h2#lists Lists
%h3 %h4
%code .content-list %code .content-list
%ul.content-list %ul.content-list
%li %li
...@@ -57,7 +74,7 @@ ...@@ -57,7 +74,7 @@
%li %li
One item One item
%h3 %h4
%code .well-list %code .well-list
%ul.well-list %ul.well-list
%li %li
...@@ -67,7 +84,7 @@ ...@@ -67,7 +84,7 @@
%li %li
One item One item
%h3 %h4
%code .panel .well-list %code .panel .well-list
.panel.panel-default .panel.panel-default
...@@ -80,7 +97,7 @@ ...@@ -80,7 +97,7 @@
%li %li
One item One item
%h3 %h4
%code .bordered-list %code .bordered-list
%ul.bordered-list %ul.bordered-list
%li %li
...@@ -121,7 +138,7 @@ ...@@ -121,7 +138,7 @@
%h2#navs Navigation %h2#navs Navigation
%h3 %h4
%code .center-top-menu %code .center-top-menu
.example .example
%ul.center-top-menu %ul.center-top-menu
...@@ -130,7 +147,7 @@ ...@@ -130,7 +147,7 @@
%li %li
%a Closed %a Closed
%h3 %h4
%code .btn-group.btn-group-next %code .btn-group.btn-group-next
.example .example
%div.btn-group.btn-group-next %div.btn-group.btn-group-next
...@@ -138,7 +155,7 @@ ...@@ -138,7 +155,7 @@
%a.btn Closed %a.btn Closed
%h3 %h4
%code .nav.nav-tabs %code .nav.nav-tabs
.example .example
%ul.nav.nav-tabs %ul.nav.nav-tabs
...@@ -204,7 +221,7 @@ ...@@ -204,7 +221,7 @@
%h2#forms Forms %h2#forms Forms
%h3 %h4
%code form.horizontal-form %code form.horizontal-form
%form.form-horizontal %form.form-horizontal
...@@ -226,7 +243,7 @@ ...@@ -226,7 +243,7 @@
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
%button.btn.btn-default{:type => "submit"} Sign in %button.btn.btn-default{:type => "submit"} Sign in
%h3 %h4
%code form %code form
%form %form
...@@ -243,7 +260,7 @@ ...@@ -243,7 +260,7 @@
%button.btn.btn-default{:type => "submit"} Sign in %button.btn.btn-default{:type => "submit"} Sign in
%h2#file File %h2#file File
%h3 %h4
%code .file-holder %code .file-holder
- blob = Snippet.new(content: "Wow\nSuch\nFile") - blob = Snippet.new(content: "Wow\nSuch\nFile")
...@@ -254,13 +271,12 @@ ...@@ -254,13 +271,12 @@
.file-actions .file-actions
.btn-group .btn-group
%a.btn Edit %a.btn Edit
%a.btn Remove %a.btn.btn-danger Remove
.file-contenta.code .file-contenta.code
= render 'shared/file_highlight', blob: blob = render 'shared/file_highlight', blob: blob
%h2#markdown Markdown %h2#markdown Markdown
%h3 %h4
%code .md or .wiki and others %code .md or .wiki and others
Markdown rendering has a bit different css and presented in next UI elements: Markdown rendering has a bit different css and presented in next UI elements:
......
...@@ -28,9 +28,9 @@ ...@@ -28,9 +28,9 @@
.form-actions .form-actions
- if current_user.private_token - if current_user.private_token
= f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default btn-build-token" = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default"
- else - else
= f.submit 'Generate', class: "btn btn-default btn-build-token" = f.submit 'Generate', class: "btn btn-default"
- unless current_user.ldap_user? - unless current_user.ldap_user?
.panel.panel-default .panel.panel-default
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
$('#key_key').on('focusout', function(){ $('#key_key').on('focusout', function(){
var title = $('#key_title'), var title = $('#key_title'),
val = $('#key_key').val(), val = $('#key_key').val(),
comment = val.match(/^\S+ \S+ (.+)$/); comment = val.match(/^\S+ \S+ (.+)\n?$/);
if( comment && comment.length > 1 && title.val() == '' ){ if( comment && comment.length > 1 && title.val() == '' ){
$('#key_title').val( comment[1] ); $('#key_title').val( comment[1] );
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
- if ci_commit - if ci_commit
= link_to ci_status_path(ci_commit), class: "ci-status ci-#{ci_commit.status}" do = link_to ci_status_path(ci_commit), class: "ci-status ci-#{ci_commit.status}" do
= ci_status_icon(ci_commit) = 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 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" = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
= link_to 'Raw', namespace_project_raw_path(@project.namespace, @project, @id), = link_to 'Raw', namespace_project_raw_path(@project.namespace, @project, @id),
class: 'btn btn-sm', target: '_blank' class: 'btn btn-sm', target: '_blank'
-# only show normal/blame view links for text files -# only show normal/blame view links for text files
- if @blob.text? - if blob_viewable?(@blob)
- if current_page? namespace_project_blame_path(@project.namespace, @project, @id) - if current_page? namespace_project_blame_path(@project.namespace, @project, @id)
= link_to 'Normal View', namespace_project_blob_path(@project.namespace, @project, @id), = link_to 'Normal View', namespace_project_blob_path(@project.namespace, @project, @id),
class: 'btn btn-sm' class: 'btn btn-sm'
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
= link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project, = link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project,
tree_join(@commit.sha, @path)), class: 'btn btn-sm' tree_join(@commit.sha, @path)), class: 'btn btn-sm'
- if allowed_tree_edit? - if can_delete_or_replace?(@blob)
.btn-group{ role: "group" } .btn-group{ role: "group" }
%button.btn.btn-default{ 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } Replace %button.btn.btn-default{ 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } Replace
%button.btn.btn-remove{ 'data-target' => '#modal-remove-blob', 'data-toggle' => 'modal' } Delete %button.btn.btn-remove{ 'data-target' => '#modal-remove-blob', 'data-toggle' => 'modal' } Delete
...@@ -29,10 +29,12 @@ ...@@ -29,10 +29,12 @@
%strong %strong
= blob.name = blob.name
%small %small
= number_to_human_size(blob.size) = number_to_human_size(blob_size(blob))
.file-actions.hidden-xs .file-actions.hidden-xs
= render "actions" = render "actions"
- if blob.text? - if blob.lfs_pointer?
= render "download", blob: blob
- elsif blob.text?
= render "text", blob: blob = render "text", blob: blob
- elsif blob.image? - elsif blob.image?
= render "image", blob: blob = render "image", blob: blob
......
...@@ -4,4 +4,4 @@ ...@@ -4,4 +4,4 @@
%h1.light %h1.light
%i.fa.fa-download %i.fa.fa-download
%h4 %h4
Download (#{number_to_human_size blob.size}) Download (#{number_to_human_size blob_size(blob)})
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
%div#tree-holder.tree-holder %div#tree-holder.tree-holder
= render 'blob', blob: @blob = render 'blob', blob: @blob
- if allowed_tree_edit? - if can_delete_or_replace?(@blob)
= render 'projects/blob/remove' = render 'projects/blob/remove'
- title = "Replace #{@blob.name}" - title = "Replace #{@blob.name}"
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
Compare Compare
- if can_remove_branch?(@project, branch.name) - 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") = icon("trash-o")
- if commit - if commit
......
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
.project-issuable-filter .project-issuable-filter
.controls .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 .pull-left.hidden-xs
- if @all_builds.running_or_pending.any? - 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 %ul.center-top-menu
%li{class: ('active' if @scope.nil?)} %li{class: ('active' if @scope.nil?)}
...@@ -50,4 +50,3 @@ ...@@ -50,4 +50,3 @@
= render 'projects/commit_statuses/commit_status', commit_status: build, commit_sha: true, stage: true, allow_retry: true = render 'projects/commit_statuses/commit_status', commit_status: build, commit_sha: true, stage: true, allow_retry: true
= paginate @builds, theme: 'gitlab' = paginate @builds, theme: 'gitlab'
- page_title "#{@build.name} (#{@build.id})", "Builds" - page_title "#{@build.name} (##{@build.id})", "Builds"
= render "header_title" = render "header_title"
.build-page .build-page
.gray-content-block .gray-content-block.top-block
Build ##{@build.id} for commit Build ##{@build.id} for commit
%strong.monospace %strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit)
= link_to @build.commit.short_sha, ci_status_path(@build.commit)
from from
= link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref) = link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref)
#up-build-trace #up-build-trace
- if @commit.matrix_for_ref?(@build.ref) - 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| - @commit.latest_builds_for_ref(@build.ref).each do |build|
%li{class: ('active' if build == @build) } %li{class: ('active' if build == @build) }
= link_to namespace_project_build_path(@project.namespace, @project, build) do = link_to namespace_project_build_path(@project.namespace, @project, build) do
...@@ -22,7 +21,6 @@ ...@@ -22,7 +21,6 @@
- else - else
= build.id = build.id
- if @build.retried? - if @build.retried?
%li.active %li.active
%a %a
...@@ -31,7 +29,7 @@ ...@@ -31,7 +29,7 @@
%i.fa.fa-warning %i.fa.fa-warning
This build was retried. This build was retried.
.gray-content-block.second-block .gray-content-block.middle-block
.build-head .build-head
.clearfix .clearfix
= ci_status_with_icon(@build.status) = ci_status_with_icon(@build.status)
......
...@@ -20,7 +20,8 @@ ...@@ -20,7 +20,8 @@
%p %p
%span.light Commit %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 .commit-info-row
%span.light Authored by %span.light Authored by
%strong %strong
...@@ -44,7 +45,7 @@ ...@@ -44,7 +45,7 @@
= link_to ci_status_path(@ci_commit), class: "ci-status ci-#{@ci_commit.status}" do = link_to ci_status_path(@ci_commit), class: "ci-status ci-#{@ci_commit.status}" do
= ci_status_icon(@ci_commit) = ci_status_icon(@ci_commit)
build: build:
= @ci_commit.status = ci_status_label(@ci_commit)
.commit-info-row.branches .commit-info-row.branches
%i.fa.fa-spinner.fa-spin %i.fa.fa-spinner.fa-spin
......
%tr.commit_status %tr.commit_status
%td.status %td.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) = ci_status_with_icon(commit_status.status)
%td.commit_status-link %td.commit_status-link
- if commit_status.target_url - if commit_status.target_url
= link_to commit_status.target_url do = link_to commit_status.target_url do
%strong Build ##{commit_status.id} %strong ##{commit_status.id}
- else - else
%strong Build ##{commit_status.id} %strong ##{commit_status.id}
- if commit_status.show_warning? - if commit_status.show_warning?
%i.fa.fa-warning.text-warning %i.fa.fa-warning.text-warning
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
= "#{diff_file.diff.a_mode}#{diff_file.diff.b_mode}" = "#{diff_file.diff.a_mode}#{diff_file.diff.b_mode}"
.diff-controls .diff-controls
- if blob.text? - if blob_viewable?(blob)
= link_to '#', class: 'js-toggle-diff-comments btn btn-sm active has_tooltip', title: "Toggle comments for this file" do = link_to '#', class: 'js-toggle-diff-comments btn btn-sm active has_tooltip', title: "Toggle comments for this file" do
%i.fa.fa-comments %i.fa.fa-comments
&nbsp; &nbsp;
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
.diff-content.diff-wrap-lines .diff-content.diff-wrap-lines
-# Skipp all non non-supported blobs -# Skipp all non non-supported blobs
- return unless blob.respond_to?('text?') - return unless blob.respond_to?('text?')
- if blob.text? - if blob_viewable?(blob)
- if diff_view == 'parallel' - if diff_view == 'parallel'
= render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
- else - else
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
= link_to 'Contributors', namespace_project_graph_path = link_to 'Contributors', namespace_project_graph_path
= nav_link(action: :commits) do = nav_link(action: :commits) do
= link_to 'Commits', commits_namespace_project_graph_path = 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? - if @project.builds_enabled?
= nav_link(action: :ci) do = nav_link(action: :ci) do
= link_to ci_namespace_project_graph_path do = link_to ci_namespace_project_graph_path do
......
- 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);
...@@ -12,15 +12,12 @@ ...@@ -12,15 +12,12 @@
.col-md-9 .col-md-9
.votes-holder.pull-right .votes-holder.pull-right
#votes= render 'votes/votes_block', votable: @issue #votes= render 'votes/votes_block', votable: @issue
.participants = render "shared/issuable/participants"
%span= pluralize(@participants.count, 'participant')
- @participants.each do |participant|
= link_to_member(@project, participant, name: false, size: 24)
.col-md-3 .col-md-3
.input-group.cross-project-reference .input-group.cross-project-reference
%span#cross-project-reference.slead.has_tooltip{title: 'Cross-project reference'} %span#cross-project-reference.slead.has_tooltip{title: 'Cross-project reference'}
= cross_project_reference(@project, @issue) = cross_project_reference(@project, @issue)
= clipboard_button(clipboard_target: '#cross-project-reference') = clipboard_button(clipboard_target: 'span#cross-project-reference')
.row .row
%section.col-md-9 %section.col-md-9
......
...@@ -12,16 +12,16 @@ ...@@ -12,16 +12,16 @@
.col-md-9 .col-md-9
.votes-holder.pull-right .votes-holder.pull-right
#votes= render 'votes/votes_block', votable: @merge_request #votes= render 'votes/votes_block', votable: @merge_request
= render "projects/merge_requests/show/participants" = render "shared/issuable/participants"
.col-md-3 .col-md-3
.input-group.cross-project-reference .input-group.cross-project-reference
%span#cross-project-reference.slead.has_tooltip{title: 'Cross-project reference'} %span#cross-project-reference.slead.has_tooltip{title: 'Cross-project reference'}
= cross_project_reference(@project, @merge_request) = cross_project_reference(@project, @merge_request)
= clipboard_button(clipboard_target: '#cross-project-reference') = clipboard_button(clipboard_target: 'span#cross-project-reference')
.row .row
%section.col-md-9 %section.col-md-9
= render "projects/notes/notes_with_form" .voting_notes#notes= render "projects/notes/notes_with_form"
%aside.col-md-3 %aside.col-md-3
.issuable-affix .issuable-affix
.context .context
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
.mr-compare.merge-request .mr-compare.merge-request
%ul.merge-request-tabs.center-top-menu.no-top.no-bottom %ul.merge-request-tabs.center-top-menu.no-top.no-bottom
%li.commits-tab %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 Commits
%span.badge= @commits.size %span.badge= @commits.size
- if @ci_commit - if @ci_commit
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
Builds Builds
%span.badge= @statuses.size %span.badge= @statuses.size
%li.diffs-tab.active %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 Changes
%span.badge= @diffs.size %span.badge= @diffs.size
......
...@@ -43,11 +43,11 @@ ...@@ -43,11 +43,11 @@
- if @commits.present? - if @commits.present?
%ul.merge-request-tabs.center-top-menu.no-top.no-bottom %ul.merge-request-tabs.center-top-menu.no-top.no-bottom
%li.notes-tab %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 Discussion
%span.badge= @merge_request.mr_and_commit_notes.user.count %span.badge= @merge_request.mr_and_commit_notes.user.count
%li.commits-tab %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 Commits
%span.badge= @commits.size %span.badge= @commits.size
- if @ci_commit - if @ci_commit
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
Builds Builds
%span.badge= @statuses.size %span.badge= @statuses.size
%li.diffs-tab %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 Changes
%span.badge= @merge_request.diffs.size %span.badge= @merge_request.diffs.size
......
- ci_commit = @merge_request.ci_commit - if @ci_commit
- if ci_commit
- status = ci_commit.status
.mr-widget-heading .mr-widget-heading
.ci_widget{class: "ci-#{status}"} .ci_widget{class: "ci-#{@ci_commit.status}"}
= ci_status_icon(ci_commit) = ci_status_icon(@ci_commit)
%span Build #{status} %span
Build
= ci_status_label(@ci_commit)
for for
= succeed "." do = succeed "." do
= link_to @ci_commit.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @ci_commit.sha), class: "monospace" = link_to @ci_commit.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @ci_commit.sha), class: "monospace"
...@@ -15,21 +15,19 @@ ...@@ -15,21 +15,19 @@
- # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX - # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX
- # Remove in later versions when services like Jenkins will set CI status via Commit status API - # Remove in later versions when services like Jenkins will set CI status via Commit status API
.mr-widget-heading .mr-widget-heading
- [:success, :skipped, :canceled, :failed, :running, :pending].each do |status| - %w[success skipped canceled failed running pending].each do |status|
.ci_widget{class: "ci-#{status}", style: "display:none"} .ci_widget{class: "ci-#{status}", style: "display:none"}
- if status == :success = ci_icon_for_status(status)
- status = "passed" %span
= icon("check-circle") CI build
- else = ci_label_for_status(status)
= icon("circle")
%span CI build #{status}
for for
- commit = @merge_request.last_commit - commit = @merge_request.last_commit
= succeed "." do = succeed "." do
= link_to commit.short_id, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, commit), class: "monospace" = link_to commit.short_id, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, commit), class: "monospace"
%span.ci-coverage %span.ci-coverage
- if ci_build_details_path(@merge_request) - if details_path = ci_build_details_path(@merge_request)
= link_to "View details", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink" = link_to "View details", details_path, :"data-no-turbolink" => "data-no-turbolink"
.ci_widget .ci_widget
= icon("spinner spin") = icon("spinner spin")
......
- 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| = 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
......
- page_title "Milestones" - page_title "Milestones"
= render "header_title" = render "header_title"
= render 'shared/milestones_filter'
.gray-content-block
.pull-right .project-issuable-filter
- if can? current_user, :admin_milestone, @project .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 = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "pull-right btn btn-new", title: "New Milestone" do
%i.fa.fa-plus %i.fa.fa-plus
New Milestone New Milestone
.oneline
= render 'shared/milestones_filter'
.gray-content-block
Milestone allows you to group issues and set due date for it Milestone allows you to group issues and set due date for it
.milestones .milestones
......
.append-bottom-20 .gray-content-block.top-block.append-bottom-default
.tree-ref-holder
= render partial: 'shared/ref_switcher', locals: {destination: 'graph'} = render partial: 'shared/ref_switcher', locals: {destination: 'graph'}
.pull-right.visible-lg.light You can move around the graph by using the arrow keys.
.oneline
You can move around the graph by using the arrow keys.
...@@ -4,10 +4,11 @@ ...@@ -4,10 +4,11 @@
group members group members
%small %small
(#{members.count}) (#{members.count})
- if can?(current_user, :admin_group_member, @group)
.pull-right .pull-right
= link_to group_group_members_path(@group), class: 'btn' do = link_to group_group_members_path(@group), class: 'btn' do
= icon('pencil-square-o') = icon('pencil-square-o')
Edit group members Manage group members
%ul.content-list %ul.content-list
- members.each do |member| - members.each do |member|
= render 'groups/group_members/group_member', member: member, show_controls: false = render 'groups/group_members/group_member', member: member, show_controls: false
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
%p.light Keep stable branches secure and force developers to use Merge Requests %p.light Keep stable branches secure and force developers to use Merge Requests
%hr %hr
.well.append-bottom-20 .well
%p Protected branches are designed to %p Protected branches are designed to
%ul %ul
%li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"} %li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"}
......
...@@ -11,11 +11,17 @@ ...@@ -11,11 +11,17 @@
= strip_gpg_signature(tag.message) = strip_gpg_signature(tag.message)
.controls .controls
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn-grouped btn' do - if can?(current_user, :download_code, @project)
= icon("pencil")
- if can? current_user, :download_code, @project
= render 'projects/tags/download', ref: tag.name, project: @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 - if commit
= render 'projects/branches/commit', commit: commit, project: @project = render 'projects/branches/commit', commit: commit, project: @project
- else - else
......
...@@ -5,17 +5,17 @@ ...@@ -5,17 +5,17 @@
.gray-content-block .gray-content-block
.pull-right .pull-right
- if can?(current_user, :push_code, @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', 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") = 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') = 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') = icon('history')
- if can? current_user, :download_code, @project - if can? current_user, :download_code, @project
= render 'projects/tags/download', ref: @tag.name, project: @project = render 'projects/tags/download', ref: @tag.name, project: @project
- if can?(current_user, :admin_project, @project) - if can?(current_user, :admin_project, @project)
.pull-right .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 %i.fa.fa-trash-o
.title .title
%strong= @tag.name %strong= @tag.name
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
- if allowed_tree_edit? - if allowed_tree_edit?
%li %li
%span.dropdown %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') = icon('plus')
%ul.dropdown-menu %ul.dropdown-menu
%li %li
......
...@@ -6,12 +6,11 @@ ...@@ -6,12 +6,11 @@
.col-sm-10 .col-sm-10
= text_field_tag 'new_branch', @new_branch || @ref, required: true, class: "form-control js-new-branch" = text_field_tag 'new_branch', @new_branch || @ref, required: true, class: "form-control js-new-branch"
.form-group.js-create-merge-request-form-group .js-create-merge-request-container
.col-sm-offset-2.col-sm-10
.checkbox .checkbox
- nonce = SecureRandom.hex - nonce = SecureRandom.hex
= label_tag "create_merge_request-#{nonce}" do = 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}" = 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 Start a <strong>new merge request</strong> with these changes
= hidden_field_tag 'original_branch', @ref, class: 'js-original-branch' = hidden_field_tag 'original_branch', @ref, class: 'js-original-branch'
- 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()
.participants .participants
%span #{@participants.count} participants %span
= pluralize @participants.count, "participant"
- @participants.each do |participant| - @participants.each do |participant|
= link_to_member(@project, participant, name: false, size: 24) = link_to_member(@project, participant, name: false, size: 24)
...@@ -73,7 +73,7 @@ ...@@ -73,7 +73,7 @@
.user-calendar-activities .user-calendar-activities
%ul.center-middle-menu %ul.center-top-menu.no-top.no-bottom.bottom-border
%li.active %li.active
= link_to "#activity", 'data-toggle' => 'tab' do = link_to "#activity", 'data-toggle' => 'tab' do
Activity Activity
......
#!/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
...@@ -16,7 +16,7 @@ OmniAuth.config.allowed_request_methods = [:post] ...@@ -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) #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.allowed_request_methods << :get if Gitlab.config.omniauth.auto_sign_in_with_provider.present?
OmniAuth.config.before_request_phase do |env| OmniAuth.config.before_request_phase do |env|
OmniAuth::RequestForgeryProtection.new(env).call OmniAuth::RequestForgeryProtection.call(env)
end end
if Gitlab.config.omniauth.enabled if Gitlab.config.omniauth.enabled
......
...@@ -500,6 +500,7 @@ Rails.application.routes.draw do ...@@ -500,6 +500,7 @@ Rails.application.routes.draw do
member do member do
get :commits get :commits
get :ci get :ci
get :languages
end end
end end
......
# Groups # Groups
## List project groups ## List groups
Get a list of groups. (As user: my groups, as admin: all groups) Get a list of groups. (As user: my groups, as admin: all groups)
...@@ -21,6 +21,70 @@ GET /groups ...@@ -21,6 +21,70 @@ GET /groups
You can search for groups by name or path, see below. 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 ## Details of a group
Get all details of a group. Get all details of a group.
......
...@@ -13,6 +13,12 @@ An LDAP user who is allowed to change their email on the LDAP server can [take o ...@@ -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. 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 ## 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`. 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`.
......
# 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
```
# 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.
...@@ -18,3 +18,8 @@ Feature: Project Graph ...@@ -18,3 +18,8 @@ Feature: Project Graph
Given project "Shop" has CI enabled Given project "Shop" has CI enabled
When I visit project "Shop" CI graph page When I visit project "Shop" CI graph page
Then page should have CI graphs 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
...@@ -221,3 +221,9 @@ Feature: Project Source Browse Files ...@@ -221,3 +221,9 @@ Feature: Project Source Browse Files
Given I switch ref to fix Given I switch ref to fix
And I visit the fix tree And I visit the fix tree
Then I see the commit data for a directory with a leading dot Then I see the commit data for a directory with a leading dot
Scenario: I browse LFS object
Given I click on "files/lfs/lfs_object.iso" file in repo
Then I should see download link and object size
And I should not see lfs pointer details
And I should see buttons for allowed commands
...@@ -14,6 +14,15 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps ...@@ -14,6 +14,15 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps
visit commits_namespace_project_graph_path(project.namespace, project, "master") visit commits_namespace_project_graph_path(project.namespace, project, "master")
end 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 step 'page should have commits graphs' do
expect(page).to have_content "Commit statistics for master" expect(page).to have_content "Commit statistics for master"
expect(page).to have_content "Commits per day of month" expect(page).to have_content "Commits per day of month"
......
...@@ -305,6 +305,33 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -305,6 +305,33 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
expect(page).not_to have_content('Loading commit data...') expect(page).not_to have_content('Loading commit data...')
end end
step 'I click on "files/lfs/lfs_object.iso" file in repo' do
visit namespace_project_tree_path(@project.namespace, @project, "lfs")
click_link 'files'
click_link "lfs"
click_link "lfs_object.iso"
end
step 'I should see download link and object size' do
expect(page).to have_content 'Download (1.5 MB)'
end
step 'I should not see lfs pointer details' do
expect(page).not_to have_content 'version https://git-lfs.github.com/spec/v1'
expect(page).not_to have_content 'oid sha256:91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897'
expect(page).not_to have_content 'size 1575078'
end
step 'I should see buttons for allowed commands' do
expect(page).to have_content 'Raw'
expect(page).to have_content 'History'
expect(page).to have_content 'Permalink'
expect(page).not_to have_content 'Edit'
expect(page).not_to have_content 'Blame'
expect(page).not_to have_content 'Delete'
expect(page).not_to have_content 'Replace'
end
private private
def set_new_content def set_new_content
......
...@@ -65,6 +65,18 @@ module API ...@@ -65,6 +65,18 @@ module API
DestroyGroupService.new(group, current_user).execute DestroyGroupService.new(group, current_user).execute
end 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 # Transfer a project to the Group namespace
# #
# Parameters: # Parameters:
......
...@@ -37,13 +37,15 @@ module Gitlab ...@@ -37,13 +37,15 @@ module Gitlab
# Block user in GitLab if he/she was blocked in AD # 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) if Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter)
user.block unless user.blocked? user.block
false false
else else
user.activate if user.blocked? && !ldap_config.block_auto_created_users user.activate if user.blocked? && !ldap_config.block_auto_created_users
true true
end end
else else
# Block the user if they no longer exist in LDAP/AD
user.block
false false
end end
rescue rescue
......
...@@ -220,7 +220,7 @@ module Gitlab ...@@ -220,7 +220,7 @@ module Gitlab
def storage_project(project) def storage_project(project)
if project.forked? if project.forked?
project.forked_from_project storage_project(project.forked_from_project)
else else
project project
end end
......
# Protects OmniAuth request phase against CSRF. # Protects OmniAuth request phase against CSRF.
module OmniAuth module OmniAuth
# Based on ActionController::RequestForgeryProtection. module RequestForgeryProtection
class RequestForgeryProtection class Controller < ActionController::Base
def initialize(env) protect_from_forgery with: :exception
@env = env
end
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
end
end
private
def protect_against_forgery? def index
ApplicationController.allow_forgery_protection head :ok
end 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 end
def handle_unverified_request def self.app
forgery_protection_strategy.new(self).handle_unverified_request @app ||= Controller.action(:index)
end end
# Sets the token value for the current session. def self.call(env)
def form_authenticity_token app.call(env)
session[:_csrf_token] ||= SecureRandom.base64(32)
end end
end end
end end
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
...@@ -64,6 +64,8 @@ namespace :gitlab do ...@@ -64,6 +64,8 @@ namespace :gitlab do
if project.persisted? if project.persisted?
puts " * Created #{project.name} (#{repo_path})".green puts " * Created #{project.name} (#{repo_path})".green
project.update_repository_size
project.update_commit_count
else else
puts " * Failed trying to create #{project.name} (#{repo_path})".red puts " * Failed trying to create #{project.name} (#{repo_path})".red
puts " Errors: #{project.errors.messages}".red puts " Errors: #{project.errors.messages}".red
......
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
...@@ -118,4 +118,12 @@ namespace :gitlab do ...@@ -118,4 +118,12 @@ namespace :gitlab do
false false
end end
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 end
...@@ -33,5 +33,39 @@ describe Projects::RawController do ...@@ -33,5 +33,39 @@ describe Projects::RawController do
expect(response.header['Content-Type']).to eq('image/jpeg') expect(response.header['Content-Type']).to eq('image/jpeg')
end end
end end
context 'lfs object' do
let(:id) { 'be93687/files/lfs/lfs_object.iso' }
let!(:lfs_object) { create(:lfs_object, oid: '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', size: '1575078') }
context 'when project has access' do
before do
public_project.lfs_objects << lfs_object
allow_any_instance_of(LfsObjectUploader).to receive(:exists?).and_return(true)
allow(controller).to receive(:send_file) { controller.render nothing: true }
end
it 'serves the file' do
expect(controller).to receive(:send_file).with("#{Gitlab.config.shared.path}/lfs-objects/91/ef/f75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", filename: "lfs_object.iso", disposition: 'attachment')
get(:show,
namespace_id: public_project.namespace.to_param,
project_id: public_project.to_param,
id: id)
expect(response.status).to eq(200)
end
end
context 'when project does not have access' do
it 'does not serve the file' do
get(:show,
namespace_id: public_project.namespace.to_param,
project_id: public_project.to_param,
id: id)
expect(response.status).to eq(404)
end
end
end
end end
end end
...@@ -19,7 +19,7 @@ describe "Builds" do ...@@ -19,7 +19,7 @@ describe "Builds" do
end end
it { expect(page).to have_content 'Running' } 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.short_sha }
it { expect(page).to have_content @build.ref } it { expect(page).to have_content @build.ref }
it { expect(page).to have_content @build.name } it { expect(page).to have_content @build.name }
...@@ -32,7 +32,7 @@ describe "Builds" do ...@@ -32,7 +32,7 @@ describe "Builds" do
end end
it { expect(page).to have_content 'No builds to show' } 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 end
context "All builds" do context "All builds" do
...@@ -45,7 +45,7 @@ describe "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.short_sha }
it { expect(page).to have_content @build.ref } it { expect(page).to have_content @build.ref }
it { expect(page).to have_content @build.name } 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
end end
...@@ -53,11 +53,11 @@ describe "Builds" do ...@@ -53,11 +53,11 @@ describe "Builds" do
before do before do
@build.run! @build.run!
visit namespace_project_builds_path(@gl_project.namespace, @gl_project) visit namespace_project_builds_path(@gl_project.namespace, @gl_project)
click_link "Cancel all" click_link "Cancel running"
end end
it { expect(page).to have_content 'No builds to show' } 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 end
describe "GET /:project/builds/:id" do describe "GET /:project/builds/:id" do
......
%ul.nav.nav-tabs.merge-request-tabs %ul.nav.nav-tabs.merge-request-tabs
%li.notes-tab %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 Discussion
%li.commits-tab %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 Commits
%li.diffs-tab %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 Diffs
.tab-content .tab-content
......
...@@ -13,6 +13,11 @@ describe Gitlab::LDAP::Access do ...@@ -13,6 +13,11 @@ describe Gitlab::LDAP::Access do
end end
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
it 'should block user in GitLab' do
access.allowed?
expect(user).to be_blocked
end
end end
context 'when the user is found' do context 'when the user is found' do
......
...@@ -10,6 +10,8 @@ describe API::API, api: true do ...@@ -10,6 +10,8 @@ describe API::API, api: true do
let(:avatar_file_path) { File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') } let(:avatar_file_path) { File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') }
let!(:group1) { create(:group, avatar: File.open(avatar_file_path)) } let!(:group1) { create(:group, avatar: File.open(avatar_file_path)) }
let!(:group2) { create(:group) } let!(:group2) { create(:group) }
let!(:project1) { create(:project, namespace: group1) }
let!(:project2) { create(:project, namespace: group2) }
before do before do
group1.add_owner(user1) group1.add_owner(user1)
...@@ -67,7 +69,7 @@ describe API::API, api: true do ...@@ -67,7 +69,7 @@ describe API::API, api: true do
it "should return any existing group" do it "should return any existing group" do
get api("/groups/#{group2.id}", admin) get api("/groups/#{group2.id}", admin)
expect(response.status).to eq(200) expect(response.status).to eq(200)
json_response['name'] == group2.name expect(json_response['name']).to eq(group2.name)
end end
it "should not return a non existing group" do it "should not return a non existing group" do
...@@ -80,7 +82,7 @@ describe API::API, api: true do ...@@ -80,7 +82,7 @@ describe API::API, api: true do
it 'should return any existing group' do it 'should return any existing group' do
get api("/groups/#{group1.path}", admin) get api("/groups/#{group1.path}", admin)
expect(response.status).to eq(200) expect(response.status).to eq(200)
json_response['name'] == group2.name expect(json_response['name']).to eq(group1.name)
end end
it 'should not return a non existing group' do it 'should not return a non existing group' do
...@@ -95,6 +97,59 @@ describe API::API, api: true do ...@@ -95,6 +97,59 @@ describe API::API, api: true do
end end
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 describe "POST /groups" do
context "when authenticated as user without group permissions" do context "when authenticated as user without group permissions" do
it "should not create group" do it "should not create group" do
......
...@@ -12,6 +12,7 @@ module TestEnv ...@@ -12,6 +12,7 @@ module TestEnv
'fix' => '48f0be4', 'fix' => '48f0be4',
'improve/awesome' => '5937ac0', 'improve/awesome' => '5937ac0',
'markdown' => '0ed8c6c', 'markdown' => '0ed8c6c',
'lfs' => 'be93687',
'master' => '5937ac0', 'master' => '5937ac0',
"'test'" => 'e56497b', "'test'" => 'e56497b',
} }
......
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