Commit ef8d96e5 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'ce-to-ee-2' of dev.gitlab.org:gitlab/gitlabhq into ce-to-ee

Signed-off-by: default avatarDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>

Conflicts:
	LICENSE
	app/assets/javascripts/dispatcher.js.coffee
	app/controllers/help_controller.rb
	app/models/user.rb
	app/views/groups/group_members/_new_group_member.html.haml
	config/gitlab.yml.example
	config/routes.rb
	features/groups.feature
	features/steps/dashboard/help.rb
parents 9311f862 9925051e
...@@ -5,10 +5,7 @@ v 7.10.0 (unreleased) ...@@ -5,10 +5,7 @@ v 7.10.0 (unreleased)
- Set Application controller default URL options to ensure all url_for calls are consistent (Stan Hu) - Set Application controller default URL options to ensure all url_for calls are consistent (Stan Hu)
- Allow HTML tags in Markdown input - Allow HTML tags in Markdown input
- Fix code unfold not working on Compare commits page (Stan Hu) - Fix code unfold not working on Compare commits page (Stan Hu)
- Include missing events and fix save functionality in admin service template settings form (Stan Hu)
- Fix "Import projects from" button to show the correct instructions (Stan Hu)
- Fix dots in Wiki slugs causing errors (Stan Hu) - Fix dots in Wiki slugs causing errors (Stan Hu)
- Fix OAuth2 issue importing a new project from GitHub and GitLab (Stan Hu)
- Update poltergeist to version 1.6.0 to support PhantomJS 2.0 (Zeger-Jan van de Weg) - Update poltergeist to version 1.6.0 to support PhantomJS 2.0 (Zeger-Jan van de Weg)
- Fix cross references when usernames, milestones, or project names contain underscores (Stan Hu) - Fix cross references when usernames, milestones, or project names contain underscores (Stan Hu)
- Disable reference creation for comments surrounded by code/preformatted blocks (Stan Hu) - Disable reference creation for comments surrounded by code/preformatted blocks (Stan Hu)
...@@ -17,6 +14,7 @@ v 7.10.0 (unreleased) ...@@ -17,6 +14,7 @@ v 7.10.0 (unreleased)
- extend the commit calendar to show the actual commits made on a date (Hannes Rosenögger) - extend the commit calendar to show the actual commits made on a date (Hannes Rosenögger)
- Fix a link in the patch update guide - Fix a link in the patch update guide
- Add a service to support external wikis (Hannes Rosenögger) - Add a service to support external wikis (Hannes Rosenögger)
- Omit the "email patches" link and fix plain diff view for merge commits
- List new commits for newly pushed branch in activity view. - List new commits for newly pushed branch in activity view.
- Add sidetiq gem dependency to match EE - Add sidetiq gem dependency to match EE
- Add changelog, license and contribution guide links to project sidebar. - Add changelog, license and contribution guide links to project sidebar.
...@@ -24,6 +22,7 @@ v 7.10.0 (unreleased) ...@@ -24,6 +22,7 @@ v 7.10.0 (unreleased)
- Fix alignment of navbar toggle button (Cody Mize) - Fix alignment of navbar toggle button (Cody Mize)
- Fix checkbox rendering for nested task lists - Fix checkbox rendering for nested task lists
- Identical look of selectboxes in UI - Identical look of selectboxes in UI
- Upgrade the gitlab_git gem to version 7.1.3
- Move "Import existing repository by URL" option to button. - Move "Import existing repository by URL" option to button.
- Improve error message when save profile has error. - Improve error message when save profile has error.
- Passing the name of pushed ref to CI service (requires GitLab CI 7.9+) - Passing the name of pushed ref to CI service (requires GitLab CI 7.9+)
...@@ -40,7 +39,6 @@ v 7.10.0 (unreleased) ...@@ -40,7 +39,6 @@ v 7.10.0 (unreleased)
v 7.9.0 (unreleased) v 7.9.0 (unreleased)
- Replace commits calendar with faster contribution calendar that includes issues and merge requests - Replace commits calendar with faster contribution calendar that includes issues and merge requests
- Add inifinite scroll to user page activity - Add inifinite scroll to user page activity
- Don't show commit comment button when user is not signed in.
- Don't include system notes in issue/MR comment count. - Don't include system notes in issue/MR comment count.
- Don't mark merge request as updated when merge status relative to target branch changes. - Don't mark merge request as updated when merge status relative to target branch changes.
- Link note avatar to user. - Link note avatar to user.
...@@ -50,13 +48,31 @@ v 7.9.0 (unreleased) ...@@ -50,13 +48,31 @@ v 7.9.0 (unreleased)
- AJAX selectbox for issue assignee and author filters - AJAX selectbox for issue assignee and author filters
- Fix issue with missing options in issue filtering dropdown if selected one - Fix issue with missing options in issue filtering dropdown if selected one
- Prevent holding Control-Enter or Command-Enter from posting comment multiple times. - Prevent holding Control-Enter or Command-Enter from posting comment multiple times.
- Prevent note form from being cleared when submitting failed.
- Improve file icons rendering on tree (Sullivan Sénéchal) - Improve file icons rendering on tree (Sullivan Sénéchal)
- API: Add pagination to project events
- Get issue links in notification mail to work again.
- Don't show commit comment button when user is not signed in.
- Fix admin user projects lists.
- Don't leak private group existence by redirecting from namespace controller to group controller.
- Ability to skip some items from backup (database, respositories or uploads)
- Fix "Hello @username." references not working by no longer allowing usernames to end in period.
- Archive repositories in background worker.
- Import GitHub, Bitbucket or GitLab.com projects owned by authenticated user into current namespace.
- Fix and improve help rendering (Sullivan Sénéchal)
v 7.9.0
- Send EmailsOnPush email when branch or tag is created or deleted. v 7.9.2
- Faster merge request processing for large repository - Contains no changes
- Prevent doubling AJAX request with each commit visit via Turbolink
- Prevent unnecessary doubling of js events on import pages and user calendar v 7.9.1
- Include missing events and fix save functionality in admin service template settings form (Stan Hu)
- Fix "Import projects from" button to show the correct instructions (Stan Hu)
- Fix OAuth2 issue importing a new project from GitHub and GitLab (Stan Hu)
- Fix for LDAP with commas in DN
- Fix missing events and in admin Slack service template settings form (Stan Hu)
- Don't show commit comment button when user is not signed in.
- Downgrade gemnasium-gitlab-service gem
v 7.9.0 v 7.9.0
- Add HipChat integration documentation (Stan Hu) - Add HipChat integration documentation (Stan Hu)
...@@ -138,6 +154,10 @@ v 7.9.0 ...@@ -138,6 +154,10 @@ v 7.9.0
- Fix invalid Atom feeds when using emoji, horizontal rules, or images (Christian Walther) - Fix invalid Atom feeds when using emoji, horizontal rules, or images (Christian Walther)
- Backup of repositories with tar instead of git bundle (only now are git-annex files included in the backup) - Backup of repositories with tar instead of git bundle (only now are git-annex files included in the backup)
- Add canceled status for CI - Add canceled status for CI
- Send EmailsOnPush email when branch or tag is created or deleted.
- Faster merge request processing for large repository
- Prevent doubling AJAX request with each commit visit via Turbolink
- Prevent unnecessary doubling of js events on import pages and user calendar
v 7.8.4 v 7.8.4
- Fix issue_tracker_id substitution in custom issue trackers - Fix issue_tracker_id substitution in custom issue trackers
......
...@@ -39,7 +39,7 @@ gem "browser" ...@@ -39,7 +39,7 @@ gem "browser"
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
gem "gitlab_git", '~> 7.1.2' gem "gitlab_git", '~> 7.1.3'
# Ruby/Rack Git Smart-HTTP Server Handler # Ruby/Rack Git Smart-HTTP Server Handler
gem 'gitlab-grack', '~> 2.0.0.rc2', require: 'grack' gem 'gitlab-grack', '~> 2.0.0.rc2', require: 'grack'
...@@ -116,13 +116,14 @@ end ...@@ -116,13 +116,14 @@ end
gem "state_machine" gem "state_machine"
# Issue tags # Issue tags
gem "acts-as-taggable-on" gem 'acts-as-taggable-on', '~> 3.4'
# Background jobs # Background jobs
gem 'slim' gem 'slim'
gem 'sinatra', require: nil gem 'sinatra', require: nil
gem 'sidekiq', '~> 3.3' gem 'sidekiq', '~> 3.3'
gem 'sidetiq', '0.6.3' gem 'sidetiq', '0.6.3'
gem 'sidekiq-limit_fetch'
# HTTP requests # HTTP requests
gem "httparty" gem "httparty"
......
...@@ -33,8 +33,8 @@ GEM ...@@ -33,8 +33,8 @@ GEM
minitest (~> 5.1) minitest (~> 5.1)
thread_safe (~> 0.1) thread_safe (~> 0.1)
tzinfo (~> 1.1) tzinfo (~> 1.1)
acts-as-taggable-on (2.4.1) acts-as-taggable-on (3.5.0)
rails (>= 3, < 5) activerecord (>= 3.2, < 5)
addressable (2.3.5) addressable (2.3.5)
annotate (2.6.0) annotate (2.6.0)
activerecord (>= 2.3.0) activerecord (>= 2.3.0)
...@@ -212,7 +212,7 @@ GEM ...@@ -212,7 +212,7 @@ GEM
mime-types (~> 1.19) mime-types (~> 1.19)
gitlab_emoji (0.1.0) gitlab_emoji (0.1.0)
gemojione (~> 2.0) gemojione (~> 2.0)
gitlab_git (7.1.2) gitlab_git (7.1.3)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.6) charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0) gitlab-linguist (~> 3.0)
...@@ -547,6 +547,8 @@ GEM ...@@ -547,6 +547,8 @@ GEM
json json
redis (>= 3.0.6) redis (>= 3.0.6)
redis-namespace (>= 1.3.1) redis-namespace (>= 1.3.1)
sidekiq-limit_fetch (2.4.1)
sidekiq (>= 2.6.5, < 4.0)
sidetiq (0.6.3) sidetiq (0.6.3)
celluloid (>= 0.14.1) celluloid (>= 0.14.1)
ice_cube (= 0.11.1) ice_cube (= 0.11.1)
...@@ -662,7 +664,7 @@ PLATFORMS ...@@ -662,7 +664,7 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
RedCloth RedCloth
ace-rails-ap ace-rails-ap
acts-as-taggable-on acts-as-taggable-on (~> 3.4)
addressable addressable
annotate (~> 2.6.0.beta2) annotate (~> 2.6.0.beta2)
asana (~> 0.0.6) asana (~> 0.0.6)
...@@ -703,7 +705,7 @@ DEPENDENCIES ...@@ -703,7 +705,7 @@ DEPENDENCIES
gitlab-grack (~> 2.0.0.rc2) gitlab-grack (~> 2.0.0.rc2)
gitlab-linguist (~> 3.0.1) gitlab-linguist (~> 3.0.1)
gitlab_emoji (~> 0.1) gitlab_emoji (~> 0.1)
gitlab_git (~> 7.1.2) gitlab_git (~> 7.1.3)
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.0.2)
...@@ -770,6 +772,7 @@ DEPENDENCIES ...@@ -770,6 +772,7 @@ DEPENDENCIES
settingslogic settingslogic
shoulda-matchers (~> 2.7.0) shoulda-matchers (~> 2.7.0)
sidekiq (~> 3.3) sidekiq (~> 3.3)
sidekiq-limit_fetch
sidetiq (= 0.6.3) sidetiq (= 0.6.3)
simplecov simplecov
sinatra sinatra
......
The GitLab Enterprise Edition (EE) license The GitLab Enterprise Edition (EE) license
Copyright (c) 2011-2015 GitLab B.V.
Copyright (c) 2013-2014 GitLab B.V.
This software and associated documentation files (the "Software") can only be This software and associated documentation files (the "Software") can only be
used with a valid GitLab subscription for the correct number of users. You are used with a valid GitLab subscription for the correct number of users. You are
......
web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"} web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"}
worker: bundle exec sidekiq -q post_receive -q mailer -q system_hook -q project_web_hook -q gitlab_shell -q common -q default worker: bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q common -q default
...@@ -20,9 +20,9 @@ class @Calendar ...@@ -20,9 +20,9 @@ class @Calendar
position: "top" position: "top"
legend: [ legend: [
0 0
1 10
4 20
7 30
] ]
legendCellPadding: 3 legendCellPadding: 3
onClick: (date, count) -> onClick: (date, count) ->
......
...@@ -104,6 +104,8 @@ class Dispatcher ...@@ -104,6 +104,8 @@ class Dispatcher
new GroupsSelect() new GroupsSelect()
when 'admin:emails:show' when 'admin:emails:show'
new AdminEmailSelect() new AdminEmailSelect()
when 'admin:users:show'
new ProjectsList()
switch path.first() switch path.first()
when 'admin' when 'admin'
......
...@@ -9,12 +9,8 @@ class @Issue ...@@ -9,12 +9,8 @@ class @Issue
if $("a.btn-close").length if $("a.btn-close").length
$("li.task-list-item input:checkbox").prop("disabled", false) $("li.task-list-item input:checkbox").prop("disabled", false)
$(".task-list-item input:checkbox").on( $('.task-list-item input:checkbox').off('change')
"click" $('.task-list-item input:checkbox').change('issue', updateTaskState)
null
"issue"
updateTaskState
)
$('.issue-details').waitForImages -> $('.issue-details').waitForImages ->
$('.issuable-affix').affix offset: $('.issuable-affix').affix offset:
......
...@@ -81,12 +81,8 @@ class @MergeRequest ...@@ -81,12 +81,8 @@ class @MergeRequest
this.$('.remove_source_branch_in_progress').hide() this.$('.remove_source_branch_in_progress').hide()
this.$('.remove_source_branch_widget.failed').show() this.$('.remove_source_branch_widget.failed').show()
$(".task-list-item input:checkbox").on( $('.task-list-item input:checkbox').off('change')
"click" $('.task-list-item input:checkbox').change('merge_request', updateTaskState)
null
"merge_request"
updateTaskState
)
activateTab: (action) -> activateTab: (action) ->
this.$('.merge-request-tabs li').removeClass 'active' this.$('.merge-request-tabs li').removeClass 'active'
......
...@@ -37,7 +37,8 @@ class @Notes ...@@ -37,7 +37,8 @@ class @Notes
$(document).on "click", ".js-note-attachment-delete", @removeAttachment $(document).on "click", ".js-note-attachment-delete", @removeAttachment
# reset main target form after submit # reset main target form after submit
$(document).on "ajax:complete", ".js-main-target-form", @resetMainTargetForm $(document).on "ajax:complete", ".js-main-target-form", @reenableTargetFormSubmitButton
$(document).on "ajax:success", ".js-main-target-form", @resetMainTargetForm
# update the file name when an attachment is selected # update the file name when an attachment is selected
$(document).on "change", ".js-note-attachment-input", @updateFormAttachment $(document).on "change", ".js-note-attachment-input", @updateFormAttachment
...@@ -71,6 +72,7 @@ class @Notes ...@@ -71,6 +72,7 @@ class @Notes
$(document).off "click", ".js-note-delete" $(document).off "click", ".js-note-delete"
$(document).off "click", ".js-note-attachment-delete" $(document).off "click", ".js-note-attachment-delete"
$(document).off "ajax:complete", ".js-main-target-form" $(document).off "ajax:complete", ".js-main-target-form"
$(document).off "ajax:success", ".js-main-target-form"
$(document).off "click", ".js-discussion-reply-button" $(document).off "click", ".js-discussion-reply-button"
$(document).off "click", ".js-add-diff-note-button" $(document).off "click", ".js-add-diff-note-button"
$(document).off "visibilitychange" $(document).off "visibilitychange"
...@@ -170,6 +172,11 @@ class @Notes ...@@ -170,6 +172,11 @@ class @Notes
form.find(".js-note-text").data("autosave").reset() form.find(".js-note-text").data("autosave").reset()
reenableTargetFormSubmitButton: ->
form = $(".js-main-target-form")
form.find(".js-note-text").trigger "input"
### ###
Shows the main form and does some setup on it. Shows the main form and does some setup on it.
......
...@@ -3,12 +3,7 @@ $(document).on("click", '.toggle-nav-collapse', (e) -> ...@@ -3,12 +3,7 @@ $(document).on("click", '.toggle-nav-collapse', (e) ->
collapsed = 'page-sidebar-collapsed' collapsed = 'page-sidebar-collapsed'
expanded = 'page-sidebar-expanded' expanded = 'page-sidebar-expanded'
if $('.page-with-sidebar').hasClass(collapsed) $('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
$('.page-with-sidebar').removeClass(collapsed).addClass(expanded) $('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
$('.toggle-nav-collapse i').removeClass('fa-angle-right').addClass('fa-angle-left') $.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
$.cookie("collapsed_nav", "false", { path: '/' })
else
$('.page-with-sidebar').removeClass(expanded).addClass(collapsed)
$('.toggle-nav-collapse i').removeClass('fa-angle-left').addClass('fa-angle-right')
$.cookie("collapsed_nav", "true", { path: '/' })
) )
...@@ -246,7 +246,7 @@ li.note { ...@@ -246,7 +246,7 @@ li.note {
.milestone { .milestone {
&.milestone-closed { &.milestone-closed {
background: #eee; background: #f9f9f9;
} }
.progress { .progress {
margin-bottom: 0; margin-bottom: 0;
......
...@@ -24,13 +24,6 @@ ...@@ -24,13 +24,6 @@
display: none !important; display: none !important;
} }
.project-home-panel {
.star-fork-buttons {
padding-top: 10px;
padding-right: 15px;
}
}
.project-home-links { .project-home-links {
display: none; display: none;
} }
......
...@@ -49,35 +49,20 @@ ...@@ -49,35 +49,20 @@
@extend .clearfix; @extend .clearfix;
margin-bottom: 15px; margin-bottom: 15px;
.project-home-desc, &.project-home-row-top {
.star-fork-buttons { margin-bottom: 15px;
}
.project-home-desc {
font-size: 16px; font-size: 16px;
line-height: 1.3; line-height: 1.3;
margin-right: 215px;
} }
.project-home-desc { .project-home-desc {
float: left; float: left;
color: #666; color: #666;
} }
.star-fork-buttons {
float: right;
min-width: 200px;
font-weight: bold;
.star-buttons, .fork-buttons {
float: right;
margin-left: 20px;
a:hover {
text-decoration: none;
}
.count {
margin-left: 5px;
}
}
}
} }
.visibility-level-label { .visibility-level-label {
...@@ -87,6 +72,27 @@ ...@@ -87,6 +72,27 @@
color: inherit; color: inherit;
} }
} }
.project-repo-buttons {
margin-top: -3px;
position: absolute;
right: 0;
width: 260px;
text-align: right;
.btn {
font-weight: bold;
font-size: 14px;
line-height: 16px;
.count {
padding-left: 10px;
border-left: 1px solid #ccc;
display: inline-block;
margin-left: 10px;
}
}
}
} }
.project-home-links { .project-home-links {
......
...@@ -3,21 +3,35 @@ class HelpController < ApplicationController ...@@ -3,21 +3,35 @@ class HelpController < ApplicationController
end end
def show def show
@category = params[:category] @filepath = params[:filepath]
@file = params[:file] @format = params[:format]
format = params[:format] || 'md'
file_path = Rails.root.join('doc', @category, @file + ".#{format}")
if %w(png jpg jpeg gif).include?(format) respond_to do |format|
send_file file_path, disposition: 'inline' format.md { render_doc }
elsif File.exists?(file_path) format.all { send_file_data }
render 'show' end
end
def shortcuts
end
private
def render_doc
if File.exists?(Rails.root.join('doc', @filepath + '.md'))
render 'show.html.haml'
else else
not_found! not_found!
end end
end end
def shortcuts def send_file_data
path = Rails.root.join('doc', "#{@filepath}.#{@format}")
if File.exists?(path)
send_file(path, disposition: 'inline')
else
head :not_found
end
end end
def ui def ui
......
...@@ -36,8 +36,11 @@ class Import::BitbucketController < Import::BaseController ...@@ -36,8 +36,11 @@ class Import::BitbucketController < Import::BaseController
def create def create
@repo_id = params[:repo_id] || "" @repo_id = params[:repo_id] || ""
repo = client.project(@repo_id.gsub("___", "/")) repo = client.project(@repo_id.gsub("___", "/"))
@target_namespace = params[:new_namespace].presence || repo["owner"]
@project_name = repo["slug"] @project_name = repo["slug"]
repo_owner = repo["owner"]
repo_owner = current_user.username if repo_owner == client.user["user"]["username"]
@target_namespace = params[:new_namespace].presence || repo_owner
namespace = get_or_create_namespace || (render and return) namespace = get_or_create_namespace || (render and return)
......
...@@ -31,8 +31,11 @@ class Import::GithubController < Import::BaseController ...@@ -31,8 +31,11 @@ class Import::GithubController < Import::BaseController
def create def create
@repo_id = params[:repo_id].to_i @repo_id = params[:repo_id].to_i
repo = client.repo(@repo_id) repo = client.repo(@repo_id)
@target_namespace = params[:new_namespace].presence || repo.owner.login
@project_name = repo.name @project_name = repo.name
repo_owner = repo.owner.login
repo_owner = current_user.username if repo_owner == client.user.login
@target_namespace = params[:new_namespace].presence || repo_owner
namespace = get_or_create_namespace || (render and return) namespace = get_or_create_namespace || (render and return)
......
...@@ -28,8 +28,11 @@ class Import::GitlabController < Import::BaseController ...@@ -28,8 +28,11 @@ class Import::GitlabController < Import::BaseController
def create def create
@repo_id = params[:repo_id].to_i @repo_id = params[:repo_id].to_i
repo = client.project(@repo_id) repo = client.project(@repo_id)
@target_namespace = params[:new_namespace].presence || repo["namespace"]["path"]
@project_name = repo["name"] @project_name = repo["name"]
repo_owner = repo["namespace"]["path"]
repo_owner = current_user.username if repo_owner == client.user["username"]
@target_namespace = params[:new_namespace].presence || repo_owner
namespace = get_or_create_namespace || (render and return) namespace = get_or_create_namespace || (render and return)
......
...@@ -4,14 +4,22 @@ class NamespacesController < ApplicationController ...@@ -4,14 +4,22 @@ class NamespacesController < ApplicationController
def show def show
namespace = Namespace.find_by(path: params[:id]) namespace = Namespace.find_by(path: params[:id])
unless namespace if namespace
return render_404 if namespace.is_a?(Group)
group = namespace
else
user = namespace.owner
end
end end
if namespace.type == "Group" if user
redirect_to group_path(namespace) redirect_to user_path(user)
elsif group && can?(current_user, :read_group, group)
redirect_to group_path(group)
elsif current_user.nil?
authenticate_user!
else else
redirect_to user_path(namespace.owner) render_404
end end
end end
end end
...@@ -11,18 +11,18 @@ class Projects::RepositoriesController < Projects::ApplicationController ...@@ -11,18 +11,18 @@ class Projects::RepositoriesController < Projects::ApplicationController
end end
def archive def archive
unless can?(current_user, :download_code, @project) begin
render_404 and return file_path = ArchiveRepositoryService.new(@project, params[:ref], params[:format]).execute
rescue
return head :not_found
end end
file_path = ArchiveRepositoryService.new.execute(@project, params[:ref], params[:format])
if file_path if file_path
# Send file to user # Send file to user
response.headers["Content-Length"] = File.open(file_path).size.to_s response.headers["Content-Length"] = File.open(file_path).size.to_s
send_file file_path send_file file_path
else else
render_404 redirect_to request.fullpath
end end
end end
end end
...@@ -29,6 +29,10 @@ module GitlabRoutingHelper ...@@ -29,6 +29,10 @@ module GitlabRoutingHelper
namespace_project_merge_request_path(entity.project.namespace, entity.project, entity, *args) namespace_project_merge_request_path(entity.project.namespace, entity.project, entity, *args)
end end
def milestone_path(entity, *args)
namespace_project_milestone_path(entity.project.namespace, entity.project, entity, *args)
end
def project_url(project, *args) def project_url(project, *args)
namespace_project_url(project.namespace, project, *args) namespace_project_url(project.namespace, project, *args)
end end
......
...@@ -40,7 +40,7 @@ module IconsHelper ...@@ -40,7 +40,7 @@ module IconsHelper
def file_type_icon_class(type, mode, name) def file_type_icon_class(type, mode, name)
if type == 'folder' if type == 'folder'
icon_class = 'folder' icon_class = 'folder'
elsif mode == 0120000 elsif mode == '120000'
icon_class = 'share' icon_class = 'share'
else else
# Guess which icon to choose based on file extension. # Guess which icon to choose based on file extension.
......
...@@ -13,22 +13,34 @@ module IssuesHelper ...@@ -13,22 +13,34 @@ module IssuesHelper
OpenStruct.new(id: 0, title: 'None (backlog)', name: 'Unassigned') OpenStruct.new(id: 0, title: 'None (backlog)', name: 'Unassigned')
end end
def url_for_project_issues(project = @project) def url_for_project_issues(project = @project, options = {})
return '' if project.nil? return '' if project.nil?
project.issues_tracker.project_url if options[:only_path]
project.issues_tracker.project_path
else
project.issues_tracker.project_url
end
end end
def url_for_new_issue(project = @project) def url_for_new_issue(project = @project, options = {})
return '' if project.nil? return '' if project.nil?
project.issues_tracker.new_issue_url if options[:only_path]
project.issues_tracker.new_issue_path
else
project.issues_tracker.new_issue_url
end
end end
def url_for_issue(issue_iid, project = @project) def url_for_issue(issue_iid, project = @project, options = {})
return '' if project.nil? return '' if project.nil?
project.issues_tracker.issue_url(issue_iid) if options[:only_path]
project.issues_tracker.issue_path(issue_iid)
else
project.issues_tracker.issue_url(issue_iid)
end
end end
def title_for_issue(issue_iid, project = @project) def title_for_issue(issue_iid, project = @project)
......
...@@ -80,17 +80,17 @@ module ProjectsHelper ...@@ -80,17 +80,17 @@ module ProjectsHelper
@project.milestones.active.order("due_date, title ASC") @project.milestones.active.order("due_date, title ASC")
end end
def link_to_toggle_star(title, starred, signed_in) def link_to_toggle_star(title, starred)
cls = 'star-btn' cls = 'star-btn btn btn-sm btn-default'
cls << ' disabled' unless signed_in
toggle_text =
if starred
' Unstar'
else
' Star'
end
toggle_html = content_tag('span', class: 'toggle') do toggle_html = content_tag('span', class: 'toggle') do
toggle_text = if starred
' Unstar'
else
' Star'
end
icon('star') + toggle_text icon('star') + toggle_text
end end
...@@ -106,23 +106,25 @@ module ProjectsHelper ...@@ -106,23 +106,25 @@ module ProjectsHelper
data: { type: 'json' } data: { type: 'json' }
} }
path = toggle_star_namespace_project_path(@project.namespace, @project)
content_tag 'span', class: starred ? 'turn-on' : 'turn-off' do content_tag 'span', class: starred ? 'turn-on' : 'turn-off' do
link_to( link_to(path, link_opts) do
toggle_star_namespace_project_path(@project.namespace, @project),
link_opts
) do
toggle_html + ' ' + count_html toggle_html + ' ' + count_html
end end
end end
end end
def link_to_toggle_fork def link_to_toggle_fork
out = icon('code-fork') html = content_tag('span') do
out << ' Fork' icon('code-fork') + ' Fork'
out << content_tag(:span, class: 'count') do end
count_html = content_tag(:span, class: 'count') do
@project.forks_count.to_s @project.forks_count.to_s
end end
html + count_html
end end
private private
......
...@@ -52,7 +52,7 @@ module Mentionable ...@@ -52,7 +52,7 @@ module Mentionable
if identifier == "all" if identifier == "all"
users.push(*project.team.members.flatten) users.push(*project.team.members.flatten)
elsif namespace = Namespace.find_by(path: identifier) elsif namespace = Namespace.find_by(path: identifier)
if namespace.type == "Group" if namespace.is_a?(Group)
users.push(*namespace.users) users.push(*namespace.users)
else else
users << namespace.owner users << namespace.owner
......
...@@ -24,8 +24,8 @@ class Namespace < ActiveRecord::Base ...@@ -24,8 +24,8 @@ class Namespace < ActiveRecord::Base
validates :name, validates :name,
presence: true, uniqueness: true, presence: true, uniqueness: true,
length: { within: 0..255 }, length: { within: 0..255 },
format: { with: Gitlab::Regex.name_regex, format: { with: Gitlab::Regex.namespace_name_regex,
message: Gitlab::Regex.name_regex_message } message: Gitlab::Regex.namespace_name_regex_message }
validates :description, length: { within: 0..255 } validates :description, length: { within: 0..255 }
validates :path, validates :path,
...@@ -33,8 +33,8 @@ class Namespace < ActiveRecord::Base ...@@ -33,8 +33,8 @@ class Namespace < ActiveRecord::Base
presence: true, presence: true,
length: { within: 1..255 }, length: { within: 1..255 },
exclusion: { in: Gitlab::Blacklist.path }, exclusion: { in: Gitlab::Blacklist.path },
format: { with: Gitlab::Regex.path_regex, format: { with: Gitlab::Regex.namespace_regex,
message: Gitlab::Regex.path_regex_message } message: Gitlab::Regex.namespace_regex_message }
delegate :name, to: :owner, allow_nil: true, prefix: true delegate :name, to: :owner, allow_nil: true, prefix: true
...@@ -44,21 +44,36 @@ class Namespace < ActiveRecord::Base ...@@ -44,21 +44,36 @@ class Namespace < ActiveRecord::Base
scope :root, -> { where('type IS NULL') } scope :root, -> { where('type IS NULL') }
def self.by_path(path) class << self
where('lower(path) = :value', value: path.downcase).first def by_path(path)
end where('lower(path) = :value', value: path.downcase).first
end
# Case insensetive search for namespace by path or name # Case insensetive search for namespace by path or name
def self.find_by_path_or_name(path) def find_by_path_or_name(path)
find_by("lower(path) = :path OR lower(name) = :path", path: path.downcase) find_by("lower(path) = :path OR lower(name) = :path", path: path.downcase)
end end
def self.search(query) def search(query)
where("name LIKE :query OR path LIKE :query", query: "%#{query}%") where("name LIKE :query OR path LIKE :query", query: "%#{query}%")
end end
def clean_path(path)
path.gsub!(/@.*\z/, "")
path.gsub!(/\.git\z/, "")
path.gsub!(/\A-/, "")
path.gsub!(/.\z/, "")
path.gsub!(/[^a-zA-Z0-9_\-\.]/, "")
counter = 0
base = path
while Namespace.by_path(path).present?
counter += 1
path = "#{base}#{counter}"
end
def self.global_id path
'GLN' end
end end
def to_param def to_param
......
...@@ -130,12 +130,12 @@ class Project < ActiveRecord::Base ...@@ -130,12 +130,12 @@ class Project < ActiveRecord::Base
presence: true, presence: true,
length: { within: 0..255 }, length: { within: 0..255 },
format: { with: Gitlab::Regex.project_name_regex, format: { with: Gitlab::Regex.project_name_regex,
message: Gitlab::Regex.project_regex_message } message: Gitlab::Regex.project_name_regex_message }
validates :path, validates :path,
presence: true, presence: true,
length: { within: 0..255 }, length: { within: 0..255 },
format: { with: Gitlab::Regex.path_regex, format: { with: Gitlab::Regex.project_path_regex,
message: Gitlab::Regex.path_regex_message } message: Gitlab::Regex.project_path_regex_message }
validates :issues_enabled, :merge_requests_enabled, validates :issues_enabled, :merge_requests_enabled,
:wiki_enabled, inclusion: { in: [true, false] } :wiki_enabled, inclusion: { in: [true, false] }
validates :issues_tracker_id, length: { maximum: 255 }, allow_blank: true validates :issues_tracker_id, length: { maximum: 255 }, allow_blank: true
......
...@@ -20,8 +20,13 @@ ...@@ -20,8 +20,13 @@
class GitlabIssueTrackerService < IssueTrackerService class GitlabIssueTrackerService < IssueTrackerService
include Rails.application.routes.url_helpers include Rails.application.routes.url_helpers
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
default_url_options[:host] = Gitlab.config.gitlab.host
default_url_options[:protocol] = Gitlab.config.gitlab.protocol
default_url_options[:port] = Gitlab.config.gitlab.port unless Gitlab.config.gitlab_on_standard_port?
default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
def default? def default?
true true
...@@ -32,20 +37,26 @@ class GitlabIssueTrackerService < IssueTrackerService ...@@ -32,20 +37,26 @@ class GitlabIssueTrackerService < IssueTrackerService
end end
def project_url def project_url
"#{gitlab_url}#{namespace_project_issues_path(project.namespace, project)}" namespace_project_issues_url(project.namespace, project)
end end
def new_issue_url def new_issue_url
"#{gitlab_url}#{new_namespace_project_issue_path(namespace_id: project.namespace, project_id: project)}" new_namespace_project_issue_url(namespace_id: project.namespace, project_id: project)
end end
def issue_url(iid) def issue_url(iid)
"#{gitlab_url}#{namespace_project_issue_path(namespace_id: project.namespace, project_id: project, id: iid)}" namespace_project_issue_url(namespace_id: project.namespace, project_id: project, id: iid)
end end
private def project_path
namespace_project_issues_path(project.namespace, project)
end
def new_issue_path
new_namespace_project_issue_path(namespace_id: project.namespace, project_id: project)
end
def gitlab_url def issue_path(iid)
Gitlab.config.gitlab.relative_url_root.chomp("/") if Gitlab.config.gitlab.relative_url_root namespace_project_issue_path(namespace_id: project.namespace, project_id: project, id: iid)
end end
end end
...@@ -38,6 +38,18 @@ class IssueTrackerService < Service ...@@ -38,6 +38,18 @@ class IssueTrackerService < Service
self.issues_url.gsub(':id', iid.to_s) self.issues_url.gsub(':id', iid.to_s)
end end
def project_path
project_url
end
def new_issue_path
new_issue_url
end
def issue_path(iid)
issue_url(iid)
end
def fields def fields
[ [
{ type: 'text', name: 'description', placeholder: description }, { type: 'text', name: 'description', placeholder: description },
......
...@@ -267,6 +267,9 @@ class Repository ...@@ -267,6 +267,9 @@ class Repository
# Remove archives older than 2 hours # Remove archives older than 2 hours
def clean_old_archives def clean_old_archives
repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
return unless File.directory?(repository_downloads_path)
Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete)) Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
end end
......
...@@ -33,8 +33,8 @@ class Snippet < ActiveRecord::Base ...@@ -33,8 +33,8 @@ class Snippet < ActiveRecord::Base
validates :file_name, validates :file_name,
presence: true, presence: true,
length: { within: 0..255 }, length: { within: 0..255 },
format: { with: Gitlab::Regex.path_regex, format: { with: Gitlab::Regex.file_name_regex,
message: Gitlab::Regex.path_regex_message } message: Gitlab::Regex.file_name_regex_message }
validates :content, presence: true validates :content, presence: true
validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values } validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values }
......
...@@ -129,8 +129,8 @@ class User < ActiveRecord::Base ...@@ -129,8 +129,8 @@ class User < ActiveRecord::Base
presence: true, presence: true,
uniqueness: { case_sensitive: false }, uniqueness: { case_sensitive: false },
exclusion: { in: Gitlab::Blacklist.path }, exclusion: { in: Gitlab::Blacklist.path },
format: { with: Gitlab::Regex.username_regex, format: { with: Gitlab::Regex.namespace_regex,
message: Gitlab::Regex.username_regex_message } message: Gitlab::Regex.namespace_regex_message }
validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true
validate :namespace_uniq, if: ->(user) { user.username_changed? } validate :namespace_uniq, if: ->(user) { user.username_changed? }
...@@ -238,7 +238,7 @@ class User < ActiveRecord::Base ...@@ -238,7 +238,7 @@ class User < ActiveRecord::Base
def non_ldap def non_ldap
joins('LEFT JOIN identities ON identities.user_id = users.id'). joins('LEFT JOIN identities ON identities.user_id = users.id').
where('identities.provider IS NULL OR identities.provider NOT LIKE ?', 'ldap%') where('identities.provider IS NULL OR identities.provider NOT LIKE ?', 'ldap%')
end end
def clean_username(username) def clean_username(username)
......
class ArchiveRepositoryService class ArchiveRepositoryService
def execute(project, ref, format) attr_reader :project, :ref, :format
storage_path = Gitlab.config.gitlab.repository_downloads_path
unless File.directory?(storage_path) def initialize(project, ref, format)
FileUtils.mkdir_p(storage_path) format ||= 'tar.gz'
@project, @ref, @format = project, ref, format.downcase
end
def execute(options = {})
project.repository.clean_old_archives
raise "No archive file path" unless file_path
return file_path if archived?
unless archiving?
RepositoryArchiveWorker.perform_async(project.id, ref, format)
end end
format ||= 'tar.gz' archived = wait_until_archived(options[:timeout] || 5.0)
repository = project.repository
repository.clean_old_archives file_path if archived
repository.archive_repo(ref, storage_path, format.downcase) end
private
def storage_path
Gitlab.config.gitlab.repository_downloads_path
end
def file_path
@file_path ||= project.repository.archive_file_path(ref, storage_path, format)
end
def pid_file_path
@pid_file_path ||= project.repository.archive_pid_file_path(ref, storage_path, format)
end
def archived?
File.exist?(file_path)
end
def archiving?
File.exist?(pid_file_path)
end
def wait_until_archived(timeout = 5.0)
return archived? if timeout == 0.0
t1 = Time.now
begin
sleep 0.1
success = archived?
t2 = Time.now
end until success || t2 - t1 >= timeout
success
end end
end end
...@@ -16,10 +16,10 @@ module Files ...@@ -16,10 +16,10 @@ module Files
file_name = File.basename(path) file_name = File.basename(path)
file_path = path file_path = path
unless file_name =~ Gitlab::Regex.path_regex unless file_name =~ Gitlab::Regex.file_name_regex
return error( return error(
'Your changes could not be committed, because the file name ' + 'Your changes could not be committed, because the file name ' +
Gitlab::Regex.path_regex_message Gitlab::Regex.file_name_regex_message
) )
end end
......
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
%h4
= link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title)
.row
.col-sm-6
= link_to dashboard_milestone_path(milestone.safe_title, title: milestone.title) do
= pluralize milestone.issue_count, 'Issue'
&nbsp;
= link_to dashboard_milestone_path(milestone.safe_title, title: milestone.title) do
= pluralize milestone.merge_requests_count, 'Merge Request'
&nbsp;
%span.light #{milestone.percent_complete}% complete
.col-sm-6
= milestone_progress_bar(milestone)
%div
- milestone.milestones.each do |milestone|
= link_to milestone_path(milestone) do
%span.label.label-gray
= milestone.project.name_with_namespace
...@@ -16,23 +16,5 @@ ...@@ -16,23 +16,5 @@
.nothing-here-block No milestones to show .nothing-here-block No milestones to show
- else - else
- @dashboard_milestones.each do |milestone| - @dashboard_milestones.each do |milestone|
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } = render 'milestone', milestone: milestone
%h4
= link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title)
%div
%div
= link_to dashboard_milestone_path(milestone.safe_title, title: milestone.title) do
= pluralize milestone.issue_count, 'Issue'
&nbsp;
= link_to dashboard_milestone_path(milestone.safe_title, title: milestone.title) do
= pluralize milestone.merge_requests_count, 'Merge Request'
&nbsp;
%span.light #{milestone.percent_complete}% complete
= milestone_progress_bar(milestone)
%div
%br
- milestone.milestones.each do |milestone|
= link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) do
%span.label.label-default
= milestone.project.name_with_namespace
= paginate @dashboard_milestones, theme: "gitlab" = paginate @dashboard_milestones, theme: "gitlab"
= form_for @group_member, url: group_group_members_path(@group), html: { class: 'form-horizontal users-group-form' } do |f| = form_for @group_member, url: group_group_members_path(@group), html: { class: 'form-horizontal users-group-form' } do |f|
.form-group .form-group
= f.label :user_ids, "People", class: 'control-label' = f.label :user_ids, "People", class: 'control-label'
.col-sm-10= users_select_tag(:user_ids, { multiple: true, skip_ldap: @group.ldap_synced? , class: 'input-large' }) .col-sm-10= users_select_tag(:user_ids, multiple: true, skip_ldap: @group.ldap_synced?, class: 'input-large', scope: :all)
.form-group .form-group
= f.label :access_level, "Group Access", class: 'control-label' = f.label :access_level, "Group Access", class: 'control-label'
......
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
.pull-right
- if can?(current_user, :manage_group, @group)
- if milestone.closed?
= link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen"
- else
= link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close"
%h4
= link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title)
.row
.col-sm-6
= link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do
= pluralize milestone.issue_count, 'Issue'
&nbsp;
= link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do
= pluralize milestone.merge_requests_count, 'Merge Request'
&nbsp;
%span.light #{milestone.percent_complete}% complete
.col-sm-6
= milestone_progress_bar(milestone)
%div
- milestone.milestones.each do |milestone|
= link_to milestone_path(milestone) do
%span.label.label-gray
= milestone.project.name
...@@ -18,29 +18,5 @@ ...@@ -18,29 +18,5 @@
.nothing-here-block No milestones to show .nothing-here-block No milestones to show
- else - else
- @group_milestones.each do |milestone| - @group_milestones.each do |milestone|
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } = render 'milestone', milestone: milestone
.pull-right
- if can?(current_user, :manage_group, @group)
- if milestone.closed?
= link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen"
- else
= link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close"
%h4
= link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title)
%div
%div
= link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do
= pluralize milestone.issue_count, 'Issue'
&nbsp;
= link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do
= pluralize milestone.merge_requests_count, 'Merge Request'
&nbsp;
%span.light #{milestone.percent_complete}% complete
= milestone_progress_bar(milestone)
%div
%br
- milestone.milestones.each do |milestone|
= link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) do
%span.label.label-default
= milestone.project.name
= paginate @group_milestones, theme: "gitlab" = paginate @group_milestones, theme: "gitlab"
.documentation.wiki .documentation.wiki
= markdown File.read(Rails.root.join('doc', @category, @file + '.md')).gsub("$your_email", current_user.email) = markdown File.read(Rails.root.join('doc', @filepath + '.md')).gsub("$your_email", current_user.email)
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
- if project_nav_tab? :issues - if project_nav_tab? :issues
= nav_link(controller: :issues) do = nav_link(controller: :issues) do
= link_to url_for_project_issues, title: 'Issues', class: 'shortcuts-issues' do = link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues' do
%i.fa.fa-exclamation-circle %i.fa.fa-exclamation-circle
%span %span
Issues Issues
......
%div %div
= replace_image_links_with_base64(markdown(@note.note), @note.project) = replace_image_links_with_base64(markdown(@note.note, reference_only_path: false), @note.project)
-if @issue.description -if @issue.description
= replace_image_links_with_base64(markdown(@issue.description), @issue.project) = replace_image_links_with_base64(markdown(@issue.description, reference_only_path: false), @issue.project)
- if @issue.assignee_id.present? - if @issue.assignee_id.present?
%p %p
......
...@@ -6,4 +6,4 @@ ...@@ -6,4 +6,4 @@
Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name} Assignee: #{@merge_request.author_name} &rarr; #{@merge_request.assignee_name}
-if @merge_request.description -if @merge_request.description
= replace_image_links_with_base64(markdown(@merge_request.description), @merge_request.project) = replace_image_links_with_base64(markdown(@merge_request.description, reference_only_path: false), @merge_request.project)
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
%ul.dropdown-menu %ul.dropdown-menu
- if @project.issues_enabled && can?(current_user, :write_issue, @project) - if @project.issues_enabled && can?(current_user, :write_issue, @project)
%li %li
= link_to url_for_new_issue, title: "New Issue" do = link_to url_for_new_issue(@project, only_path: true), title: "New Issue" do
New issue New issue
- if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project) - if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project)
%li %li
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
.project-home-panel{:class => ("empty-project" if empty_repo)} .project-home-panel{:class => ("empty-project" if empty_repo)}
.project-identicon-holder .project-identicon-holder
= project_icon(@project, alt: '', class: 'avatar project-avatar') = project_icon(@project, alt: '', class: 'avatar project-avatar')
.project-home-row .project-home-row.project-home-row-top
.project-home-desc .project-home-desc
- if @project.description.present? - if @project.description.present?
= escaped_autolink(@project.description) = escaped_autolink(@project.description)
...@@ -14,31 +14,30 @@ ...@@ -14,31 +14,30 @@
&ndash; &ndash;
= link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)) do = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)) do
= readme.name = readme.name
.star-fork-buttons .project-repo-buttons
.inline.star.js-toggler-container{class: @show_star ? 'on' : ''}
- if current_user
= link_to_toggle_star('Star this project.', false)
= link_to_toggle_star('Unstar this project.', true)
- else
= link_to new_user_session_path, class: 'btn star-btn has_tooltip', title: 'You must sign in to star a project' do
%span
= icon('star')
Star
%span.count
= @project.star_count
- unless @project.empty_repo? - unless @project.empty_repo?
.fork-buttons - if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace
- if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace .inline.fork-buttons.prepend-left-10
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork' do = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-sm btn-default' do
= link_to_toggle_fork = link_to_toggle_fork
- else - else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project" do = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-sm btn-default' do
= link_to_toggle_fork = link_to_toggle_fork
.star-buttons
%span.star.js-toggler-container{class: @show_star ? 'on' : ''}
- if current_user
= link_to_toggle_star('Star this project.', false, true)
= link_to_toggle_star('Unstar this project.', true, true)
- else
= link_to_toggle_star('You must sign in to star a project.', false, false)
.project-home-row.hidden-xs .project-home-row.hidden-xs
- if current_user && !empty_repo - if current_user && !empty_repo
.project-home-dropdown .project-home-dropdown
= render "dropdown" = render "dropdown"
- unless @project.empty_repo?
- if can? current_user, :download_code, @project
.pull-right.prepend-left-10
= render 'projects/repositories/download_archive', split_button: true
= render "shared/clone_panel" = render "shared/clone_panel"
...@@ -10,7 +10,8 @@ ...@@ -10,7 +10,8 @@
Download as Download as
%span.caret %span.caret
%ul.dropdown-menu %ul.dropdown-menu
%li= link_to "Email Patches", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch) - unless @commit.parents.length > 1
%li= link_to "Email Patches", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch)
%li= link_to "Plain Diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff) %li= link_to "Plain Diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff)
= link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-primary btn-grouped" do = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-primary btn-grouped" do
%span Browse Code » %span Browse Code »
......
%ul.nav.nav-tabs %ul.nav.nav-tabs
= nav_link(controller: [:commit, :commits]) do = nav_link(controller: [:commit, :commits]) do
= link_to 'Commits', namespace_project_commits_path(@project.namespace, @project, @repository.root_ref) = link_to namespace_project_commits_path(@project.namespace, @project, @repository.root_ref) do
Commits
%span.badge= number_with_precision(@repository.commit_count, precision: 0, delimiter: ',')
= nav_link(controller: :compare) do = nav_link(controller: :compare) do
= link_to 'Compare', namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: @ref || @repository.root_ref) = link_to 'Compare', namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: @ref || @repository.root_ref)
......
...@@ -11,16 +11,14 @@ ...@@ -11,16 +11,14 @@
%span.cred (Expired) %span.cred (Expired)
%small %small
= milestone.expires_at = milestone.expires_at
- if milestone.is_empty? .row
%span.muted Empty .col-sm-6
- else = link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do
%div = pluralize milestone.issues.count, 'Issue'
%div &nbsp;
= link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do = link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do
= pluralize milestone.issues.count, 'Issue' = pluralize milestone.merge_requests.count, 'Merge Request'
&nbsp; &nbsp;
= link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do %span.light #{milestone.percent_complete}% complete
= pluralize milestone.merge_requests.count, 'Merge Request' .col-sm-6
&nbsp;
%span.light #{milestone.percent_complete}% complete
= milestone_progress_bar(milestone) = milestone_progress_bar(milestone)
...@@ -15,5 +15,5 @@ ...@@ -15,5 +15,5 @@
if(current_url == log_url) { if(current_url == log_url) {
// Load 10 more commit log for each file in tree // Load 10 more commit log for each file in tree
// if we still on the same page // if we still on the same page
ajaxGet('#{logs_file_namespace_project_ref_path(@project.namespace, @project, @ref, @path || '/', offset: (@offset + @limit))}'); ajaxGet('#{logs_file_namespace_project_ref_path(@project.namespace, @project, @ref, @path || '', offset: (@offset + @limit))}');
} }
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
- split_button = split_button || false - split_button = split_button || false
- if split_button == true - if split_button == true
%span.btn-group{class: btn_class} %span.btn-group{class: btn_class}
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn btn-sm', rel: 'nofollow' do
%i.fa.fa-download %i.fa.fa-download
%span Download zip %span Download zip
%a.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' } %a.btn-sm.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' }
%span.caret %span.caret
%span.sr-only %span.sr-only
Select Archive Format Select Archive Format
......
...@@ -68,7 +68,7 @@ ...@@ -68,7 +68,7 @@
= link_to license_url(@project), class: 'btn btn-block' do = link_to license_url(@project), class: 'btn btn-block' do
View license View license
.prepend-top-10 .prepend-top-10.append-bottom-10
%p %p
%span.light Created on %span.light Created on
#{@project.created_at.stamp('Aug 22, 2013')} #{@project.created_at.stamp('Aug 22, 2013')}
...@@ -79,8 +79,16 @@ ...@@ -79,8 +79,16 @@
- else - else
#{link_to @project.owner_name, @project.owner} #{link_to @project.owner_name, @project.owner}
- unless @project.empty_repo?
- if can? current_user, :download_code, @project
%hr
.prepend-top-10.append-bottom-10
= render 'projects/repositories/download_archive', split_button: true
.prepend-top-10
- @project.ci_services.each do |ci_service| - @project.ci_services.each do |ci_service|
- if ci_service.active? && ci_service.respond_to?(:builds_path) - if ci_service.active? && ci_service.respond_to?(:builds_path)
%hr
- if ci_service.respond_to?(:status_img_path) - if ci_service.respond_to?(:status_img_path)
= link_to ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink' do = link_to ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink' do
= image_tag ci_service.status_img_path, alt: "build status" = image_tag ci_service.status_img_path, alt: "build status"
...@@ -97,4 +105,3 @@ ...@@ -97,4 +105,3 @@
= readme.name = readme.name
.wiki .wiki
= render_readme(readme) = render_readme(readme)
- if @contributed_projects.present? - if local_assigns.has_key?(:contributed_projects) && contributed_projects.present?
.panel.panel-default.contributed-projects .panel.panel-default.contributed-projects
.panel-heading Projects contributed to .panel-heading Projects contributed to
= render 'shared/projects_list', = render 'shared/projects_list',
projects: @contributed_projects.sort_by(&:star_count).reverse, projects: contributed_projects.sort_by(&:star_count).reverse,
projects_limit: 5, stars: true, avatar: false projects_limit: 5, stars: true, avatar: false
- if @projects.present? - if local_assigns.has_key?(:projects) && projects.present?
.panel.panel-default .panel.panel-default
.panel-heading Personal projects .panel-heading Personal projects
= render 'shared/projects_list', = render 'shared/projects_list',
projects: @projects.sort_by(&:star_count).reverse, projects: projects.sort_by(&:star_count).reverse,
projects_limit: 10, stars: true, avatar: false projects_limit: 10, stars: true, avatar: false
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
= spinner = spinner
%aside.col-md-4 %aside.col-md-4
= render 'profile', user: @user = render 'profile', user: @user
= render 'projects' = render 'projects', projects: @projects, contributed_projects: @contributed_projects
:coffeescript :coffeescript
$(".user-calendar").load("#{user_calendar_path}") $(".user-calendar").load("#{user_calendar_path}")
class RepositoryArchiveWorker
include Sidekiq::Worker
sidekiq_options queue: :archive_repo
attr_accessor :project, :ref, :format
def perform(project_id, ref, format)
@project = Project.find(project_id)
@ref, @format = ref, format.downcase
repository = project.repository
repository.clean_old_archives
return unless file_path
return if archived? || archiving?
repository.archive_repo(ref, storage_path, format)
end
private
def storage_path
Gitlab.config.gitlab.repository_downloads_path
end
def file_path
@file_path ||= project.repository.archive_file_path(ref, storage_path, format)
end
def pid_file_path
@pid_file_path ||= project.repository.archive_pid_file_path(ref, storage_path, format)
end
def archived?
File.exist?(file_path)
end
def archiving?
File.exist?(pid_file_path)
end
end
...@@ -37,7 +37,7 @@ start_no_deamonize() ...@@ -37,7 +37,7 @@ start_no_deamonize()
start_sidekiq() start_sidekiq()
{ {
bundle exec sidekiq -q post_receive -q mailer -q system_hook -q project_web_hook -q gitlab_shell -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1 bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1
} }
load_ok() load_ok()
......
...@@ -66,8 +66,8 @@ production: &base ...@@ -66,8 +66,8 @@ production: &base
# If a commit message matches this regular expression, all issues referenced from the matched text will be closed. # If a commit message matches this regular expression, all issues referenced from the matched text will be closed.
# This happens when the commit is pushed or merged into the default branch of a project. # This happens when the commit is pushed or merged into the default branch of a project.
# When not specified the default issue_closing_pattern as specified below will be used. # When not specified the default issue_closing_pattern as specified below will be used.
# Tip: you can test your closing pattern at http://rubular.com # Tip: you can test your closing pattern at http://rubular.com.
# issue_closing_pattern: '((?:[Cc]los(?:e[sd]|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?)|([A-Z]*-\d*))+)' # issue_closing_pattern: '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)'
## Default project features settings ## Default project features settings
default_projects_features: default_projects_features:
...@@ -86,35 +86,6 @@ production: &base ...@@ -86,35 +86,6 @@ production: &base
# The default is 'tmp/repositories' relative to the root of the Rails app. # The default is 'tmp/repositories' relative to the root of the Rails app.
# repository_downloads_path: tmp/repositories # repository_downloads_path: tmp/repositories
## External issues trackers
issues_tracker:
# redmine:
# title: "Redmine"
# ## If not nil, link 'Issues' on project page will be replaced with this
# ## Use placeholders:
# ## :project_id - GitLab project identifier
# ## :issues_tracker_id - Project Name or Id in external issue tracker
# project_url: "http://redmine.sample/projects/:issues_tracker_id"
#
# ## If not nil, links from /#\d/ entities from commit messages will replaced with this
# ## Use placeholders:
# ## :project_id - GitLab project identifier
# ## :issues_tracker_id - Project Name or Id in external issue tracker
# ## :id - Issue id (from commit messages)
# issues_url: "http://redmine.sample/issues/:id"
#
# ## If not nil, links to creating new issues will be replaced with this
# ## Use placeholders:
# ## :project_id - GitLab project identifier
# ## :issues_tracker_id - Project Name or Id in external issue tracker
# new_issue_url: "http://redmine.sample/projects/:issues_tracker_id/issues/new"
#
# jira:
# title: "Atlassian Jira"
# project_url: "http://jira.sample/issues/?jql=project=:issues_tracker_id"
# issues_url: "http://jira.sample/browse/:id"
# new_issue_url: "http://jira.sample/secure/CreateIssue.jspa"
## Gravatar ## Gravatar
## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html ## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html
gravatar: gravatar:
......
...@@ -25,3 +25,5 @@ Sidekiq.configure_client do |config| ...@@ -25,3 +25,5 @@ Sidekiq.configure_client do |config|
namespace: 'resque:gitlab' namespace: 'resque:gitlab'
} }
end end
Sidekiq::Queue["archive_repo"].limit = 2
# This is a patch to address the issue in https://github.com/mbleigh/acts-as-taggable-on/issues/427 caused by
# https://github.com/rails/rails/commit/31a43ebc107fbd50e7e62567e5208a05909ec76c
# gem 'acts-as-taggable-on' has the fix included https://github.com/mbleigh/acts-as-taggable-on/commit/89bbed3864a9252276fb8dd7d535fce280454b90
# but not in the currently used version of gem ('2.4.1')
# With replacement of 'acts-as-taggable-on' gem this file will become obsolete
module ActsAsTaggableOn::Taggable
module Core
module ClassMethods
def tagged_with(tags, options = {})
tag_list = ActsAsTaggableOn::TagList.from(tags)
empty_result = where("1 = 0")
return empty_result if tag_list.empty?
joins = []
conditions = []
having = []
select_clause = []
context = options.delete(:on)
owned_by = options.delete(:owned_by)
alias_base_name = undecorated_table_name.gsub('.','_')
quote = ActsAsTaggableOn::Tag.using_postgresql? ? '"' : ''
if options.delete(:exclude)
if options.delete(:wild)
tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name #{like_operator} ? ESCAPE '!'", "%#{escape_like(t)}%"]) }.join(" OR ")
else
tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name #{like_operator} ?", t]) }.join(" OR ")
end
conditions << "#{table_name}.#{primary_key} NOT IN (SELECT #{ActsAsTaggableOn::Tagging.table_name}.taggable_id FROM #{ActsAsTaggableOn::Tagging.table_name} JOIN #{ActsAsTaggableOn::Tag.table_name} ON #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND (#{tags_conditions}) WHERE #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name, nil)})"
if owned_by
joins << "JOIN #{ActsAsTaggableOn::Tagging.table_name}" +
" ON #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
" AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name, nil)}" +
" AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_id = #{owned_by.id}" +
" AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_type = #{quote_value(owned_by.class.base_class.to_s, nil)}"
end
elsif options.delete(:any)
# get tags, drop out if nothing returned (we need at least one)
tags =
if options.delete(:wild)
ActsAsTaggableOn::Tag.named_like_any(tag_list)
else
ActsAsTaggableOn::Tag.named_any(tag_list)
end
return empty_result unless tags.length > 0
# setup taggings alias so we can chain, ex: items_locations_taggings_awesome_cool_123
# avoid ambiguous column name
taggings_context = context ? "_#{context}" : ''
taggings_alias = adjust_taggings_alias(
"#{alias_base_name[0..4]}#{taggings_context[0..6]}_taggings_#{sha_prefix(tags.map(&:name).join('_'))}"
)
tagging_join = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
" ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
" AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}"
tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
# don't need to sanitize sql, map all ids and join with OR logic
conditions << tags.map { |t| "#{taggings_alias}.tag_id = #{t.id}" }.join(" OR ")
select_clause = "DISTINCT #{table_name}.*" unless context and tag_types.one?
if owned_by
tagging_join << " AND " +
sanitize_sql([
"#{taggings_alias}.tagger_id = ? AND #{taggings_alias}.tagger_type = ?",
owned_by.id,
owned_by.class.base_class.to_s
])
end
joins << tagging_join
else
tags = ActsAsTaggableOn::Tag.named_any(tag_list)
return empty_result unless tags.length == tag_list.length
tags.each do |tag|
taggings_alias = adjust_taggings_alias("#{alias_base_name[0..11]}_taggings_#{sha_prefix(tag.name)}")
tagging_join = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
" ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
" AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}" +
" AND #{taggings_alias}.tag_id = #{tag.id}"
tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
if owned_by
tagging_join << " AND " +
sanitize_sql([
"#{taggings_alias}.tagger_id = ? AND #{taggings_alias}.tagger_type = ?",
owned_by.id,
owned_by.class.base_class.to_s
])
end
joins << tagging_join
end
end
taggings_alias, tags_alias = adjust_taggings_alias("#{alias_base_name}_taggings_group"), "#{alias_base_name}_tags_group"
if options.delete(:match_all)
joins << "LEFT OUTER JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
" ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" +
" AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}"
group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}"
group = group_columns
having = "COUNT(#{taggings_alias}.taggable_id) = #{tags.size}"
end
select(select_clause) \
.joins(joins.join(" ")) \
.where(conditions.join(" AND ")) \
.group(group) \
.having(having) \
.order(options[:order]) \
.readonly(false)
end
end
end
end
...@@ -6,3 +6,4 @@ ...@@ -6,3 +6,4 @@
Mime::Type.register_alias "text/plain", :diff Mime::Type.register_alias "text/plain", :diff
Mime::Type.register_alias "text/plain", :patch Mime::Type.register_alias "text/plain", :patch
Mime::Type.register_alias 'text/html', :md
...@@ -39,10 +39,9 @@ Gitlab::Application.routes.draw do ...@@ -39,10 +39,9 @@ Gitlab::Application.routes.draw do
# Help # Help
get 'help' => 'help#index' get 'help' => 'help#index'
get 'help/:category/:file' => 'help#show', as: :help_page
get 'help/:category/*file' => 'help#show'
get 'help/shortcuts' get 'help/shortcuts'
get 'help/ui' => 'help#ui' get 'help/ui' => 'help#ui'
get 'help/:filepath' => 'help#show', as: :help_page, constraints: { filepath: /[^\.]+/ }
# #
# Global snippets # Global snippets
......
class RemovePeriodsAtEndsOfUsernames < ActiveRecord::Migration
include Gitlab::ShellAdapter
class Namespace < ActiveRecord::Base
class << self
def by_path(path)
where('lower(path) = :value', value: path.downcase).first
end
def clean_path(path)
path = path.dup
path.gsub!(/@.*\z/, "")
path.gsub!(/\.git\z/, "")
path.gsub!(/\A-/, "")
path.gsub!(/.\z/, "")
path.gsub!(/[^a-zA-Z0-9_\-\.]/, "")
counter = 0
base = path
while Namespace.by_path(path).present?
counter += 1
path = "#{base}#{counter}"
end
path
end
end
end
def up
changed_paths = {}
select_all("SELECT id, username FROM users WHERE username LIKE '%.'").each do |user|
username_was = user["username"]
username = Namespace.clean_path(username_was)
changed_paths[username_was] = username
username = quote_string(username)
execute "UPDATE users SET username = '#{username}' WHERE id = #{user["id"]}"
execute "UPDATE namespaces SET path = '#{username}', name = '#{username}' WHERE type IS NULL AND owner_id = #{user["id"]}"
end
select_all("SELECT id, path FROM namespaces WHERE type = 'Group' AND path LIKE '%.'").each do |group|
path_was = group["path"]
path = Namespace.clean_path(path_was)
changed_paths[path_was] = path
path = quote_string(path)
execute "UPDATE namespaces SET path = '#{path}' WHERE id = #{group["id"]}"
end
changed_paths.each do |path_was, path|
if gitlab_shell.mv_namespace(path_was, path)
# If repositories moved successfully we need to remove old satellites
# and send update instructions to users.
# However we cannot allow rollback since we moved namespace dir
# So we basically we mute exceptions in next actions
begin
gitlab_shell.rm_satellites(path_was)
# We cannot send update instructions since models and mailers
# can't safely be used from migrations as they may be written for
# later versions of the database.
# send_update_instructions
rescue
# Returning false does not rollback after_* transaction but gives
# us information about failing some of tasks
false
end
else
# if we cannot move namespace directory we should rollback
# db changes in order to prevent out of sync between db and fs
raise Exception.new('namespace directory cannot be moved')
end
end
end
end
# Issue closing pattern # Issue closing pattern
By default you can close issues from commit messages by saying 'Closes #12' or 'Fixed #101'. If a commit message matches the regular expression below, all issues referenced from
the matched text will be closed. This happens when the commit is pushed or merged
into the default branch of a project.
If you want to customize the message please do so in [gitlab.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/73b92f85bcd6c213b845cc997843a969cf0906cf/config/gitlab.yml.example#L73) When not specified, the default issue_closing_pattern as shown below will be used:
```bash
((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)
```
For example:
```
git commit -m "Awesome commit message (Fix #20, Fixes #21 and Closes #22). This commit is also related to #17 and fixes #18, #19 and #23."
```
will close `#20`, `#21`, `#22`, `#18`, `#19` and `#23`, but `#17` won't be closed
as it does not match the pattern. It also works with multiline commit messages.
Tip: you can test this closing pattern at [http://rubular.com][1]. Use this site
to test your own patterns.
## Change the pattern
For Omnibus installs you can change the default pattern in `/etc/gitlab/gitlab.rb`:
```
issue_closing_pattern: '((?:[Cc]los(?:e[sd]|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)'
```
For manual installs you can customize the pattern in [gitlab.yml][0].
[0]: https://gitlab.com/gitlab-org/gitlab-ce/blob/40c3675372320febf5264061c9bcd63db2dfd13c/config/gitlab.yml.example#L65
[1]: http://rubular.com/r/Xmbexed1OJ
...@@ -17,6 +17,13 @@ sudo gitlab-rake gitlab:backup:create ...@@ -17,6 +17,13 @@ sudo gitlab-rake gitlab:backup:create
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
``` ```
Also you can choose what should be backed up by adding environment variable SKIP. Available options: db,
uploads (attachments), repositories. Use a comma to specify several options at the same time.
```
sudo gitlab-rake gitlab:backup:create SKIP=db,uploads
```
Example output: Example output:
``` ```
......
...@@ -11,7 +11,7 @@ RUN apt-get update -q \ ...@@ -11,7 +11,7 @@ RUN apt-get update -q \
# If the Omnibus package version below is outdated please contribute a merge request to update it. # If the Omnibus package version below is outdated please contribute a merge request to update it.
# If you run GitLab Enterprise Edition point it to a location where you have downloaded it. # If you run GitLab Enterprise Edition point it to a location where you have downloaded it.
RUN TMP_FILE=$(mktemp); \ RUN TMP_FILE=$(mktemp); \
wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.9.0-omnibus.2-1_amd64.deb \ wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.9.1-omnibus.1-1_amd64.deb \
&& dpkg -i $TMP_FILE \ && dpkg -i $TMP_FILE \
&& rm -f $TMP_FILE && rm -f $TMP_FILE
......
...@@ -54,6 +54,14 @@ Feature: Groups ...@@ -54,6 +54,14 @@ Feature: Groups
Then I see a new LDAP synchronization listed Then I see a new LDAP synchronization listed
And LDAP disabled And LDAP disabled
@javascript
Scenario: Add user to group
Given gitlab user "Mike"
When I visit group "Owned" members page
And I click link "Add members"
When I select "Mike" as "Reporter"
Then I should see "Mike" in team list as "Reporter"
# Leave # Leave
@javascript @javascript
......
...@@ -13,7 +13,7 @@ Feature: Project Star ...@@ -13,7 +13,7 @@ Feature: Project Star
Given public project "Community" Given public project "Community"
And I visit project "Community" page And I visit project "Community" page
When I click on the star toggle button When I click on the star toggle button
Then The project has 0 stars Then I redirected to sign in page
@javascript @javascript
Scenario: Signed in users can toggle star Scenario: Signed in users can toggle star
......
...@@ -8,7 +8,7 @@ class Spinach::Features::DashboardHelp < Spinach::FeatureSteps ...@@ -8,7 +8,7 @@ class Spinach::Features::DashboardHelp < Spinach::FeatureSteps
end end
step 'I visit the "Rake Tasks" help page' do step 'I visit the "Rake Tasks" help page' do
visit help_page_path("raketasks", "maintenance.md") visit help_page_path('raketasks/maintenance', format: 'md')
end end
step 'I should see "Rake Tasks" page markdown rendered' do step 'I should see "Rake Tasks" page markdown rendered' do
......
...@@ -5,6 +5,32 @@ class Spinach::Features::Groups < Spinach::FeatureSteps ...@@ -5,6 +5,32 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
include SharedUser include SharedUser
include Select2Helper include Select2Helper
step 'gitlab user "Mike"' do
create(:user, name: "Mike")
end
step 'I click link "Add members"' do
find(:css, 'button.btn-new').click
end
step 'I select "Mike" as "Reporter"' do
user = User.find_by(name: "Mike")
within ".users-group-form" do
select2(user.id, from: "#user_ids", multiple: true)
select "Reporter", from: "access_level"
end
click_button "Add users to group"
end
step 'I should see "Mike" in team list as "Reporter"' do
within '.well-list' do
page.should have_content('Mike')
page.should have_content('Reporter')
end
end
step 'I should see group "Owned" projects list' do step 'I should see group "Owned" projects list' do
Group.find_by(name: "Owned").projects.each do |project| Group.find_by(name: "Owned").projects.each do |project|
page.should have_link project.name page.should have_link project.name
......
...@@ -74,7 +74,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -74,7 +74,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end end
step 'I fill the new file name with an illegal name' do step 'I fill the new file name with an illegal name' do
fill_in :file_name, with: '.git' fill_in :file_name, with: 'Spaces Not Allowed'
end end
step 'I fill the commit message' do step 'I fill the commit message' do
......
...@@ -22,12 +22,16 @@ class Spinach::Features::ProjectStar < Spinach::FeatureSteps ...@@ -22,12 +22,16 @@ class Spinach::Features::ProjectStar < Spinach::FeatureSteps
# Requires @javascript # Requires @javascript
step "I click on the star toggle button" do step "I click on the star toggle button" do
find(".star .toggle", visible: true).click find(".star-btn", visible: true).click
end
step 'I redirected to sign in page' do
current_path.should == new_user_session_path
end end
protected protected
def has_n_stars(n) def has_n_stars(n)
expect(page).to have_css(".star .count", text: /^#{n}$/, visible: true) expect(page).to have_css(".star-btn .count", text: n, visible: true)
end end
end end
require 'mime/types' require 'mime/types'
require 'uri'
module API module API
# Projects API # Projects API
...@@ -101,10 +100,11 @@ module API ...@@ -101,10 +100,11 @@ module API
# branch (required) - The name of the branch # branch (required) - The name of the branch
# Example Request: # Example Request:
# DELETE /projects/:id/repository/branches/:branch # DELETE /projects/:id/repository/branches/:branch
delete ":id/repository/branches/:branch" do delete ":id/repository/branches/:branch",
requirements: { branch: /.*/ } do
authorize_push_project authorize_push_project
result = DeleteBranchService.new(user_project, current_user). result = DeleteBranchService.new(user_project, current_user).
execute(URI.unescape(params[:branch])) execute(params[:branch])
if result[:status] == :success if result[:status] == :success
{ {
......
...@@ -88,17 +88,14 @@ module API ...@@ -88,17 +88,14 @@ module API
present user_project, with: Entities::ProjectWithAccess, user: current_user present user_project, with: Entities::ProjectWithAccess, user: current_user
end end
# Get a single project events # Get events for a single project
# #
# Parameters: # Parameters:
# id (required) - The ID of a project # id (required) - The ID of a project
# Example Request: # Example Request:
# GET /projects/:id/events # GET /projects/:id/events
get ":id/events" do get ":id/events" do
limit = (params[:per_page] || 20).to_i events = paginate user_project.events.recent
offset = (params[:page] || 0).to_i * limit
events = user_project.events.recent.limit(limit).offset(offset)
present events, with: Entities::Event present events, with: Entities::Event
end end
......
...@@ -133,10 +133,11 @@ module API ...@@ -133,10 +133,11 @@ module API
authorize! :download_code, user_project authorize! :download_code, user_project
begin begin
file_path = ArchiveRepositoryService.new.execute( file_path = ArchiveRepositoryService.new(
user_project, user_project,
params[:sha], params[:sha],
params[:format]) params[:format]
).execute
rescue rescue
not_found!('File') not_found!('File')
end end
...@@ -149,7 +150,7 @@ module API ...@@ -149,7 +150,7 @@ module API
env['api.format'] = :binary env['api.format'] = :binary
present data present data
else else
not_found!('File') redirect request.fullpath
end end
end end
......
module Backup module Backup
class Manager class Manager
BACKUP_CONTENTS = %w{repositories/ db/ uploads/ backup_information.yml}
def pack def pack
# saving additional informations # saving additional informations
s = {} s = {}
...@@ -9,6 +7,7 @@ module Backup ...@@ -9,6 +7,7 @@ module Backup
s[:backup_created_at] = Time.now s[:backup_created_at] = Time.now
s[:gitlab_version] = Gitlab::VERSION s[:gitlab_version] = Gitlab::VERSION
s[:tar_version] = tar_version s[:tar_version] = tar_version
s[:skipped] = ENV["SKIP"]
tar_file = "#{s[:backup_created_at].to_i}_gitlab_backup.tar" tar_file = "#{s[:backup_created_at].to_i}_gitlab_backup.tar"
Dir.chdir(Gitlab.config.backup.path) do Dir.chdir(Gitlab.config.backup.path) do
...@@ -17,12 +16,12 @@ module Backup ...@@ -17,12 +16,12 @@ module Backup
file << s.to_yaml.gsub(/^---\n/,'') file << s.to_yaml.gsub(/^---\n/,'')
end end
FileUtils.chmod_R(0700, %w{db uploads repositories}) FileUtils.chmod(0700, folders_to_backup)
# create archive # create archive
$progress.print "Creating backup archive: #{tar_file} ... " $progress.print "Creating backup archive: #{tar_file} ... "
orig_umask = File.umask(0077) orig_umask = File.umask(0077)
if Kernel.system('tar', '-cf', tar_file, *BACKUP_CONTENTS) if Kernel.system('tar', '-cf', tar_file, *backup_contents)
$progress.puts "done".green $progress.puts "done".green
else else
puts "creating archive #{tar_file} failed".red puts "creating archive #{tar_file} failed".red
...@@ -46,6 +45,7 @@ module Backup ...@@ -46,6 +45,7 @@ module Backup
connection = ::Fog::Storage.new(connection_settings) connection = ::Fog::Storage.new(connection_settings)
directory = connection.directories.get(remote_directory) directory = connection.directories.get(remote_directory)
if directory.files.create(key: tar_file, body: File.open(tar_file), public: false) if directory.files.create(key: tar_file, body: File.open(tar_file), public: false)
$progress.puts "done".green $progress.puts "done".green
else else
...@@ -56,7 +56,10 @@ module Backup ...@@ -56,7 +56,10 @@ module Backup
def cleanup def cleanup
$progress.print "Deleting tmp directories ... " $progress.print "Deleting tmp directories ... "
BACKUP_CONTENTS.each do |dir|
backup_contents.each do |dir|
next unless File.exist?(File.join(Gitlab.config.backup.path, dir))
if FileUtils.rm_rf(File.join(Gitlab.config.backup.path, dir)) if FileUtils.rm_rf(File.join(Gitlab.config.backup.path, dir))
$progress.puts "done".green $progress.puts "done".green
else else
...@@ -73,6 +76,7 @@ module Backup ...@@ -73,6 +76,7 @@ module Backup
if keep_time > 0 if keep_time > 0
removed = 0 removed = 0
Dir.chdir(Gitlab.config.backup.path) do Dir.chdir(Gitlab.config.backup.path) do
file_list = Dir.glob('*_gitlab_backup.tar') file_list = Dir.glob('*_gitlab_backup.tar')
file_list.map! { |f| $1.to_i if f =~ /(\d+)_gitlab_backup.tar/ } file_list.map! { |f| $1.to_i if f =~ /(\d+)_gitlab_backup.tar/ }
...@@ -84,6 +88,7 @@ module Backup ...@@ -84,6 +88,7 @@ module Backup
end end
end end
end end
$progress.puts "done. (#{removed} removed)".green $progress.puts "done. (#{removed} removed)".green
else else
$progress.puts "skipping".yellow $progress.puts "skipping".yellow
...@@ -96,6 +101,7 @@ module Backup ...@@ -96,6 +101,7 @@ module Backup
# check for existing backups in the backup dir # check for existing backups in the backup dir
file_list = Dir.glob("*_gitlab_backup.tar").each.map { |f| f.split(/_/).first.to_i } file_list = Dir.glob("*_gitlab_backup.tar").each.map { |f| f.split(/_/).first.to_i }
puts "no backups found" if file_list.count == 0 puts "no backups found" if file_list.count == 0
if file_list.count > 1 && ENV["BACKUP"].nil? if file_list.count > 1 && ENV["BACKUP"].nil?
puts "Found more than one backup, please specify which one you want to restore:" puts "Found more than one backup, please specify which one you want to restore:"
puts "rake gitlab:backup:restore BACKUP=timestamp_of_backup" puts "rake gitlab:backup:restore BACKUP=timestamp_of_backup"
...@@ -110,6 +116,7 @@ module Backup ...@@ -110,6 +116,7 @@ module Backup
end end
$progress.print "Unpacking backup ... " $progress.print "Unpacking backup ... "
unless Kernel.system(*%W(tar -xf #{tar_file})) unless Kernel.system(*%W(tar -xf #{tar_file}))
puts "unpacking backup failed".red puts "unpacking backup failed".red
exit 1 exit 1
...@@ -117,7 +124,6 @@ module Backup ...@@ -117,7 +124,6 @@ module Backup
$progress.puts "done".green $progress.puts "done".green
end end
settings = YAML.load_file("backup_information.yml")
ENV["VERSION"] = "#{settings[:db_version]}" if settings[:db_version].to_i > 0 ENV["VERSION"] = "#{settings[:db_version]}" if settings[:db_version].to_i > 0
# restoring mismatching backups can lead to unexpected problems # restoring mismatching backups can lead to unexpected problems
...@@ -136,5 +142,29 @@ module Backup ...@@ -136,5 +142,29 @@ module Backup
tar_version, _ = Gitlab::Popen.popen(%W(tar --version)) tar_version, _ = Gitlab::Popen.popen(%W(tar --version))
tar_version.force_encoding('locale').split("\n").first tar_version.force_encoding('locale').split("\n").first
end end
def skipped?(item)
settings[:skipped] && settings[:skipped].include?(item)
end
private
def backup_contents
folders_to_backup + ["backup_information.yml"]
end
def folders_to_backup
folders = %w{repositories db uploads}
if ENV["SKIP"]
return folders.reject{ |folder| ENV["SKIP"].include?(folder) }
end
folders
end
def settings
@settings ||= YAML.load_file("backup_information.yml")
end
end end
end end
...@@ -28,6 +28,10 @@ module Gitlab ...@@ -28,6 +28,10 @@ module Gitlab
client.auth_code.get_token(code, redirect_uri: redirect_uri).token client.auth_code.get_token(code, redirect_uri: redirect_uri).token
end end
def user
api.get("/api/v3/user").parsed
end
def issues(project_identifier) def issues(project_identifier)
lazy_page_iterator(PER_PAGE) do |page| lazy_page_iterator(PER_PAGE) do |page|
api.get("/api/v3/projects/#{project_identifier}/issues?per_page=#{PER_PAGE}&page=#{page}").parsed api.get("/api/v3/projects/#{project_identifier}/issues?per_page=#{PER_PAGE}&page=#{page}").parsed
......
...@@ -32,12 +32,12 @@ module Gitlab ...@@ -32,12 +32,12 @@ module Gitlab
module Markdown module Markdown
include IssuesHelper include IssuesHelper
attr_reader :html_options attr_reader :options, :html_options
# Public: Parse the provided text with GitLab-Flavored Markdown # Public: Parse the provided text with GitLab-Flavored Markdown
# #
# text - the source text # text - the source text
# project - extra options for the reference links as given to link_to # project - the project
# html_options - extra options for the reference links as given to link_to # html_options - extra options for the reference links as given to link_to
def gfm(text, project = @project, html_options = {}) def gfm(text, project = @project, html_options = {})
gfm_with_options(text, {}, project, html_options) gfm_with_options(text, {}, project, html_options)
...@@ -46,9 +46,10 @@ module Gitlab ...@@ -46,9 +46,10 @@ module Gitlab
# Public: Parse the provided text with GitLab-Flavored Markdown # Public: Parse the provided text with GitLab-Flavored Markdown
# #
# text - the source text # text - the source text
# options - parse_tasks: true - render tasks # options - parse_tasks - render tasks
# - xhtml: true - output XHTML instead of HTML # - xhtml - output XHTML instead of HTML
# project - extra options for the reference links as given to link_to # - reference_only_path - Use relative path for reference links
# project - the project
# html_options - extra options for the reference links as given to link_to # html_options - extra options for the reference links as given to link_to
def gfm_with_options(text, options = {}, project = @project, html_options = {}) def gfm_with_options(text, options = {}, project = @project, html_options = {})
return text if text.nil? return text if text.nil?
...@@ -58,6 +59,13 @@ module Gitlab ...@@ -58,6 +59,13 @@ module Gitlab
# for gsub calls to work as we need them to. # for gsub calls to work as we need them to.
text = text.dup.to_str text = text.dup.to_str
options.reverse_merge!(
parse_tasks: false,
xhtml: false,
reference_only_path: true
)
@options = options
@html_options = html_options @html_options = html_options
# Extract pre blocks so they are not altered # Extract pre blocks so they are not altered
...@@ -113,12 +121,13 @@ module Gitlab ...@@ -113,12 +121,13 @@ module Gitlab
markdown_pipeline = HTML::Pipeline::Gitlab.new(filters).pipeline markdown_pipeline = HTML::Pipeline::Gitlab.new(filters).pipeline
result = markdown_pipeline.call(text, markdown_context) result = markdown_pipeline.call(text, markdown_context)
saveoptions = 0
save_options = 0
if options[:xhtml] if options[:xhtml]
saveoptions |= Nokogiri::XML::Node::SaveOptions::AS_XHTML save_options |= Nokogiri::XML::Node::SaveOptions::AS_XHTML
end end
text = result[:output].to_html(save_with: saveoptions) text = result[:output].to_html(save_with: save_options)
if options[:parse_tasks] if options[:parse_tasks]
text = parse_tasks(text) text = parse_tasks(text)
...@@ -152,7 +161,7 @@ module Gitlab ...@@ -152,7 +161,7 @@ module Gitlab
text text
end end
NAME_STR = '[a-zA-Z0-9_][a-zA-Z0-9_\-\.]*' NAME_STR = Gitlab::Regex::NAMESPACE_REGEX_STR
PROJ_STR = "(?<project>#{NAME_STR}/#{NAME_STR})" PROJ_STR = "(?<project>#{NAME_STR}/#{NAME_STR})"
REFERENCE_PATTERN = %r{ REFERENCE_PATTERN = %r{
...@@ -229,33 +238,37 @@ module Gitlab ...@@ -229,33 +238,37 @@ module Gitlab
end end
def reference_user(identifier, project = @project, _ = nil) def reference_user(identifier, project = @project, _ = nil)
options = html_options.merge( link_options = html_options.merge(
class: "gfm gfm-project_member #{html_options[:class]}" class: "gfm gfm-project_member #{html_options[:class]}"
) )
if identifier == "all" if identifier == "all"
link_to("@all", namespace_project_url(project.namespace, project), options) link_to(
"@all",
namespace_project_url(project.namespace, project, only_path: options[:reference_only_path]),
link_options
)
elsif namespace = Namespace.find_by(path: identifier) elsif namespace = Namespace.find_by(path: identifier)
url = url =
if namespace.type == "Group" if namespace.is_a?(Group)
group_url(identifier) group_url(identifier, only_path: options[:reference_only_path])
else else
user_url(identifier) user_url(identifier, only_path: options[:reference_only_path])
end end
link_to("@#{identifier}", url, options) link_to("@#{identifier}", url, link_options)
end end
end end
def reference_label(identifier, project = @project, _ = nil) def reference_label(identifier, project = @project, _ = nil)
if label = project.labels.find_by(id: identifier) if label = project.labels.find_by(id: identifier)
options = html_options.merge( link_options = html_options.merge(
class: "gfm gfm-label #{html_options[:class]}" class: "gfm gfm-label #{html_options[:class]}"
) )
link_to( link_to(
render_colored_label(label), render_colored_label(label),
namespace_project_issues_path(project.namespace, project, label_name: label.name), namespace_project_issues_path(project.namespace, project, label_name: label.name),
options link_options
) )
end end
end end
...@@ -263,14 +276,14 @@ module Gitlab ...@@ -263,14 +276,14 @@ module Gitlab
def reference_issue(identifier, project = @project, prefix_text = nil) def reference_issue(identifier, project = @project, prefix_text = nil)
if project.default_issues_tracker? if project.default_issues_tracker?
if project.issue_exists? identifier if project.issue_exists? identifier
url = url_for_issue(identifier, project) url = url_for_issue(identifier, project, only_path: options[:reference_only_path])
title = title_for_issue(identifier, project) title = title_for_issue(identifier, project)
options = html_options.merge( link_options = html_options.merge(
title: "Issue: #{title}", title: "Issue: #{title}",
class: "gfm gfm-issue #{html_options[:class]}" class: "gfm gfm-issue #{html_options[:class]}"
) )
link_to("#{prefix_text}##{identifier}", url, options) link_to("#{prefix_text}##{identifier}", url, link_options)
end end
else else
if project.external_issue_tracker.present? if project.external_issue_tracker.present?
...@@ -280,44 +293,46 @@ module Gitlab ...@@ -280,44 +293,46 @@ module Gitlab
end end
end end
def reference_merge_request(identifier, project = @project, def reference_merge_request(identifier, project = @project, prefix_text = nil)
prefix_text = nil)
if merge_request = project.merge_requests.find_by(iid: identifier) if merge_request = project.merge_requests.find_by(iid: identifier)
options = html_options.merge( link_options = html_options.merge(
title: "Merge Request: #{merge_request.title}", title: "Merge Request: #{merge_request.title}",
class: "gfm gfm-merge_request #{html_options[:class]}" class: "gfm gfm-merge_request #{html_options[:class]}"
) )
url = namespace_project_merge_request_url(project.namespace, project, url = namespace_project_merge_request_url(project.namespace, project,
merge_request) merge_request,
link_to("#{prefix_text}!#{identifier}", url, options) only_path: options[:reference_only_path])
link_to("#{prefix_text}!#{identifier}", url, link_options)
end end
end end
def reference_snippet(identifier, project = @project, _ = nil) def reference_snippet(identifier, project = @project, _ = nil)
if snippet = project.snippets.find_by(id: identifier) if snippet = project.snippets.find_by(id: identifier)
options = html_options.merge( link_options = html_options.merge(
title: "Snippet: #{snippet.title}", title: "Snippet: #{snippet.title}",
class: "gfm gfm-snippet #{html_options[:class]}" class: "gfm gfm-snippet #{html_options[:class]}"
) )
link_to( link_to(
"$#{identifier}", "$#{identifier}",
namespace_project_snippet_url(project.namespace, project, snippet), namespace_project_snippet_url(project.namespace, project, snippet,
options only_path: options[:reference_only_path]),
link_options
) )
end end
end end
def reference_commit(identifier, project = @project, prefix_text = nil) def reference_commit(identifier, project = @project, prefix_text = nil)
if project.valid_repo? && commit = project.repository.commit(identifier) if project.valid_repo? && commit = project.repository.commit(identifier)
options = html_options.merge( link_options = html_options.merge(
title: commit.link_title, title: commit.link_title,
class: "gfm gfm-commit #{html_options[:class]}" class: "gfm gfm-commit #{html_options[:class]}"
) )
prefix_text = "#{prefix_text}@" if prefix_text prefix_text = "#{prefix_text}@" if prefix_text
link_to( link_to(
"#{prefix_text}#{identifier}", "#{prefix_text}#{identifier}",
namespace_project_commit_url(project.namespace, project, commit), namespace_project_commit_url( project.namespace, project, commit,
options only_path: options[:reference_only_path]),
link_options
) )
end end
end end
...@@ -328,11 +343,11 @@ module Gitlab ...@@ -328,11 +343,11 @@ module Gitlab
inclusive = identifier !~ /\.{3}/ inclusive = identifier !~ /\.{3}/
from_id << "^" if inclusive from_id << "^" if inclusive
if project.valid_repo? && if project.valid_repo? &&
from = project.repository.commit(from_id) && from = project.repository.commit(from_id) &&
to = project.repository.commit(to_id) to = project.repository.commit(to_id)
options = html_options.merge( link_options = html_options.merge(
title: "Commits #{from_id} through #{to_id}", title: "Commits #{from_id} through #{to_id}",
class: "gfm gfm-commit_range #{html_options[:class]}" class: "gfm gfm-commit_range #{html_options[:class]}"
) )
...@@ -340,22 +355,23 @@ module Gitlab ...@@ -340,22 +355,23 @@ module Gitlab
link_to( link_to(
"#{prefix_text}#{identifier}", "#{prefix_text}#{identifier}",
namespace_project_compare_url(project.namespace, project, from: from_id, to: to_id), namespace_project_compare_url(project.namespace, project,
options from: from_id, to: to_id,
only_path: options[:reference_only_path]),
link_options
) )
end end
end end
def reference_external_issue(identifier, project = @project, def reference_external_issue(identifier, project = @project, prefix_text = nil)
prefix_text = nil) url = url_for_issue(identifier, project, only_path: options[:reference_only_path])
url = url_for_issue(identifier, project)
title = project.external_issue_tracker.title title = project.external_issue_tracker.title
options = html_options.merge( link_options = html_options.merge(
title: "Issue in #{title}", title: "Issue in #{title}",
class: "gfm gfm-issue #{html_options[:class]}" class: "gfm gfm-issue #{html_options[:class]}"
) )
link_to("#{prefix_text}##{identifier}", url, options) link_to("#{prefix_text}##{identifier}", url, link_options)
end end
# Turn list items that start with "[ ]" into HTML checkbox inputs. # Turn list items that start with "[ ]" into HTML checkbox inputs.
......
...@@ -86,7 +86,7 @@ module Gitlab ...@@ -86,7 +86,7 @@ module Gitlab
def user_attributes def user_attributes
{ {
name: auth_hash.name, name: auth_hash.name,
username: ::User.clean_username(auth_hash.username), username: ::Namespace.clean_path(auth_hash.username),
email: auth_hash.email, email: auth_hash.email,
password: auth_hash.password, password: auth_hash.password,
password_confirmation: auth_hash.password, password_confirmation: auth_hash.password,
......
...@@ -2,49 +2,66 @@ module Gitlab ...@@ -2,49 +2,66 @@ module Gitlab
module Regex module Regex
extend self extend self
def username_regex NAMESPACE_REGEX_STR = '(?:[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*[a-zA-Z0-9_\-]|[a-zA-Z0-9_])'.freeze
default_regex
def namespace_regex
@namespace_regex ||= /\A#{NAMESPACE_REGEX_STR}\z/.freeze
end
def namespace_regex_message
"can contain only letters, digits, '_', '-' and '.'. " \
"Cannot start with '-' or end in '.'." \
end
def namespace_name_regex
@namespace_name_regex ||= /\A[a-zA-Z0-9_\-\. ]*\z/.freeze
end end
def username_regex_message def namespace_name_regex_message
default_regex_message "can contain only letters, digits, '_', '-', '.' and space."
end end
def project_name_regex def project_name_regex
/\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\. ]*\z/ @project_name_regex ||= /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\. ]*\z/.freeze
end end
def project_regex_message def project_name_regex_message
"can contain only letters, digits, '_', '-' and '.' and space. " \ "can contain only letters, digits, '_', '-', '.' and space. " \
"It must start with letter, digit or '_'." "It must start with letter, digit or '_'."
end end
def name_regex
/\A[a-zA-Z0-9_\-\. ]*\z/ def project_path_regex
@project_path_regex ||= /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\.]*(?<!\.git)\z/.freeze
end end
def name_regex_message def project_path_regex_message
"can contain only letters, digits, '_', '-' and '.' and space." "can contain only letters, digits, '_', '-' and '.'. " \
"Cannot start with '-' or end in '.git'" \
end end
def path_regex
default_regex def file_name_regex
@file_name_regex ||= /\A[a-zA-Z0-9_\-\.]*\z/.freeze
end end
def path_regex_message def file_name_regex_message
default_regex_message "can contain only letters, digits, '_', '-' and '.'. "
end end
def archive_formats_regex def archive_formats_regex
#|zip|tar| tar.gz | tar.bz2 | # |zip|tar| tar.gz | tar.bz2 |
/(zip|tar|tar\.gz|tgz|gz|tar\.bz2|tbz|tbz2|tb2|bz2)/ @archive_formats_regex ||= /(zip|tar|tar\.gz|tgz|gz|tar\.bz2|tbz|tbz2|tb2|bz2)/.freeze
end end
def git_reference_regex def git_reference_regex
# Valid git ref regex, see: # Valid git ref regex, see:
# https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html # https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
%r{ @git_reference_regex ||= %r{
(?! (?!
(?# doesn't begins with) (?# doesn't begins with)
\/| (?# rule #6) \/| (?# rule #6)
...@@ -60,18 +77,7 @@ module Gitlab ...@@ -60,18 +77,7 @@ module Gitlab
(?# doesn't end with) (?# doesn't end with)
(?<!\.lock) (?# rule #1) (?<!\.lock) (?# rule #1)
(?<![\/.]) (?# rule #6-7) (?<![\/.]) (?# rule #6-7)
}x }x.freeze
end
protected
def default_regex_message
"can contain only letters, digits, '_', '-' and '.'. " \
"Cannot start with '-' or end in '.git'" \
end
def default_regex
/\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\.]*(?<!\.git)\z/
end end
end end
end end
...@@ -27,9 +27,9 @@ namespace :gitlab do ...@@ -27,9 +27,9 @@ namespace :gitlab do
backup = Backup::Manager.new backup = Backup::Manager.new
backup.unpack backup.unpack
Rake::Task["gitlab:backup:db:restore"].invoke Rake::Task["gitlab:backup:db:restore"].invoke unless backup.skipped?("db")
Rake::Task["gitlab:backup:repo:restore"].invoke Rake::Task["gitlab:backup:repo:restore"].invoke unless backup.skipped?("repositories")
Rake::Task["gitlab:backup:uploads:restore"].invoke Rake::Task["gitlab:backup:uploads:restore"].invoke unless backup.skipped?("uploads")
Rake::Task["gitlab:shell:setup"].invoke Rake::Task["gitlab:shell:setup"].invoke
backup.cleanup backup.cleanup
...@@ -38,8 +38,13 @@ namespace :gitlab do ...@@ -38,8 +38,13 @@ namespace :gitlab do
namespace :repo do namespace :repo do
task create: :environment do task create: :environment do
$progress.puts "Dumping repositories ...".blue $progress.puts "Dumping repositories ...".blue
Backup::Repository.new.dump
$progress.puts "done".green if ENV["SKIP"] && ENV["SKIP"].include?("repositories")
$progress.puts "[SKIPPED]".cyan
else
Backup::Repository.new.dump
$progress.puts "done".green
end
end end
task restore: :environment do task restore: :environment do
...@@ -52,8 +57,13 @@ namespace :gitlab do ...@@ -52,8 +57,13 @@ namespace :gitlab do
namespace :db do namespace :db do
task create: :environment do task create: :environment do
$progress.puts "Dumping database ... ".blue $progress.puts "Dumping database ... ".blue
Backup::Database.new.dump
$progress.puts "done".green if ENV["SKIP"] && ENV["SKIP"].include?("db")
$progress.puts "[SKIPPED]".cyan
else
Backup::Database.new.dump
$progress.puts "done".green
end
end end
task restore: :environment do task restore: :environment do
...@@ -66,8 +76,13 @@ namespace :gitlab do ...@@ -66,8 +76,13 @@ namespace :gitlab do
namespace :uploads do namespace :uploads do
task create: :environment do task create: :environment do
$progress.puts "Dumping uploads ... ".blue $progress.puts "Dumping uploads ... ".blue
Backup::Uploads.new.dump
$progress.puts "done".green if ENV["SKIP"] && ENV["SKIP"].include?("uploads")
$progress.puts "[SKIPPED]".cyan
else
Backup::Uploads.new.dump
$progress.puts "done".green
end
end end
task restore: :environment do task restore: :environment do
......
...@@ -2,6 +2,7 @@ namespace :gitlab do ...@@ -2,6 +2,7 @@ namespace :gitlab do
desc "GITLAB | Run all tests" desc "GITLAB | Run all tests"
task :test do task :test do
cmds = [ cmds = [
%W(rake brakeman),
%W(rake rubocop), %W(rake rubocop),
%W(rake spinach), %W(rake spinach),
%W(rake spec), %W(rake spec),
......
...@@ -55,24 +55,109 @@ describe Import::BitbucketController do ...@@ -55,24 +55,109 @@ describe Import::BitbucketController do
end end
describe "POST create" do describe "POST create" do
before do let(:bitbucket_username) { user.username }
@repo = {
slug: 'vim', let(:bitbucket_user) {
owner: "john" {
user: {
username: bitbucket_username
}
}.with_indifferent_access }.with_indifferent_access
end }
it "takes already existing namespace" do let(:bitbucket_repo) {
namespace = create(:namespace, name: "john", owner: user) {
expect(Gitlab::BitbucketImport::KeyAdder). slug: "vim",
to receive(:new).with(@repo, user). owner: bitbucket_username
and_return(double(execute: true)) }.with_indifferent_access
expect(Gitlab::BitbucketImport::ProjectCreator). }
to receive(:new).with(@repo, namespace, user).
before do
allow(Gitlab::BitbucketImport::KeyAdder).
to receive(:new).with(bitbucket_repo, user).
and_return(double(execute: true)) and_return(double(execute: true))
controller.stub_chain(:client, :project).and_return(@repo)
post :create, format: :js controller.stub_chain(:client, :user).and_return(bitbucket_user)
controller.stub_chain(:client, :project).and_return(bitbucket_repo)
end
context "when the repository owner is the Bitbucket user" do
context "when the Bitbucket user and GitLab user's usernames match" do
it "takes the current user's namespace" do
expect(Gitlab::BitbucketImport::ProjectCreator).
to receive(:new).with(bitbucket_repo, user.namespace, user).
and_return(double(execute: true))
post :create, format: :js
end
end
context "when the Bitbucket user and GitLab user's usernames don't match" do
let(:bitbucket_username) { "someone_else" }
it "takes the current user's namespace" do
expect(Gitlab::BitbucketImport::ProjectCreator).
to receive(:new).with(bitbucket_repo, user.namespace, user).
and_return(double(execute: true))
post :create, format: :js
end
end
end
context "when the repository owner is not the Bitbucket user" do
let(:other_username) { "someone_else" }
before do
bitbucket_repo["owner"] = other_username
end
context "when a namespace with the Bitbucket user's username already exists" do
let!(:existing_namespace) { create(:namespace, name: other_username, owner: user) }
context "when the namespace is owned by the GitLab user" do
it "takes the existing namespace" do
expect(Gitlab::BitbucketImport::ProjectCreator).
to receive(:new).with(bitbucket_repo, existing_namespace, user).
and_return(double(execute: true))
post :create, format: :js
end
end
context "when the namespace is not owned by the GitLab user" do
before do
existing_namespace.owner = create(:user)
existing_namespace.save
end
it "doesn't create a project" do
expect(Gitlab::BitbucketImport::ProjectCreator).
not_to receive(:new)
post :create, format: :js
end
end
end
context "when a namespace with the Bitbucket user's username doesn't exist" do
it "creates the namespace" do
expect(Gitlab::BitbucketImport::ProjectCreator).
to receive(:new).and_return(double(execute: true))
post :create, format: :js
expect(Namespace.where(name: other_username).first).not_to be_nil
end
it "takes the new namespace" do
expect(Gitlab::BitbucketImport::ProjectCreator).
to receive(:new).with(bitbucket_repo, an_instance_of(Group), user).
and_return(double(execute: true))
post :create, format: :js
end
end
end end
end end
end end
...@@ -57,18 +57,98 @@ describe Import::GithubController do ...@@ -57,18 +57,98 @@ describe Import::GithubController do
end end
describe "POST create" do describe "POST create" do
let(:github_username) { user.username }
let(:github_user) {
OpenStruct.new(login: github_username)
}
let(:github_repo) {
OpenStruct.new(name: 'vim', full_name: "#{github_username}/vim", owner: OpenStruct.new(login: github_username))
}
before do before do
@repo = OpenStruct.new(login: 'vim', full_name: 'asd/vim', owner: OpenStruct.new(login: "john")) controller.stub_chain(:client, :user).and_return(github_user)
controller.stub_chain(:client, :repo).and_return(github_repo)
end
context "when the repository owner is the GitHub user" do
context "when the GitHub user and GitLab user's usernames match" do
it "takes the current user's namespace" do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(github_repo, user.namespace, user).
and_return(double(execute: true))
post :create, format: :js
end
end
context "when the GitHub user and GitLab user's usernames don't match" do
let(:github_username) { "someone_else" }
it "takes the current user's namespace" do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(github_repo, user.namespace, user).
and_return(double(execute: true))
post :create, format: :js
end
end
end end
it "takes already existing namespace" do context "when the repository owner is not the GitHub user" do
namespace = create(:namespace, name: "john", owner: user) let(:other_username) { "someone_else" }
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(@repo, namespace, user). before do
and_return(double(execute: true)) github_repo.owner = OpenStruct.new(login: other_username)
controller.stub_chain(:client, :repo).and_return(@repo) end
context "when a namespace with the GitHub user's username already exists" do
let!(:existing_namespace) { create(:namespace, name: other_username, owner: user) }
context "when the namespace is owned by the GitLab user" do
it "takes the existing namespace" do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(github_repo, existing_namespace, user).
and_return(double(execute: true))
post :create, format: :js
end
end
context "when the namespace is not owned by the GitLab user" do
before do
existing_namespace.owner = create(:user)
existing_namespace.save
end
it "doesn't create a project" do
expect(Gitlab::GithubImport::ProjectCreator).
not_to receive(:new)
post :create, format: :js
end
end
end
context "when a namespace with the GitHub user's username doesn't exist" do
it "creates the namespace" do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).and_return(double(execute: true))
post :create, format: :js
expect(Namespace.where(name: other_username).first).not_to be_nil
end
it "takes the new namespace" do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(github_repo, an_instance_of(Group), user).
and_return(double(execute: true))
post :create, format: :js post :create, format: :js
end
end
end end
end end
end end
...@@ -48,23 +48,105 @@ describe Import::GitlabController do ...@@ -48,23 +48,105 @@ describe Import::GitlabController do
end end
describe "POST create" do describe "POST create" do
before do let(:gitlab_username) { user.username }
@repo = {
let(:gitlab_user) {
{
username: gitlab_username
}.with_indifferent_access
}
let(:gitlab_repo) {
{
path: 'vim', path: 'vim',
path_with_namespace: 'asd/vim', path_with_namespace: "#{gitlab_username}/vim",
owner: {name: "john"}, owner: { name: gitlab_username },
namespace: {path: "john"} namespace: { path: gitlab_username }
}.with_indifferent_access }.with_indifferent_access
}
before do
controller.stub_chain(:client, :user).and_return(gitlab_user)
controller.stub_chain(:client, :project).and_return(gitlab_repo)
end
context "when the repository owner is the GitLab.com user" do
context "when the GitLab.com user and GitLab server user's usernames match" do
it "takes the current user's namespace" do
expect(Gitlab::GitlabImport::ProjectCreator).
to receive(:new).with(gitlab_repo, user.namespace, user).
and_return(double(execute: true))
post :create, format: :js
end
end
context "when the GitLab.com user and GitLab server user's usernames don't match" do
let(:gitlab_username) { "someone_else" }
it "takes the current user's namespace" do
expect(Gitlab::GitlabImport::ProjectCreator).
to receive(:new).with(gitlab_repo, user.namespace, user).
and_return(double(execute: true))
post :create, format: :js
end
end
end end
it "takes already existing namespace" do context "when the repository owner is not the GitLab.com user" do
namespace = create(:namespace, name: "john", owner: user) let(:other_username) { "someone_else" }
expect(Gitlab::GitlabImport::ProjectCreator).
to receive(:new).with(@repo, namespace, user). before do
and_return(double(execute: true)) gitlab_repo["namespace"]["path"] = other_username
controller.stub_chain(:client, :project).and_return(@repo) end
context "when a namespace with the GitLab.com user's username already exists" do
let!(:existing_namespace) { create(:namespace, name: other_username, owner: user) }
context "when the namespace is owned by the GitLab server user" do
it "takes the existing namespace" do
expect(Gitlab::GitlabImport::ProjectCreator).
to receive(:new).with(gitlab_repo, existing_namespace, user).
and_return(double(execute: true))
post :create, format: :js
end
end
context "when the namespace is not owned by the GitLab server user" do
before do
existing_namespace.owner = create(:user)
existing_namespace.save
end
it "doesn't create a project" do
expect(Gitlab::GitlabImport::ProjectCreator).
not_to receive(:new)
post :create, format: :js
end
end
end
context "when a namespace with the GitLab.com user's username doesn't exist" do
it "creates the namespace" do
expect(Gitlab::GitlabImport::ProjectCreator).
to receive(:new).and_return(double(execute: true))
post :create, format: :js
expect(Namespace.where(name: other_username).first).not_to be_nil
end
it "takes the new namespace" do
expect(Gitlab::GitlabImport::ProjectCreator).
to receive(:new).with(gitlab_repo, an_instance_of(Group), user).
and_return(double(execute: true))
post :create, format: :js post :create, format: :js
end
end
end end
end end
end end
require 'spec_helper'
describe NamespacesController do
let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
describe "GET show" do
context "when the namespace belongs to a user" do
let!(:other_user) { create(:user) }
it "redirects to the user's page" do
get :show, id: other_user.username
expect(response).to redirect_to(user_path(other_user))
end
end
context "when the namespace belongs to a group" do
let!(:group) { create(:group) }
let!(:project) { create(:project, namespace: group) }
context "when the group has public projects" do
before do
project.update_attribute(:visibility_level, Project::PUBLIC)
end
context "when not signed in" do
it "redirects to the group's page" do
get :show, id: group.path
expect(response).to redirect_to(group_path(group))
end
end
context "when signed in" do
before do
sign_in(user)
end
it "redirects to the group's page" do
get :show, id: group.path
expect(response).to redirect_to(group_path(group))
end
end
end
context "when the project doesn't have public projects" do
context "when not signed in" do
it "redirects to the sign in page" do
get :show, id: group.path
expect(response).to redirect_to(new_user_session_path)
end
end
context "when signed in" do
before do
sign_in(user)
end
context "when the user has access to the project" do
before do
project.team << [user, :master]
end
context "when the user is blocked" do
before do
user.block
project.team << [user, :master]
end
it "redirects to the sign in page" do
get :show, id: group.path
expect(response).to redirect_to(new_user_session_path)
end
end
context "when the user isn't blocked" do
it "redirects to the group's page" do
get :show, id: group.path
expect(response).to redirect_to(group_path(group))
end
end
end
context "when the user doesn't have access to the project" do
it "responds with status 404" do
get :show, id: group.path
expect(response.status).to eq(404)
end
end
end
end
end
context "when the namespace doesn't exist" do
context "when signed in" do
before do
sign_in(user)
end
it "responds with status 404" do
get :show, id: "doesntexist"
expect(response.status).to eq(404)
end
end
context "when not signed in" do
it "redirects to the sign in page" do
get :show, id: "doesntexist"
expect(response).to redirect_to(new_user_session_path)
end
end
end
end
end
require "spec_helper"
describe Projects::RepositoriesController do
let(:project) { create(:project) }
let(:user) { create(:user) }
describe "GET archive" do
before do
sign_in(user)
project.team << [user, :developer]
allow(ArchiveRepositoryService).to receive(:new).and_return(service)
end
let(:service) { ArchiveRepositoryService.new(project, "master", "zip") }
it "executes ArchiveRepositoryService" do
expect(ArchiveRepositoryService).to receive(:new).with(project, "master", "zip")
expect(service).to receive(:execute)
get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
end
context "when the service raises an error" do
before do
allow(service).to receive(:execute).and_raise("Archive failed")
end
it "renders Not Found" do
get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
expect(response.status).to eq(404)
end
end
context "when the service doesn't return a path" do
before do
allow(service).to receive(:execute).and_return(nil)
end
it "reloads the page" do
get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
expect(response).to redirect_to(archive_namespace_project_repository_path(project.namespace, project, ref: "master", format: "zip"))
end
end
context "when the service returns a path" do
let(:path) { Rails.root.join("spec/fixtures/dk.png").to_s }
before do
allow(service).to receive(:execute).and_return(path)
end
it "sends the file" do
get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
expect(response.body).to eq(File.binread(path))
end
end
end
end
...@@ -6,7 +6,7 @@ describe 'Help Pages', feature: true do ...@@ -6,7 +6,7 @@ describe 'Help Pages', feature: true do
login_as :user login_as :user
end end
it 'replace the variable $your_email with the email of the user' do it 'replace the variable $your_email with the email of the user' do
visit help_page_path(category: 'ssh', file: 'README.md') visit help_page_path(filepath: 'ssh/README', format: 'md')
expect(page).to have_content("ssh-keygen -t rsa -C \"#{@user.email}\"") expect(page).to have_content("ssh-keygen -t rsa -C \"#{@user.email}\"")
end end
end end
......
...@@ -700,7 +700,7 @@ describe GitlabMarkdownHelper do ...@@ -700,7 +700,7 @@ describe GitlabMarkdownHelper do
end end
it "should leave ref-like href of 'manual' links untouched" do it "should leave ref-like href of 'manual' links untouched" do
expect(markdown("why not [inspect !#{merge_request.iid}](http://example.tld/#!#{merge_request.iid})")).to eq("<p>why not <a href=\"http://example.tld/#!#{merge_request.iid}\">inspect </a><a class=\"gfm gfm-merge_request \" href=\"#{namespace_project_merge_request_url(project.namespace, project, merge_request)}\" title=\"Merge Request: #{merge_request.title}\">!#{merge_request.iid}</a><a href=\"http://example.tld/#!#{merge_request.iid}\"></a></p>\n") expect(markdown("why not [inspect !#{merge_request.iid}](http://example.tld/#!#{merge_request.iid})")).to eq("<p>why not <a href=\"http://example.tld/#!#{merge_request.iid}\">inspect </a><a class=\"gfm gfm-merge_request \" href=\"#{namespace_project_merge_request_path(project.namespace, project, merge_request)}\" title=\"Merge Request: #{merge_request.title}\">!#{merge_request.iid}</a><a href=\"http://example.tld/#!#{merge_request.iid}\"></a></p>\n")
end end
it "should leave ref-like src of images untouched" do it "should leave ref-like src of images untouched" do
......
...@@ -7,7 +7,7 @@ describe IconsHelper do ...@@ -7,7 +7,7 @@ describe IconsHelper do
end end
it 'returns share class' do it 'returns share class' do
expect(file_type_icon_class('file', 0120000, 'link')).to eq 'share' expect(file_type_icon_class('file', '120000', 'link')).to eq 'share'
end end
it 'returns file-pdf-o class with .pdf' do it 'returns file-pdf-o class with .pdf' do
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::Regex do describe Gitlab::Regex do
describe 'path regex' do describe 'project path regex' do
it { expect('gitlab-ce').to match(Gitlab::Regex.path_regex) } it { expect('gitlab-ce').to match(Gitlab::Regex.project_path_regex) }
it { expect('gitlab_git').to match(Gitlab::Regex.path_regex) } it { expect('gitlab_git').to match(Gitlab::Regex.project_path_regex) }
it { expect('_underscore.js').to match(Gitlab::Regex.path_regex) } it { expect('_underscore.js').to match(Gitlab::Regex.project_path_regex) }
it { expect('100px.com').to match(Gitlab::Regex.path_regex) } it { expect('100px.com').to match(Gitlab::Regex.project_path_regex) }
it { expect('?gitlab').not_to match(Gitlab::Regex.path_regex) } it { expect('?gitlab').not_to match(Gitlab::Regex.project_path_regex) }
it { expect('git lab').not_to match(Gitlab::Regex.path_regex) } it { expect('git lab').not_to match(Gitlab::Regex.project_path_regex) }
it { expect('gitlab.git').not_to match(Gitlab::Regex.path_regex) } it { expect('gitlab.git').not_to match(Gitlab::Regex.project_path_regex) }
end end
describe 'project name regex' do describe 'project name regex' do
......
...@@ -33,8 +33,6 @@ describe Namespace do ...@@ -33,8 +33,6 @@ describe Namespace do
it { is_expected.to respond_to(:to_param) } it { is_expected.to respond_to(:to_param) }
end end
it { expect(Namespace.global_id).to eq('GLN') }
describe :to_param do describe :to_param do
it { expect(namespace.to_param).to eq(namespace.path) } it { expect(namespace.to_param).to eq(namespace.path) }
end end
...@@ -85,4 +83,14 @@ describe Namespace do ...@@ -85,4 +83,14 @@ describe Namespace do
it { expect(Namespace.find_by_path_or_name('WOW')).to eq(@namespace) } it { expect(Namespace.find_by_path_or_name('WOW')).to eq(@namespace) }
it { expect(Namespace.find_by_path_or_name('unknown')).to eq(nil) } it { expect(Namespace.find_by_path_or_name('unknown')).to eq(nil) }
end end
describe ".clean_path" do
let!(:user) { create(:user, username: "johngitlab-etc") }
let!(:namespace) { create(:namespace, path: "JohnGitLab-etc1") }
it "cleans the path and makes sure it's available" do
expect(Namespace.clean_path("-john+gitlab-ETC%.git@gmail.com")).to eq("johngitlab-ETC2")
end
end
end end
...@@ -31,6 +31,7 @@ describe GitlabIssueTrackerService do ...@@ -31,6 +31,7 @@ describe GitlabIssueTrackerService do
context 'with absolute urls' do context 'with absolute urls' do
before do before do
GitlabIssueTrackerService.default_url_options[:script_name] = "/gitlab/root"
@service = project.create_gitlab_issue_tracker_service(active: true) @service = project.create_gitlab_issue_tracker_service(active: true)
end end
...@@ -39,15 +40,15 @@ describe GitlabIssueTrackerService do ...@@ -39,15 +40,15 @@ describe GitlabIssueTrackerService do
end end
it 'should give the correct path' do it 'should give the correct path' do
expect(@service.project_url).to eq("/#{project.path_with_namespace}/issues") expect(@service.project_url).to eq("http://localhost/gitlab/root/#{project.path_with_namespace}/issues")
expect(@service.new_issue_url).to eq("/#{project.path_with_namespace}/issues/new") expect(@service.new_issue_url).to eq("http://localhost/gitlab/root/#{project.path_with_namespace}/issues/new")
expect(@service.issue_url(432)).to eq("/#{project.path_with_namespace}/issues/432") expect(@service.issue_url(432)).to eq("http://localhost/gitlab/root/#{project.path_with_namespace}/issues/432")
end end
end end
context 'with enabled relative urls' do context 'with relative urls' do
before do before do
Settings.gitlab.stub(:relative_url_root).and_return("/gitlab/root") GitlabIssueTrackerService.default_url_options[:script_name] = "/gitlab/root"
@service = project.create_gitlab_issue_tracker_service(active: true) @service = project.create_gitlab_issue_tracker_service(active: true)
end end
...@@ -56,9 +57,9 @@ describe GitlabIssueTrackerService do ...@@ -56,9 +57,9 @@ describe GitlabIssueTrackerService do
end end
it 'should give the correct path' do it 'should give the correct path' do
expect(@service.project_url).to eq("/gitlab/root/#{project.path_with_namespace}/issues") expect(@service.project_path).to eq("/gitlab/root/#{project.path_with_namespace}/issues")
expect(@service.new_issue_url).to eq("/gitlab/root/#{project.path_with_namespace}/issues/new") expect(@service.new_issue_path).to eq("/gitlab/root/#{project.path_with_namespace}/issues/new")
expect(@service.issue_url(432)).to eq("/gitlab/root/#{project.path_with_namespace}/issues/432") expect(@service.issue_path(432)).to eq("/gitlab/root/#{project.path_with_namespace}/issues/432")
end end
end end
end end
......
...@@ -320,16 +320,6 @@ describe User do ...@@ -320,16 +320,6 @@ describe User do
end end
end end
describe ".clean_username" do
let!(:user) { create(:user, username: "johngitlab-etc") }
let!(:namespace) { create(:namespace, path: "JohnGitLab-etc1") }
it "cleans a username and makes sure it's available" do
expect(User.clean_username("-john+gitlab-ETC%.git@gmail.com")).to eq("johngitlab-ETC2")
end
end
describe 'all_ssh_keys' do describe 'all_ssh_keys' do
it { is_expected.to have_many(:keys).dependent(:destroy) } it { is_expected.to have_many(:keys).dependent(:destroy) }
......
...@@ -247,12 +247,12 @@ describe API::API, api: true do ...@@ -247,12 +247,12 @@ describe API::API, api: true do
expect(json_response['message']['name']).to eq([ expect(json_response['message']['name']).to eq([
'can\'t be blank', 'can\'t be blank',
'is too short (minimum is 0 characters)', 'is too short (minimum is 0 characters)',
Gitlab::Regex.project_regex_message Gitlab::Regex.project_name_regex_message
]) ])
expect(json_response['message']['path']).to eq([ expect(json_response['message']['path']).to eq([
'can\'t be blank', 'can\'t be blank',
'is too short (minimum is 0 characters)', 'is too short (minimum is 0 characters)',
Gitlab::Regex.send(:default_regex_message) Gitlab::Regex.send(:project_path_regex_message)
]) ])
end end
......
...@@ -152,7 +152,7 @@ describe API::API, api: true do ...@@ -152,7 +152,7 @@ describe API::API, api: true do
expect(json_response['message']['projects_limit']). expect(json_response['message']['projects_limit']).
to eq(['must be greater than or equal to 0']) to eq(['must be greater than or equal to 0'])
expect(json_response['message']['username']). expect(json_response['message']['username']).
to eq([Gitlab::Regex.send(:default_regex_message)]) to eq([Gitlab::Regex.send(:namespace_regex_message)])
end end
it "shouldn't available for non admin users" do it "shouldn't available for non admin users" do
...@@ -278,7 +278,7 @@ describe API::API, api: true do ...@@ -278,7 +278,7 @@ describe API::API, api: true do
expect(json_response['message']['projects_limit']). expect(json_response['message']['projects_limit']).
to eq(['must be greater than or equal to 0']) to eq(['must be greater than or equal to 0'])
expect(json_response['message']['username']). expect(json_response['message']['username']).
to eq([Gitlab::Regex.send(:default_regex_message)]) to eq([Gitlab::Regex.send(:namespace_regex_message)])
end end
context "with existing user" do context "with existing user" do
......
...@@ -73,41 +73,41 @@ end ...@@ -73,41 +73,41 @@ end
# help_markdown GET /help/markdown(.:format) help#markdown # help_markdown GET /help/markdown(.:format) help#markdown
# help_ssh GET /help/ssh(.:format) help#ssh # help_ssh GET /help/ssh(.:format) help#ssh
# help_raketasks GET /help/raketasks(.:format) help#raketasks # help_raketasks GET /help/raketasks(.:format) help#raketasks
describe HelpController, "routing" do describe HelpController, 'routing' do
it "to #index" do it 'to #index' do
expect(get("/help")).to route_to('help#index') expect(get('/help')).to route_to('help#index')
end end
it "to #permissions" do it 'to #permissions' do
expect(get("/help/permissions/permissions")).to route_to('help#show', category: "permissions", file: "permissions") expect(get('/help/permissions/permissions')).to route_to('help#show', filepath: 'permissions/permissions')
end end
it "to #workflow" do it 'to #workflow' do
expect(get("/help/workflow/README")).to route_to('help#show', category: "workflow", file: "README") expect(get('/help/workflow/README')).to route_to('help#show', filepath: 'workflow/README')
end end
it "to #api" do it 'to #api' do
expect(get("/help/api/README")).to route_to('help#show', category: "api", file: "README") expect(get('/help/api/README')).to route_to('help#show', filepath: 'api/README')
end end
it "to #web_hooks" do it 'to #web_hooks' do
expect(get("/help/web_hooks/web_hooks")).to route_to('help#show', category: "web_hooks", file: "web_hooks") expect(get('/help/web_hooks/web_hooks')).to route_to('help#show', filepath: 'web_hooks/web_hooks')
end end
it "to #system_hooks" do it 'to #system_hooks' do
expect(get("/help/system_hooks/system_hooks")).to route_to('help#show', category: "system_hooks", file: "system_hooks") expect(get('/help/system_hooks/system_hooks')).to route_to('help#show', filepath: 'system_hooks/system_hooks')
end end
it "to #markdown" do it 'to #markdown' do
expect(get("/help/markdown/markdown")).to route_to('help#show',category: "markdown", file: "markdown") expect(get('/help/markdown/markdown')).to route_to('help#show',filepath: 'markdown/markdown')
end end
it "to #ssh" do it 'to #ssh' do
expect(get("/help/ssh/README")).to route_to('help#show', category: "ssh", file: "README") expect(get('/help/ssh/README')).to route_to('help#show', filepath: 'ssh/README')
end end
it "to #raketasks" do it 'to #raketasks' do
expect(get("/help/raketasks/README")).to route_to('help#show', category: "raketasks", file: "README") expect(get('/help/raketasks/README')).to route_to('help#show', filepath: 'raketasks/README')
end end
end end
......
require 'spec_helper'
describe ArchiveRepositoryService do
let(:project) { create(:project) }
subject { ArchiveRepositoryService.new(project, "master", "zip") }
describe "#execute" do
it "cleans old archives" do
expect(project.repository).to receive(:clean_old_archives)
subject.execute(timeout: 0.0)
end
context "when the repository doesn't have an archive file path" do
before do
allow(project.repository).to receive(:archive_file_path).and_return(nil)
end
it "raises an error" do
expect {
subject.execute(timeout: 0.0)
}.to raise_error
end
end
context "when the repository has an archive file path" do
let(:file_path) { "/archive.zip" }
let(:pid_file_path) { "/archive.zip.pid" }
before do
allow(project.repository).to receive(:archive_file_path).and_return(file_path)
allow(project.repository).to receive(:archive_pid_file_path).and_return(pid_file_path)
end
context "when the archive file already exists" do
before do
allow(File).to receive(:exist?).with(file_path).and_return(true)
end
it "returns the file path" do
expect(subject.execute(timeout: 0.0)).to eq(file_path)
end
end
context "when the archive file doesn't exist yet" do
before do
allow(File).to receive(:exist?).with(file_path).and_return(false)
allow(File).to receive(:exist?).with(pid_file_path).and_return(true)
end
context "when the archive pid file doesn't exist yet" do
before do
allow(File).to receive(:exist?).with(pid_file_path).and_return(false)
end
it "queues the RepositoryArchiveWorker" do
expect(RepositoryArchiveWorker).to receive(:perform_async)
subject.execute(timeout: 0.0)
end
end
context "when the archive pid file already exists" do
it "doesn't queue the RepositoryArchiveWorker" do
expect(RepositoryArchiveWorker).not_to receive(:perform_async)
subject.execute(timeout: 0.0)
end
end
context "when the archive file exists after a little while" do
before do
Thread.new do
sleep 0.1
allow(File).to receive(:exist?).with(file_path).and_return(true)
end
end
it "returns the file path" do
expect(subject.execute(timeout: 0.2)).to eq(file_path)
end
end
context "when the archive file doesn't exist after the timeout" do
it "returns nil" do
expect(subject.execute(timeout: 0.0)).to eq(nil)
end
end
end
end
end
end
...@@ -44,3 +44,5 @@ RSpec.configure do |config| ...@@ -44,3 +44,5 @@ RSpec.configure do |config|
TestEnv.init TestEnv.init
end end
end end
ActiveRecord::Migration.maintain_test_schema!
...@@ -85,7 +85,7 @@ module TestEnv ...@@ -85,7 +85,7 @@ module TestEnv
end end
# We must copy bare repositories because we will push to them. # We must copy bare repositories because we will push to them.
system(*%W(git clone -q --bare #{factory_repo_path} #{factory_repo_path_bare})) system(git_env, *%W(git clone -q --bare #{factory_repo_path} #{factory_repo_path_bare}))
end end
def copy_repo(project) def copy_repo(project)
...@@ -113,4 +113,10 @@ module TestEnv ...@@ -113,4 +113,10 @@ module TestEnv
def factory_repo_name def factory_repo_name
'gitlab-test' 'gitlab-test'
end end
# Prevent developer git configurations from being persisted to test
# repositories
def git_env
{'GIT_TEMPLATE_DIR' => ''}
end
end end
...@@ -87,7 +87,7 @@ describe 'gitlab:app namespace rake task' do ...@@ -87,7 +87,7 @@ describe 'gitlab:app namespace rake task' do
expect(tar_contents).to match('db/') expect(tar_contents).to match('db/')
expect(tar_contents).to match('uploads/') expect(tar_contents).to match('uploads/')
expect(tar_contents).to match('repositories/') expect(tar_contents).to match('repositories/')
expect(tar_contents).not_to match(/^.{4,9}[rwx]/) expect(tar_contents).not_to match(/^.{4,9}[rwx].*(db|uploads|repositories)\/$/)
end end
it 'should delete temp directories' do it 'should delete temp directories' do
...@@ -98,4 +98,55 @@ describe 'gitlab:app namespace rake task' do ...@@ -98,4 +98,55 @@ describe 'gitlab:app namespace rake task' do
expect(temp_dirs).to be_empty expect(temp_dirs).to be_empty
end end
end # backup_create task end # backup_create task
describe "Skipping items" do
def tars_glob
Dir.glob(File.join(Gitlab.config.backup.path, '*_gitlab_backup.tar'))
end
before :all do
@origin_cd = Dir.pwd
Rake::Task["gitlab:backup:db:create"].reenable
Rake::Task["gitlab:backup:repo:create"].reenable
Rake::Task["gitlab:backup:uploads:create"].reenable
# Record the existing backup tars so we don't touch them
existing_tars = tars_glob
# Redirect STDOUT and run the rake task
orig_stdout = $stdout
$stdout = StringIO.new
ENV["SKIP"] = "repositories"
run_rake_task('gitlab:backup:create')
$stdout = orig_stdout
@backup_tar = (tars_glob - existing_tars).first
end
after :all do
FileUtils.rm(@backup_tar)
Dir.chdir @origin_cd
end
it "does not contain skipped item" do
tar_contents, exit_status = Gitlab::Popen.popen(
%W{tar -tvf #{@backup_tar} db uploads repositories}
)
expect(tar_contents).to match('db/')
expect(tar_contents).to match('uploads/')
expect(tar_contents).not_to match('repositories/')
end
it 'does not invoke repositories restore' do
Rake::Task["gitlab:shell:setup"].stub invoke: true
allow($stdout).to receive :write
expect(Rake::Task["gitlab:backup:db:restore"]).to receive :invoke
expect(Rake::Task["gitlab:backup:repo:restore"]).not_to receive :invoke
expect(Rake::Task["gitlab:shell:setup"]).to receive :invoke
expect { run_rake_task('gitlab:backup:restore') }.to_not raise_error
end
end
end # gitlab:app namespace end # gitlab:app namespace
require 'spec_helper'
describe RepositoryArchiveWorker do
let(:project) { create(:project) }
subject { RepositoryArchiveWorker.new }
before do
allow(Project).to receive(:find).and_return(project)
end
describe "#perform" do
it "cleans old archives" do
expect(project.repository).to receive(:clean_old_archives)
subject.perform(project.id, "master", "zip")
end
context "when the repository doesn't have an archive file path" do
before do
allow(project.repository).to receive(:archive_file_path).and_return(nil)
end
it "doesn't archive the repo" do
expect(project.repository).not_to receive(:archive_repo)
subject.perform(project.id, "master", "zip")
end
end
context "when the repository has an archive file path" do
let(:file_path) { "/archive.zip" }
let(:pid_file_path) { "/archive.zip.pid" }
before do
allow(project.repository).to receive(:archive_file_path).and_return(file_path)
allow(project.repository).to receive(:archive_pid_file_path).and_return(pid_file_path)
end
context "when the archive file already exists" do
before do
allow(File).to receive(:exist?).with(file_path).and_return(true)
end
it "doesn't archive the repo" do
expect(project.repository).not_to receive(:archive_repo)
subject.perform(project.id, "master", "zip")
end
end
context "when the archive file doesn't exist yet" do
before do
allow(File).to receive(:exist?).with(file_path).and_return(false)
allow(File).to receive(:exist?).with(pid_file_path).and_return(true)
end
context "when the archive pid file doesn't exist yet" do
before do
allow(File).to receive(:exist?).with(pid_file_path).and_return(false)
end
it "archives the repo" do
expect(project.repository).to receive(:archive_repo)
subject.perform(project.id, "master", "zip")
end
end
context "when the archive pid file already exists" do
it "doesn't archive the repo" do
expect(project.repository).not_to receive(:archive_repo)
subject.perform(project.id, "master", "zip")
end
end
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment