mirror of
https://github.com/wahyd4/gitlabhq.git
synced 2026-08-21 10:36:04 +10:00
Merge branch 'ce-to-ee' into 'master'
Recent changes from CE master See merge request !367
This commit is contained in:
@@ -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)
|
||||
- Allow HTML tags in Markdown input
|
||||
- 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 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)
|
||||
- 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)
|
||||
@@ -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)
|
||||
- Fix a link in the patch update guide
|
||||
- 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.
|
||||
- Add sidetiq gem dependency to match EE
|
||||
- Add changelog, license and contribution guide links to project sidebar.
|
||||
@@ -24,6 +22,7 @@ v 7.10.0 (unreleased)
|
||||
- Fix alignment of navbar toggle button (Cody Mize)
|
||||
- Fix checkbox rendering for nested task lists
|
||||
- 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.
|
||||
- Improve error message when save profile has error.
|
||||
- Passing the name of pushed ref to CI service (requires GitLab CI 7.9+)
|
||||
@@ -40,7 +39,6 @@ v 7.10.0 (unreleased)
|
||||
v 7.9.0 (unreleased)
|
||||
- Replace commits calendar with faster contribution calendar that includes issues and merge requests
|
||||
- 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 mark merge request as updated when merge status relative to target branch changes.
|
||||
- Link note avatar to user.
|
||||
@@ -50,13 +48,31 @@ v 7.9.0 (unreleased)
|
||||
- AJAX selectbox for issue assignee and author filters
|
||||
- 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 note form from being cleared when submitting failed.
|
||||
- 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.
|
||||
- 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.9.2
|
||||
- Contains no changes
|
||||
|
||||
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
|
||||
- Add HipChat integration documentation (Stan Hu)
|
||||
@@ -138,6 +154,10 @@ v 7.9.0
|
||||
- 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)
|
||||
- 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
|
||||
- Fix issue_tracker_id substitution in custom issue trackers
|
||||
|
||||
@@ -39,7 +39,7 @@ gem "browser"
|
||||
|
||||
# Extracting information from a git repository
|
||||
# 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
|
||||
gem 'gitlab-grack', '~> 2.0.0.rc2', require: 'grack'
|
||||
@@ -116,13 +116,14 @@ end
|
||||
gem "state_machine"
|
||||
|
||||
# Issue tags
|
||||
gem "acts-as-taggable-on"
|
||||
gem 'acts-as-taggable-on', '~> 3.4'
|
||||
|
||||
# Background jobs
|
||||
gem 'slim'
|
||||
gem 'sinatra', require: nil
|
||||
gem 'sidekiq', '~> 3.3'
|
||||
gem 'sidetiq', '0.6.3'
|
||||
gem 'sidekiq-limit_fetch'
|
||||
|
||||
# HTTP requests
|
||||
gem "httparty"
|
||||
|
||||
+8
-5
@@ -33,8 +33,8 @@ GEM
|
||||
minitest (~> 5.1)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo (~> 1.1)
|
||||
acts-as-taggable-on (2.4.1)
|
||||
rails (>= 3, < 5)
|
||||
acts-as-taggable-on (3.5.0)
|
||||
activerecord (>= 3.2, < 5)
|
||||
addressable (2.3.5)
|
||||
annotate (2.6.0)
|
||||
activerecord (>= 2.3.0)
|
||||
@@ -212,7 +212,7 @@ GEM
|
||||
mime-types (~> 1.19)
|
||||
gitlab_emoji (0.1.0)
|
||||
gemojione (~> 2.0)
|
||||
gitlab_git (7.1.2)
|
||||
gitlab_git (7.1.3)
|
||||
activesupport (~> 4.0)
|
||||
charlock_holmes (~> 0.6)
|
||||
gitlab-linguist (~> 3.0)
|
||||
@@ -547,6 +547,8 @@ GEM
|
||||
json
|
||||
redis (>= 3.0.6)
|
||||
redis-namespace (>= 1.3.1)
|
||||
sidekiq-limit_fetch (2.4.1)
|
||||
sidekiq (>= 2.6.5, < 4.0)
|
||||
sidetiq (0.6.3)
|
||||
celluloid (>= 0.14.1)
|
||||
ice_cube (= 0.11.1)
|
||||
@@ -662,7 +664,7 @@ PLATFORMS
|
||||
DEPENDENCIES
|
||||
RedCloth
|
||||
ace-rails-ap
|
||||
acts-as-taggable-on
|
||||
acts-as-taggable-on (~> 3.4)
|
||||
addressable
|
||||
annotate (~> 2.6.0.beta2)
|
||||
asana (~> 0.0.6)
|
||||
@@ -703,7 +705,7 @@ DEPENDENCIES
|
||||
gitlab-grack (~> 2.0.0.rc2)
|
||||
gitlab-linguist (~> 3.0.1)
|
||||
gitlab_emoji (~> 0.1)
|
||||
gitlab_git (~> 7.1.2)
|
||||
gitlab_git (~> 7.1.3)
|
||||
gitlab_meta (= 7.0)
|
||||
gitlab_omniauth-ldap (= 1.2.1)
|
||||
gollum-lib (~> 4.0.2)
|
||||
@@ -770,6 +772,7 @@ DEPENDENCIES
|
||||
settingslogic
|
||||
shoulda-matchers (~> 2.7.0)
|
||||
sidekiq (~> 3.3)
|
||||
sidekiq-limit_fetch
|
||||
sidetiq (= 0.6.3)
|
||||
simplecov
|
||||
sinatra
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
The GitLab Enterprise Edition (EE) license
|
||||
|
||||
Copyright (c) 2013-2014 GitLab B.V.
|
||||
Copyright (c) 2011-2015 GitLab B.V.
|
||||
|
||||
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
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
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
|
||||
position: "top"
|
||||
legend: [
|
||||
0
|
||||
1
|
||||
4
|
||||
7
|
||||
10
|
||||
20
|
||||
30
|
||||
]
|
||||
legendCellPadding: 3
|
||||
onClick: (date, count) ->
|
||||
|
||||
@@ -104,6 +104,8 @@ class Dispatcher
|
||||
new GroupsSelect()
|
||||
when 'admin:emails:show'
|
||||
new AdminEmailSelect()
|
||||
when 'admin:users:show'
|
||||
new ProjectsList()
|
||||
|
||||
switch path.first()
|
||||
when 'admin'
|
||||
|
||||
@@ -9,12 +9,8 @@ class @Issue
|
||||
if $("a.btn-close").length
|
||||
$("li.task-list-item input:checkbox").prop("disabled", false)
|
||||
|
||||
$(".task-list-item input:checkbox").on(
|
||||
"click"
|
||||
null
|
||||
"issue"
|
||||
updateTaskState
|
||||
)
|
||||
$('.task-list-item input:checkbox').off('change')
|
||||
$('.task-list-item input:checkbox').change('issue', updateTaskState)
|
||||
|
||||
$('.issue-details').waitForImages ->
|
||||
$('.issuable-affix').affix offset:
|
||||
|
||||
@@ -81,12 +81,8 @@ class @MergeRequest
|
||||
this.$('.remove_source_branch_in_progress').hide()
|
||||
this.$('.remove_source_branch_widget.failed').show()
|
||||
|
||||
$(".task-list-item input:checkbox").on(
|
||||
"click"
|
||||
null
|
||||
"merge_request"
|
||||
updateTaskState
|
||||
)
|
||||
$('.task-list-item input:checkbox').off('change')
|
||||
$('.task-list-item input:checkbox').change('merge_request', updateTaskState)
|
||||
|
||||
activateTab: (action) ->
|
||||
this.$('.merge-request-tabs li').removeClass 'active'
|
||||
|
||||
@@ -37,7 +37,8 @@ class @Notes
|
||||
$(document).on "click", ".js-note-attachment-delete", @removeAttachment
|
||||
|
||||
# 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
|
||||
$(document).on "change", ".js-note-attachment-input", @updateFormAttachment
|
||||
@@ -71,6 +72,7 @@ class @Notes
|
||||
$(document).off "click", ".js-note-delete"
|
||||
$(document).off "click", ".js-note-attachment-delete"
|
||||
$(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-add-diff-note-button"
|
||||
$(document).off "visibilitychange"
|
||||
@@ -170,6 +172,11 @@ class @Notes
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -3,12 +3,7 @@ $(document).on("click", '.toggle-nav-collapse', (e) ->
|
||||
collapsed = 'page-sidebar-collapsed'
|
||||
expanded = 'page-sidebar-expanded'
|
||||
|
||||
if $('.page-with-sidebar').hasClass(collapsed)
|
||||
$('.page-with-sidebar').removeClass(collapsed).addClass(expanded)
|
||||
$('.toggle-nav-collapse i').removeClass('fa-angle-right').addClass('fa-angle-left')
|
||||
$.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: '/' })
|
||||
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
|
||||
$('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
|
||||
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
|
||||
)
|
||||
|
||||
@@ -246,7 +246,7 @@ li.note {
|
||||
|
||||
.milestone {
|
||||
&.milestone-closed {
|
||||
background: #eee;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
.progress {
|
||||
margin-bottom: 0;
|
||||
|
||||
@@ -24,13 +24,6 @@
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.project-home-panel {
|
||||
.star-fork-buttons {
|
||||
padding-top: 10px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.project-home-links {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -49,35 +49,20 @@
|
||||
@extend .clearfix;
|
||||
margin-bottom: 15px;
|
||||
|
||||
.project-home-desc,
|
||||
.star-fork-buttons {
|
||||
&.project-home-row-top {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.project-home-desc {
|
||||
font-size: 16px;
|
||||
line-height: 1.3;
|
||||
margin-right: 215px;
|
||||
}
|
||||
|
||||
.project-home-desc {
|
||||
float: left;
|
||||
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 {
|
||||
@@ -87,6 +72,27 @@
|
||||
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 {
|
||||
|
||||
@@ -3,23 +3,37 @@ class HelpController < ApplicationController
|
||||
end
|
||||
|
||||
def show
|
||||
@category = params[:category]
|
||||
@file = params[:file]
|
||||
format = params[:format] || 'md'
|
||||
file_path = Rails.root.join('doc', @category, @file + ".#{format}")
|
||||
@filepath = params[:filepath]
|
||||
@format = params[:format]
|
||||
|
||||
if %w(png jpg jpeg gif).include?(format)
|
||||
send_file file_path, disposition: 'inline'
|
||||
elsif File.exists?(file_path)
|
||||
render 'show'
|
||||
else
|
||||
not_found!
|
||||
respond_to do |format|
|
||||
format.md { render_doc }
|
||||
format.all { send_file_data }
|
||||
end
|
||||
end
|
||||
|
||||
def shortcuts
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def render_doc
|
||||
if File.exists?(Rails.root.join('doc', @filepath + '.md'))
|
||||
render 'show.html.haml'
|
||||
else
|
||||
not_found!
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def ui
|
||||
end
|
||||
end
|
||||
|
||||
@@ -36,8 +36,11 @@ class Import::BitbucketController < Import::BaseController
|
||||
def create
|
||||
@repo_id = params[:repo_id] || ""
|
||||
repo = client.project(@repo_id.gsub("___", "/"))
|
||||
@target_namespace = params[:new_namespace].presence || repo["owner"]
|
||||
@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)
|
||||
|
||||
|
||||
@@ -31,8 +31,11 @@ class Import::GithubController < Import::BaseController
|
||||
def create
|
||||
@repo_id = params[:repo_id].to_i
|
||||
repo = client.repo(@repo_id)
|
||||
@target_namespace = params[:new_namespace].presence || repo.owner.login
|
||||
@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)
|
||||
|
||||
|
||||
@@ -28,8 +28,11 @@ class Import::GitlabController < Import::BaseController
|
||||
def create
|
||||
@repo_id = params[:repo_id].to_i
|
||||
repo = client.project(@repo_id)
|
||||
@target_namespace = params[:new_namespace].presence || repo["namespace"]["path"]
|
||||
@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)
|
||||
|
||||
|
||||
@@ -4,14 +4,22 @@ class NamespacesController < ApplicationController
|
||||
def show
|
||||
namespace = Namespace.find_by(path: params[:id])
|
||||
|
||||
unless namespace
|
||||
return render_404
|
||||
if namespace
|
||||
if namespace.is_a?(Group)
|
||||
group = namespace
|
||||
else
|
||||
user = namespace.owner
|
||||
end
|
||||
end
|
||||
|
||||
if namespace.type == "Group"
|
||||
redirect_to group_path(namespace)
|
||||
if user
|
||||
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
|
||||
redirect_to user_path(namespace.owner)
|
||||
render_404
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,18 +11,18 @@ class Projects::RepositoriesController < Projects::ApplicationController
|
||||
end
|
||||
|
||||
def archive
|
||||
unless can?(current_user, :download_code, @project)
|
||||
render_404 and return
|
||||
begin
|
||||
file_path = ArchiveRepositoryService.new(@project, params[:ref], params[:format]).execute
|
||||
rescue
|
||||
return head :not_found
|
||||
end
|
||||
|
||||
file_path = ArchiveRepositoryService.new.execute(@project, params[:ref], params[:format])
|
||||
|
||||
if file_path
|
||||
# Send file to user
|
||||
response.headers["Content-Length"] = File.open(file_path).size.to_s
|
||||
send_file file_path
|
||||
else
|
||||
render_404
|
||||
redirect_to request.fullpath
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -29,6 +29,10 @@ module GitlabRoutingHelper
|
||||
namespace_project_merge_request_path(entity.project.namespace, entity.project, entity, *args)
|
||||
end
|
||||
|
||||
def milestone_path(entity, *args)
|
||||
namespace_project_milestone_path(entity.project.namespace, entity.project, entity, *args)
|
||||
end
|
||||
|
||||
def project_url(project, *args)
|
||||
namespace_project_url(project.namespace, project, *args)
|
||||
end
|
||||
|
||||
@@ -40,7 +40,7 @@ module IconsHelper
|
||||
def file_type_icon_class(type, mode, name)
|
||||
if type == 'folder'
|
||||
icon_class = 'folder'
|
||||
elsif mode == 0120000
|
||||
elsif mode == '120000'
|
||||
icon_class = 'share'
|
||||
else
|
||||
# Guess which icon to choose based on file extension.
|
||||
|
||||
@@ -13,22 +13,34 @@ module IssuesHelper
|
||||
OpenStruct.new(id: 0, title: 'None (backlog)', name: 'Unassigned')
|
||||
end
|
||||
|
||||
def url_for_project_issues(project = @project)
|
||||
def url_for_project_issues(project = @project, options = {})
|
||||
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
|
||||
|
||||
def url_for_new_issue(project = @project)
|
||||
def url_for_new_issue(project = @project, options = {})
|
||||
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
|
||||
|
||||
def url_for_issue(issue_iid, project = @project)
|
||||
def url_for_issue(issue_iid, project = @project, options = {})
|
||||
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
|
||||
|
||||
def title_for_issue(issue_iid, project = @project)
|
||||
|
||||
@@ -80,17 +80,17 @@ module ProjectsHelper
|
||||
@project.milestones.active.order("due_date, title ASC")
|
||||
end
|
||||
|
||||
def link_to_toggle_star(title, starred, signed_in)
|
||||
cls = 'star-btn'
|
||||
cls << ' disabled' unless signed_in
|
||||
def link_to_toggle_star(title, starred)
|
||||
cls = 'star-btn btn btn-sm btn-default'
|
||||
|
||||
toggle_text =
|
||||
if starred
|
||||
' Unstar'
|
||||
else
|
||||
' Star'
|
||||
end
|
||||
|
||||
toggle_html = content_tag('span', class: 'toggle') do
|
||||
toggle_text = if starred
|
||||
' Unstar'
|
||||
else
|
||||
' Star'
|
||||
end
|
||||
|
||||
icon('star') + toggle_text
|
||||
end
|
||||
|
||||
@@ -106,23 +106,25 @@ module ProjectsHelper
|
||||
data: { type: 'json' }
|
||||
}
|
||||
|
||||
path = toggle_star_namespace_project_path(@project.namespace, @project)
|
||||
|
||||
content_tag 'span', class: starred ? 'turn-on' : 'turn-off' do
|
||||
link_to(
|
||||
toggle_star_namespace_project_path(@project.namespace, @project),
|
||||
link_opts
|
||||
) do
|
||||
link_to(path, link_opts) do
|
||||
toggle_html + ' ' + count_html
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def link_to_toggle_fork
|
||||
out = icon('code-fork')
|
||||
out << ' Fork'
|
||||
out << content_tag(:span, class: 'count') do
|
||||
html = content_tag('span') do
|
||||
icon('code-fork') + ' Fork'
|
||||
end
|
||||
|
||||
count_html = content_tag(:span, class: 'count') do
|
||||
@project.forks_count.to_s
|
||||
end
|
||||
|
||||
html + count_html
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -52,7 +52,7 @@ module Mentionable
|
||||
if identifier == "all"
|
||||
users.push(*project.team.members.flatten)
|
||||
elsif namespace = Namespace.find_by(path: identifier)
|
||||
if namespace.type == "Group"
|
||||
if namespace.is_a?(Group)
|
||||
users.push(*namespace.users)
|
||||
else
|
||||
users << namespace.owner
|
||||
|
||||
+31
-16
@@ -24,8 +24,8 @@ class Namespace < ActiveRecord::Base
|
||||
validates :name,
|
||||
presence: true, uniqueness: true,
|
||||
length: { within: 0..255 },
|
||||
format: { with: Gitlab::Regex.name_regex,
|
||||
message: Gitlab::Regex.name_regex_message }
|
||||
format: { with: Gitlab::Regex.namespace_name_regex,
|
||||
message: Gitlab::Regex.namespace_name_regex_message }
|
||||
|
||||
validates :description, length: { within: 0..255 }
|
||||
validates :path,
|
||||
@@ -33,8 +33,8 @@ class Namespace < ActiveRecord::Base
|
||||
presence: true,
|
||||
length: { within: 1..255 },
|
||||
exclusion: { in: Gitlab::Blacklist.path },
|
||||
format: { with: Gitlab::Regex.path_regex,
|
||||
message: Gitlab::Regex.path_regex_message }
|
||||
format: { with: Gitlab::Regex.namespace_regex,
|
||||
message: Gitlab::Regex.namespace_regex_message }
|
||||
|
||||
delegate :name, to: :owner, allow_nil: true, prefix: true
|
||||
|
||||
@@ -44,21 +44,36 @@ class Namespace < ActiveRecord::Base
|
||||
|
||||
scope :root, -> { where('type IS NULL') }
|
||||
|
||||
def self.by_path(path)
|
||||
where('lower(path) = :value', value: path.downcase).first
|
||||
end
|
||||
class << self
|
||||
def by_path(path)
|
||||
where('lower(path) = :value', value: path.downcase).first
|
||||
end
|
||||
|
||||
# Case insensetive search for namespace by path or name
|
||||
def self.find_by_path_or_name(path)
|
||||
find_by("lower(path) = :path OR lower(name) = :path", path: path.downcase)
|
||||
end
|
||||
# Case insensetive search for namespace by path or name
|
||||
def find_by_path_or_name(path)
|
||||
find_by("lower(path) = :path OR lower(name) = :path", path: path.downcase)
|
||||
end
|
||||
|
||||
def self.search(query)
|
||||
where("name LIKE :query OR path LIKE :query", query: "%#{query}%")
|
||||
end
|
||||
def search(query)
|
||||
where("name LIKE :query OR path LIKE :query", query: "%#{query}%")
|
||||
end
|
||||
|
||||
def self.global_id
|
||||
'GLN'
|
||||
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
|
||||
|
||||
path
|
||||
end
|
||||
end
|
||||
|
||||
def to_param
|
||||
|
||||
@@ -130,12 +130,12 @@ class Project < ActiveRecord::Base
|
||||
presence: true,
|
||||
length: { within: 0..255 },
|
||||
format: { with: Gitlab::Regex.project_name_regex,
|
||||
message: Gitlab::Regex.project_regex_message }
|
||||
message: Gitlab::Regex.project_name_regex_message }
|
||||
validates :path,
|
||||
presence: true,
|
||||
length: { within: 0..255 },
|
||||
format: { with: Gitlab::Regex.path_regex,
|
||||
message: Gitlab::Regex.path_regex_message }
|
||||
format: { with: Gitlab::Regex.project_path_regex,
|
||||
message: Gitlab::Regex.project_path_regex_message }
|
||||
validates :issues_enabled, :merge_requests_enabled,
|
||||
:wiki_enabled, inclusion: { in: [true, false] }
|
||||
validates :issues_tracker_id, length: { maximum: 255 }, allow_blank: true
|
||||
|
||||
@@ -20,8 +20,13 @@
|
||||
|
||||
class GitlabIssueTrackerService < IssueTrackerService
|
||||
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?
|
||||
true
|
||||
@@ -32,20 +37,26 @@ class GitlabIssueTrackerService < IssueTrackerService
|
||||
end
|
||||
|
||||
def project_url
|
||||
"#{gitlab_url}#{namespace_project_issues_path(project.namespace, project)}"
|
||||
namespace_project_issues_url(project.namespace, project)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
private
|
||||
def project_path
|
||||
namespace_project_issues_path(project.namespace, project)
|
||||
end
|
||||
|
||||
def gitlab_url
|
||||
Gitlab.config.gitlab.relative_url_root.chomp("/") if Gitlab.config.gitlab.relative_url_root
|
||||
def new_issue_path
|
||||
new_namespace_project_issue_path(namespace_id: project.namespace, project_id: project)
|
||||
end
|
||||
|
||||
def issue_path(iid)
|
||||
namespace_project_issue_path(namespace_id: project.namespace, project_id: project, id: iid)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -38,6 +38,18 @@ class IssueTrackerService < Service
|
||||
self.issues_url.gsub(':id', iid.to_s)
|
||||
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
|
||||
[
|
||||
{ type: 'text', name: 'description', placeholder: description },
|
||||
|
||||
@@ -267,6 +267,9 @@ class Repository
|
||||
# Remove archives older than 2 hours
|
||||
def clean_old_archives
|
||||
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))
|
||||
end
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@ class Snippet < ActiveRecord::Base
|
||||
validates :file_name,
|
||||
presence: true,
|
||||
length: { within: 0..255 },
|
||||
format: { with: Gitlab::Regex.path_regex,
|
||||
message: Gitlab::Regex.path_regex_message }
|
||||
format: { with: Gitlab::Regex.file_name_regex,
|
||||
message: Gitlab::Regex.file_name_regex_message }
|
||||
validates :content, presence: true
|
||||
validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values }
|
||||
|
||||
|
||||
+3
-3
@@ -129,8 +129,8 @@ class User < ActiveRecord::Base
|
||||
presence: true,
|
||||
uniqueness: { case_sensitive: false },
|
||||
exclusion: { in: Gitlab::Blacklist.path },
|
||||
format: { with: Gitlab::Regex.username_regex,
|
||||
message: Gitlab::Regex.username_regex_message }
|
||||
format: { with: Gitlab::Regex.namespace_regex,
|
||||
message: Gitlab::Regex.namespace_regex_message }
|
||||
|
||||
validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true
|
||||
validate :namespace_uniq, if: ->(user) { user.username_changed? }
|
||||
@@ -238,7 +238,7 @@ class User < ActiveRecord::Base
|
||||
|
||||
def non_ldap
|
||||
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
|
||||
|
||||
def clean_username(username)
|
||||
|
||||
@@ -1,14 +1,62 @@
|
||||
class ArchiveRepositoryService
|
||||
def execute(project, ref, format)
|
||||
storage_path = Gitlab.config.gitlab.repository_downloads_path
|
||||
attr_reader :project, :ref, :format
|
||||
|
||||
unless File.directory?(storage_path)
|
||||
FileUtils.mkdir_p(storage_path)
|
||||
def initialize(project, ref, format)
|
||||
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
|
||||
|
||||
format ||= 'tar.gz'
|
||||
repository = project.repository
|
||||
repository.clean_old_archives
|
||||
repository.archive_repo(ref, storage_path, format.downcase)
|
||||
archived = wait_until_archived(options[:timeout] || 5.0)
|
||||
|
||||
file_path if archived
|
||||
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
|
||||
|
||||
@@ -16,10 +16,10 @@ module Files
|
||||
file_name = File.basename(path)
|
||||
file_path = path
|
||||
|
||||
unless file_name =~ Gitlab::Regex.path_regex
|
||||
unless file_name =~ Gitlab::Regex.file_name_regex
|
||||
return error(
|
||||
'Your changes could not be committed, because the file name ' +
|
||||
Gitlab::Regex.path_regex_message
|
||||
Gitlab::Regex.file_name_regex_message
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
%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'
|
||||
|
||||
= link_to dashboard_milestone_path(milestone.safe_title, title: milestone.title) do
|
||||
= pluralize milestone.merge_requests_count, 'Merge Request'
|
||||
|
||||
%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 @@
|
||||
.nothing-here-block No milestones to show
|
||||
- else
|
||||
- @dashboard_milestones.each do |milestone|
|
||||
%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)
|
||||
%div
|
||||
%div
|
||||
= link_to dashboard_milestone_path(milestone.safe_title, title: milestone.title) do
|
||||
= pluralize milestone.issue_count, 'Issue'
|
||||
|
||||
= link_to dashboard_milestone_path(milestone.safe_title, title: milestone.title) do
|
||||
= pluralize milestone.merge_requests_count, 'Merge Request'
|
||||
|
||||
%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
|
||||
= render 'milestone', milestone: milestone
|
||||
= paginate @dashboard_milestones, theme: "gitlab"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
= form_for @group_member, url: group_group_members_path(@group), html: { class: 'form-horizontal users-group-form' } do |f|
|
||||
.form-group
|
||||
= 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
|
||||
= f.label :access_level, "Group Access", class: 'control-label'
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
%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'
|
||||
|
||||
= link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do
|
||||
= pluralize milestone.merge_requests_count, 'Merge Request'
|
||||
|
||||
%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 @@
|
||||
.nothing-here-block No milestones to show
|
||||
- else
|
||||
- @group_milestones.each do |milestone|
|
||||
%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)
|
||||
%div
|
||||
%div
|
||||
= link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do
|
||||
= pluralize milestone.issue_count, 'Issue'
|
||||
|
||||
= link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do
|
||||
= pluralize milestone.merge_requests_count, 'Merge Request'
|
||||
|
||||
%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
|
||||
= render 'milestone', milestone: milestone
|
||||
= paginate @group_milestones, theme: "gitlab"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
.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 @@
|
||||
|
||||
- if project_nav_tab? :issues
|
||||
= 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
|
||||
%span
|
||||
Issues
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
%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)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
-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?
|
||||
%p
|
||||
|
||||
@@ -6,4 +6,4 @@
|
||||
Assignee: #{@merge_request.author_name} → #{@merge_request.assignee_name}
|
||||
|
||||
-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 @@
|
||||
%ul.dropdown-menu
|
||||
- if @project.issues_enabled && can?(current_user, :write_issue, @project)
|
||||
%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
|
||||
- if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project)
|
||||
%li
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
.project-home-panel{:class => ("empty-project" if empty_repo)}
|
||||
.project-identicon-holder
|
||||
= project_icon(@project, alt: '', class: 'avatar project-avatar')
|
||||
.project-home-row
|
||||
.project-home-row.project-home-row-top
|
||||
.project-home-desc
|
||||
- if @project.description.present?
|
||||
= escaped_autolink(@project.description)
|
||||
@@ -14,31 +14,30 @@
|
||||
–
|
||||
= link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)) do
|
||||
= 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?
|
||||
.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
|
||||
= 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
|
||||
- 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
|
||||
|
||||
.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
|
||||
- if current_user && !empty_repo
|
||||
.project-home-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"
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
Download as
|
||||
%span.caret
|
||||
%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)
|
||||
= link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-primary btn-grouped" do
|
||||
%span Browse Code »
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
%ul.nav.nav-tabs
|
||||
= 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
|
||||
= link_to 'Compare', namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: @ref || @repository.root_ref)
|
||||
|
||||
|
||||
@@ -11,16 +11,14 @@
|
||||
%span.cred (Expired)
|
||||
%small
|
||||
= milestone.expires_at
|
||||
- if milestone.is_empty?
|
||||
%span.muted Empty
|
||||
- else
|
||||
%div
|
||||
%div
|
||||
= link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do
|
||||
= pluralize milestone.issues.count, 'Issue'
|
||||
|
||||
= link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do
|
||||
= pluralize milestone.merge_requests.count, 'Merge Request'
|
||||
|
||||
%span.light #{milestone.percent_complete}% complete
|
||||
.row
|
||||
.col-sm-6
|
||||
= link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do
|
||||
= pluralize milestone.issues.count, 'Issue'
|
||||
|
||||
= link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do
|
||||
= pluralize milestone.merge_requests.count, 'Merge Request'
|
||||
|
||||
%span.light #{milestone.percent_complete}% complete
|
||||
.col-sm-6
|
||||
= milestone_progress_bar(milestone)
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
if(current_url == log_url) {
|
||||
// Load 10 more commit log for each file in tree
|
||||
// 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 @@
|
||||
- split_button = split_button || false
|
||||
- if split_button == true
|
||||
%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
|
||||
%span Download zip
|
||||
%a.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' }
|
||||
%a.btn-sm.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' }
|
||||
%span.caret
|
||||
%span.sr-only
|
||||
Select Archive Format
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
= link_to license_url(@project), class: 'btn btn-block' do
|
||||
View license
|
||||
|
||||
.prepend-top-10
|
||||
.prepend-top-10.append-bottom-10
|
||||
%p
|
||||
%span.light Created on
|
||||
#{@project.created_at.stamp('Aug 22, 2013')}
|
||||
@@ -79,8 +79,16 @@
|
||||
- else
|
||||
#{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|
|
||||
- if ci_service.active? && ci_service.respond_to?(:builds_path)
|
||||
%hr
|
||||
- if ci_service.respond_to?(:status_img_path)
|
||||
= link_to ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink' do
|
||||
= image_tag ci_service.status_img_path, alt: "build status"
|
||||
@@ -97,4 +105,3 @@
|
||||
= readme.name
|
||||
.wiki
|
||||
= render_readme(readme)
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
- if @contributed_projects.present?
|
||||
- if local_assigns.has_key?(:contributed_projects) && contributed_projects.present?
|
||||
.panel.panel-default.contributed-projects
|
||||
.panel-heading Projects contributed to
|
||||
= 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
|
||||
|
||||
- if @projects.present?
|
||||
- if local_assigns.has_key?(:projects) && projects.present?
|
||||
.panel.panel-default
|
||||
.panel-heading Personal projects
|
||||
= 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
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
= spinner
|
||||
%aside.col-md-4
|
||||
= render 'profile', user: @user
|
||||
= render 'projects'
|
||||
= render 'projects', projects: @projects, contributed_projects: @contributed_projects
|
||||
|
||||
:coffeescript
|
||||
$(".user-calendar").load("#{user_calendar_path}")
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
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
|
||||
+1
-1
@@ -37,7 +37,7 @@ start_no_deamonize()
|
||||
|
||||
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()
|
||||
|
||||
@@ -66,8 +66,8 @@ production: &base
|
||||
# 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.
|
||||
# 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
|
||||
# issue_closing_pattern: '((?:[Cc]los(?:e[sd]|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?)|([A-Z]*-\d*))+)'
|
||||
# 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 +)?))+)'
|
||||
|
||||
## Default project features settings
|
||||
default_projects_features:
|
||||
@@ -86,35 +86,6 @@ production: &base
|
||||
# The default is 'tmp/repositories' relative to the root of the Rails app.
|
||||
# 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
|
||||
## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html
|
||||
gravatar:
|
||||
|
||||
@@ -25,3 +25,5 @@ Sidekiq.configure_client do |config|
|
||||
namespace: 'resque:gitlab'
|
||||
}
|
||||
end
|
||||
|
||||
Sidekiq::Queue["archive_repo"].limit = 2
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
# 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 @@
|
||||
|
||||
Mime::Type.register_alias "text/plain", :diff
|
||||
Mime::Type.register_alias "text/plain", :patch
|
||||
Mime::Type.register_alias 'text/html', :md
|
||||
|
||||
+1
-2
@@ -39,10 +39,9 @@ Gitlab::Application.routes.draw do
|
||||
|
||||
# Help
|
||||
get 'help' => 'help#index'
|
||||
get 'help/:category/:file' => 'help#show', as: :help_page
|
||||
get 'help/:category/*file' => 'help#show'
|
||||
get 'help/shortcuts'
|
||||
get 'help/ui' => 'help#ui'
|
||||
get 'help/:filepath' => 'help#show', as: :help_page, constraints: { filepath: /[^\.]+/ }
|
||||
|
||||
#
|
||||
# Global snippets
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
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
|
||||
@@ -1,5 +1,36 @@
|
||||
# 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
|
||||
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:
|
||||
|
||||
```
|
||||
|
||||
+1
-1
@@ -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 you run GitLab Enterprise Edition point it to a location where you have downloaded it.
|
||||
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 \
|
||||
&& rm -f $TMP_FILE
|
||||
|
||||
|
||||
@@ -54,6 +54,14 @@ Feature: Groups
|
||||
Then I see a new LDAP synchronization listed
|
||||
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
|
||||
|
||||
@javascript
|
||||
|
||||
@@ -13,7 +13,7 @@ Feature: Project Star
|
||||
Given public project "Community"
|
||||
And I visit project "Community" page
|
||||
When I click on the star toggle button
|
||||
Then The project has 0 stars
|
||||
Then I redirected to sign in page
|
||||
|
||||
@javascript
|
||||
Scenario: Signed in users can toggle star
|
||||
|
||||
@@ -8,7 +8,7 @@ class Spinach::Features::DashboardHelp < Spinach::FeatureSteps
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
step 'I should see "Rake Tasks" page markdown rendered' do
|
||||
|
||||
@@ -5,6 +5,32 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
|
||||
include SharedUser
|
||||
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
|
||||
Group.find_by(name: "Owned").projects.each do |project|
|
||||
page.should have_link project.name
|
||||
|
||||
@@ -74,7 +74,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
step 'I fill the commit message' do
|
||||
|
||||
@@ -22,12 +22,16 @@ class Spinach::Features::ProjectStar < Spinach::FeatureSteps
|
||||
|
||||
# Requires @javascript
|
||||
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
|
||||
|
||||
protected
|
||||
|
||||
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
|
||||
|
||||
+3
-3
@@ -1,5 +1,4 @@
|
||||
require 'mime/types'
|
||||
require 'uri'
|
||||
|
||||
module API
|
||||
# Projects API
|
||||
@@ -101,10 +100,11 @@ module API
|
||||
# branch (required) - The name of the branch
|
||||
# Example Request:
|
||||
# DELETE /projects/:id/repository/branches/:branch
|
||||
delete ":id/repository/branches/:branch" do
|
||||
delete ":id/repository/branches/:branch",
|
||||
requirements: { branch: /.*/ } do
|
||||
authorize_push_project
|
||||
result = DeleteBranchService.new(user_project, current_user).
|
||||
execute(URI.unescape(params[:branch]))
|
||||
execute(params[:branch])
|
||||
|
||||
if result[:status] == :success
|
||||
{
|
||||
|
||||
+2
-5
@@ -88,17 +88,14 @@ module API
|
||||
present user_project, with: Entities::ProjectWithAccess, user: current_user
|
||||
end
|
||||
|
||||
# Get a single project events
|
||||
# Get events for a single project
|
||||
#
|
||||
# Parameters:
|
||||
# id (required) - The ID of a project
|
||||
# Example Request:
|
||||
# GET /projects/:id/events
|
||||
get ":id/events" do
|
||||
limit = (params[:per_page] || 20).to_i
|
||||
offset = (params[:page] || 0).to_i * limit
|
||||
events = user_project.events.recent.limit(limit).offset(offset)
|
||||
|
||||
events = paginate user_project.events.recent
|
||||
present events, with: Entities::Event
|
||||
end
|
||||
|
||||
|
||||
@@ -133,10 +133,11 @@ module API
|
||||
authorize! :download_code, user_project
|
||||
|
||||
begin
|
||||
file_path = ArchiveRepositoryService.new.execute(
|
||||
user_project,
|
||||
params[:sha],
|
||||
params[:format])
|
||||
file_path = ArchiveRepositoryService.new(
|
||||
user_project,
|
||||
params[:sha],
|
||||
params[:format]
|
||||
).execute
|
||||
rescue
|
||||
not_found!('File')
|
||||
end
|
||||
@@ -149,7 +150,7 @@ module API
|
||||
env['api.format'] = :binary
|
||||
present data
|
||||
else
|
||||
not_found!('File')
|
||||
redirect request.fullpath
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+36
-6
@@ -1,7 +1,5 @@
|
||||
module Backup
|
||||
class Manager
|
||||
BACKUP_CONTENTS = %w{repositories/ db/ uploads/ backup_information.yml}
|
||||
|
||||
def pack
|
||||
# saving additional informations
|
||||
s = {}
|
||||
@@ -9,6 +7,7 @@ module Backup
|
||||
s[:backup_created_at] = Time.now
|
||||
s[:gitlab_version] = Gitlab::VERSION
|
||||
s[:tar_version] = tar_version
|
||||
s[:skipped] = ENV["SKIP"]
|
||||
tar_file = "#{s[:backup_created_at].to_i}_gitlab_backup.tar"
|
||||
|
||||
Dir.chdir(Gitlab.config.backup.path) do
|
||||
@@ -17,12 +16,12 @@ module Backup
|
||||
file << s.to_yaml.gsub(/^---\n/,'')
|
||||
end
|
||||
|
||||
FileUtils.chmod_R(0700, %w{db uploads repositories})
|
||||
FileUtils.chmod(0700, folders_to_backup)
|
||||
|
||||
# create archive
|
||||
$progress.print "Creating backup archive: #{tar_file} ... "
|
||||
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
|
||||
else
|
||||
puts "creating archive #{tar_file} failed".red
|
||||
@@ -46,6 +45,7 @@ module Backup
|
||||
|
||||
connection = ::Fog::Storage.new(connection_settings)
|
||||
directory = connection.directories.get(remote_directory)
|
||||
|
||||
if directory.files.create(key: tar_file, body: File.open(tar_file), public: false)
|
||||
$progress.puts "done".green
|
||||
else
|
||||
@@ -56,7 +56,10 @@ module Backup
|
||||
|
||||
def cleanup
|
||||
$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))
|
||||
$progress.puts "done".green
|
||||
else
|
||||
@@ -73,6 +76,7 @@ module Backup
|
||||
|
||||
if keep_time > 0
|
||||
removed = 0
|
||||
|
||||
Dir.chdir(Gitlab.config.backup.path) do
|
||||
file_list = Dir.glob('*_gitlab_backup.tar')
|
||||
file_list.map! { |f| $1.to_i if f =~ /(\d+)_gitlab_backup.tar/ }
|
||||
@@ -84,6 +88,7 @@ module Backup
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
$progress.puts "done. (#{removed} removed)".green
|
||||
else
|
||||
$progress.puts "skipping".yellow
|
||||
@@ -96,6 +101,7 @@ module Backup
|
||||
# check for existing backups in the backup dir
|
||||
file_list = Dir.glob("*_gitlab_backup.tar").each.map { |f| f.split(/_/).first.to_i }
|
||||
puts "no backups found" if file_list.count == 0
|
||||
|
||||
if file_list.count > 1 && ENV["BACKUP"].nil?
|
||||
puts "Found more than one backup, please specify which one you want to restore:"
|
||||
puts "rake gitlab:backup:restore BACKUP=timestamp_of_backup"
|
||||
@@ -110,6 +116,7 @@ module Backup
|
||||
end
|
||||
|
||||
$progress.print "Unpacking backup ... "
|
||||
|
||||
unless Kernel.system(*%W(tar -xf #{tar_file}))
|
||||
puts "unpacking backup failed".red
|
||||
exit 1
|
||||
@@ -117,7 +124,6 @@ module Backup
|
||||
$progress.puts "done".green
|
||||
end
|
||||
|
||||
settings = YAML.load_file("backup_information.yml")
|
||||
ENV["VERSION"] = "#{settings[:db_version]}" if settings[:db_version].to_i > 0
|
||||
|
||||
# restoring mismatching backups can lead to unexpected problems
|
||||
@@ -136,5 +142,29 @@ module Backup
|
||||
tar_version, _ = Gitlab::Popen.popen(%W(tar --version))
|
||||
tar_version.force_encoding('locale').split("\n").first
|
||||
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
|
||||
|
||||
@@ -28,6 +28,10 @@ module Gitlab
|
||||
client.auth_code.get_token(code, redirect_uri: redirect_uri).token
|
||||
end
|
||||
|
||||
def user
|
||||
api.get("/api/v3/user").parsed
|
||||
end
|
||||
|
||||
def issues(project_identifier)
|
||||
lazy_page_iterator(PER_PAGE) do |page|
|
||||
api.get("/api/v3/projects/#{project_identifier}/issues?per_page=#{PER_PAGE}&page=#{page}").parsed
|
||||
|
||||
+59
-43
@@ -32,12 +32,12 @@ module Gitlab
|
||||
module Markdown
|
||||
include IssuesHelper
|
||||
|
||||
attr_reader :html_options
|
||||
attr_reader :options, :html_options
|
||||
|
||||
# Public: Parse the provided text with GitLab-Flavored Markdown
|
||||
#
|
||||
# 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
|
||||
def gfm(text, project = @project, html_options = {})
|
||||
gfm_with_options(text, {}, project, html_options)
|
||||
@@ -46,9 +46,10 @@ module Gitlab
|
||||
# Public: Parse the provided text with GitLab-Flavored Markdown
|
||||
#
|
||||
# text - the source text
|
||||
# options - parse_tasks: true - render tasks
|
||||
# - xhtml: true - output XHTML instead of HTML
|
||||
# project - extra options for the reference links as given to link_to
|
||||
# options - parse_tasks - render tasks
|
||||
# - xhtml - output XHTML instead of HTML
|
||||
# - 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
|
||||
def gfm_with_options(text, options = {}, project = @project, html_options = {})
|
||||
return text if text.nil?
|
||||
@@ -58,6 +59,13 @@ module Gitlab
|
||||
# for gsub calls to work as we need them to.
|
||||
text = text.dup.to_str
|
||||
|
||||
options.reverse_merge!(
|
||||
parse_tasks: false,
|
||||
xhtml: false,
|
||||
reference_only_path: true
|
||||
)
|
||||
|
||||
@options = options
|
||||
@html_options = html_options
|
||||
|
||||
# Extract pre blocks so they are not altered
|
||||
@@ -113,12 +121,13 @@ module Gitlab
|
||||
markdown_pipeline = HTML::Pipeline::Gitlab.new(filters).pipeline
|
||||
|
||||
result = markdown_pipeline.call(text, markdown_context)
|
||||
saveoptions = 0
|
||||
|
||||
save_options = 0
|
||||
if options[:xhtml]
|
||||
saveoptions |= Nokogiri::XML::Node::SaveOptions::AS_XHTML
|
||||
save_options |= Nokogiri::XML::Node::SaveOptions::AS_XHTML
|
||||
end
|
||||
|
||||
text = result[:output].to_html(save_with: saveoptions)
|
||||
text = result[:output].to_html(save_with: save_options)
|
||||
|
||||
if options[:parse_tasks]
|
||||
text = parse_tasks(text)
|
||||
@@ -152,7 +161,7 @@ module Gitlab
|
||||
text
|
||||
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})"
|
||||
|
||||
REFERENCE_PATTERN = %r{
|
||||
@@ -229,33 +238,37 @@ module Gitlab
|
||||
end
|
||||
|
||||
def reference_user(identifier, project = @project, _ = nil)
|
||||
options = html_options.merge(
|
||||
link_options = html_options.merge(
|
||||
class: "gfm gfm-project_member #{html_options[:class]}"
|
||||
)
|
||||
|
||||
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)
|
||||
url =
|
||||
if namespace.type == "Group"
|
||||
group_url(identifier)
|
||||
else
|
||||
user_url(identifier)
|
||||
if namespace.is_a?(Group)
|
||||
group_url(identifier, only_path: options[:reference_only_path])
|
||||
else
|
||||
user_url(identifier, only_path: options[:reference_only_path])
|
||||
end
|
||||
|
||||
link_to("@#{identifier}", url, options)
|
||||
|
||||
link_to("@#{identifier}", url, link_options)
|
||||
end
|
||||
end
|
||||
|
||||
def reference_label(identifier, project = @project, _ = nil)
|
||||
if label = project.labels.find_by(id: identifier)
|
||||
options = html_options.merge(
|
||||
link_options = html_options.merge(
|
||||
class: "gfm gfm-label #{html_options[:class]}"
|
||||
)
|
||||
link_to(
|
||||
render_colored_label(label),
|
||||
namespace_project_issues_path(project.namespace, project, label_name: label.name),
|
||||
options
|
||||
link_options
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -263,14 +276,14 @@ module Gitlab
|
||||
def reference_issue(identifier, project = @project, prefix_text = nil)
|
||||
if project.default_issues_tracker?
|
||||
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)
|
||||
options = html_options.merge(
|
||||
link_options = html_options.merge(
|
||||
title: "Issue: #{title}",
|
||||
class: "gfm gfm-issue #{html_options[:class]}"
|
||||
)
|
||||
|
||||
link_to("#{prefix_text}##{identifier}", url, options)
|
||||
link_to("#{prefix_text}##{identifier}", url, link_options)
|
||||
end
|
||||
else
|
||||
if project.external_issue_tracker.present?
|
||||
@@ -280,44 +293,46 @@ module Gitlab
|
||||
end
|
||||
end
|
||||
|
||||
def reference_merge_request(identifier, project = @project,
|
||||
prefix_text = nil)
|
||||
def reference_merge_request(identifier, project = @project, prefix_text = nil)
|
||||
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}",
|
||||
class: "gfm gfm-merge_request #{html_options[:class]}"
|
||||
)
|
||||
url = namespace_project_merge_request_url(project.namespace, project,
|
||||
merge_request)
|
||||
link_to("#{prefix_text}!#{identifier}", url, options)
|
||||
merge_request,
|
||||
only_path: options[:reference_only_path])
|
||||
link_to("#{prefix_text}!#{identifier}", url, link_options)
|
||||
end
|
||||
end
|
||||
|
||||
def reference_snippet(identifier, project = @project, _ = nil)
|
||||
if snippet = project.snippets.find_by(id: identifier)
|
||||
options = html_options.merge(
|
||||
link_options = html_options.merge(
|
||||
title: "Snippet: #{snippet.title}",
|
||||
class: "gfm gfm-snippet #{html_options[:class]}"
|
||||
)
|
||||
link_to(
|
||||
"$#{identifier}",
|
||||
namespace_project_snippet_url(project.namespace, project, snippet),
|
||||
options
|
||||
namespace_project_snippet_url(project.namespace, project, snippet,
|
||||
only_path: options[:reference_only_path]),
|
||||
link_options
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def reference_commit(identifier, project = @project, prefix_text = nil)
|
||||
if project.valid_repo? && commit = project.repository.commit(identifier)
|
||||
options = html_options.merge(
|
||||
link_options = html_options.merge(
|
||||
title: commit.link_title,
|
||||
class: "gfm gfm-commit #{html_options[:class]}"
|
||||
)
|
||||
prefix_text = "#{prefix_text}@" if prefix_text
|
||||
link_to(
|
||||
"#{prefix_text}#{identifier}",
|
||||
namespace_project_commit_url(project.namespace, project, commit),
|
||||
options
|
||||
namespace_project_commit_url( project.namespace, project, commit,
|
||||
only_path: options[:reference_only_path]),
|
||||
link_options
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -328,11 +343,11 @@ module Gitlab
|
||||
inclusive = identifier !~ /\.{3}/
|
||||
from_id << "^" if inclusive
|
||||
|
||||
if project.valid_repo? &&
|
||||
from = project.repository.commit(from_id) &&
|
||||
if project.valid_repo? &&
|
||||
from = project.repository.commit(from_id) &&
|
||||
to = project.repository.commit(to_id)
|
||||
|
||||
options = html_options.merge(
|
||||
link_options = html_options.merge(
|
||||
title: "Commits #{from_id} through #{to_id}",
|
||||
class: "gfm gfm-commit_range #{html_options[:class]}"
|
||||
)
|
||||
@@ -340,22 +355,23 @@ module Gitlab
|
||||
|
||||
link_to(
|
||||
"#{prefix_text}#{identifier}",
|
||||
namespace_project_compare_url(project.namespace, project, from: from_id, to: to_id),
|
||||
options
|
||||
namespace_project_compare_url(project.namespace, project,
|
||||
from: from_id, to: to_id,
|
||||
only_path: options[:reference_only_path]),
|
||||
link_options
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def reference_external_issue(identifier, project = @project,
|
||||
prefix_text = nil)
|
||||
url = url_for_issue(identifier, project)
|
||||
def reference_external_issue(identifier, project = @project, prefix_text = nil)
|
||||
url = url_for_issue(identifier, project, only_path: options[:reference_only_path])
|
||||
title = project.external_issue_tracker.title
|
||||
|
||||
options = html_options.merge(
|
||||
link_options = html_options.merge(
|
||||
title: "Issue in #{title}",
|
||||
class: "gfm gfm-issue #{html_options[:class]}"
|
||||
)
|
||||
link_to("#{prefix_text}##{identifier}", url, options)
|
||||
link_to("#{prefix_text}##{identifier}", url, link_options)
|
||||
end
|
||||
|
||||
# Turn list items that start with "[ ]" into HTML checkbox inputs.
|
||||
|
||||
@@ -86,7 +86,7 @@ module Gitlab
|
||||
def user_attributes
|
||||
{
|
||||
name: auth_hash.name,
|
||||
username: ::User.clean_username(auth_hash.username),
|
||||
username: ::Namespace.clean_path(auth_hash.username),
|
||||
email: auth_hash.email,
|
||||
password: auth_hash.password,
|
||||
password_confirmation: auth_hash.password,
|
||||
|
||||
+36
-30
@@ -2,49 +2,66 @@ module Gitlab
|
||||
module Regex
|
||||
extend self
|
||||
|
||||
def username_regex
|
||||
default_regex
|
||||
NAMESPACE_REGEX_STR = '(?:[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*[a-zA-Z0-9_\-]|[a-zA-Z0-9_])'.freeze
|
||||
|
||||
def namespace_regex
|
||||
@namespace_regex ||= /\A#{NAMESPACE_REGEX_STR}\z/.freeze
|
||||
end
|
||||
|
||||
def username_regex_message
|
||||
default_regex_message
|
||||
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
|
||||
|
||||
def namespace_name_regex_message
|
||||
"can contain only letters, digits, '_', '-', '.' and space."
|
||||
end
|
||||
|
||||
|
||||
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
|
||||
|
||||
def project_regex_message
|
||||
"can contain only letters, digits, '_', '-' and '.' and space. " \
|
||||
def project_name_regex_message
|
||||
"can contain only letters, digits, '_', '-', '.' and space. " \
|
||||
"It must start with letter, digit or '_'."
|
||||
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
|
||||
|
||||
def name_regex_message
|
||||
"can contain only letters, digits, '_', '-' and '.' and space."
|
||||
def project_path_regex_message
|
||||
"can contain only letters, digits, '_', '-' and '.'. " \
|
||||
"Cannot start with '-' or end in '.git'" \
|
||||
end
|
||||
|
||||
def path_regex
|
||||
default_regex
|
||||
|
||||
def file_name_regex
|
||||
@file_name_regex ||= /\A[a-zA-Z0-9_\-\.]*\z/.freeze
|
||||
end
|
||||
|
||||
def path_regex_message
|
||||
default_regex_message
|
||||
def file_name_regex_message
|
||||
"can contain only letters, digits, '_', '-' and '.'. "
|
||||
end
|
||||
|
||||
|
||||
def archive_formats_regex
|
||||
#|zip|tar| tar.gz | tar.bz2 |
|
||||
/(zip|tar|tar\.gz|tgz|gz|tar\.bz2|tbz|tbz2|tb2|bz2)/
|
||||
# |zip|tar| tar.gz | tar.bz2 |
|
||||
@archive_formats_regex ||= /(zip|tar|tar\.gz|tgz|gz|tar\.bz2|tbz|tbz2|tb2|bz2)/.freeze
|
||||
end
|
||||
|
||||
def git_reference_regex
|
||||
# Valid git ref regex, see:
|
||||
# https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
|
||||
|
||||
%r{
|
||||
@git_reference_regex ||= %r{
|
||||
(?!
|
||||
(?# doesn't begins with)
|
||||
\/| (?# rule #6)
|
||||
@@ -60,18 +77,7 @@ module Gitlab
|
||||
(?# doesn't end with)
|
||||
(?<!\.lock) (?# rule #1)
|
||||
(?<![\/.]) (?# rule #6-7)
|
||||
}x
|
||||
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/
|
||||
}x.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -27,9 +27,9 @@ namespace :gitlab do
|
||||
backup = Backup::Manager.new
|
||||
backup.unpack
|
||||
|
||||
Rake::Task["gitlab:backup:db:restore"].invoke
|
||||
Rake::Task["gitlab:backup:repo:restore"].invoke
|
||||
Rake::Task["gitlab:backup:uploads:restore"].invoke
|
||||
Rake::Task["gitlab:backup:db:restore"].invoke unless backup.skipped?("db")
|
||||
Rake::Task["gitlab:backup:repo:restore"].invoke unless backup.skipped?("repositories")
|
||||
Rake::Task["gitlab:backup:uploads:restore"].invoke unless backup.skipped?("uploads")
|
||||
Rake::Task["gitlab:shell:setup"].invoke
|
||||
|
||||
backup.cleanup
|
||||
@@ -38,8 +38,13 @@ namespace :gitlab do
|
||||
namespace :repo do
|
||||
task create: :environment do
|
||||
$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
|
||||
|
||||
task restore: :environment do
|
||||
@@ -52,8 +57,13 @@ namespace :gitlab do
|
||||
namespace :db do
|
||||
task create: :environment do
|
||||
$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
|
||||
|
||||
task restore: :environment do
|
||||
@@ -66,8 +76,13 @@ namespace :gitlab do
|
||||
namespace :uploads do
|
||||
task create: :environment do
|
||||
$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
|
||||
|
||||
task restore: :environment do
|
||||
|
||||
@@ -2,6 +2,7 @@ namespace :gitlab do
|
||||
desc "GITLAB | Run all tests"
|
||||
task :test do
|
||||
cmds = [
|
||||
%W(rake brakeman),
|
||||
%W(rake rubocop),
|
||||
%W(rake spinach),
|
||||
%W(rake spec),
|
||||
|
||||
@@ -55,24 +55,109 @@ describe Import::BitbucketController do
|
||||
end
|
||||
|
||||
describe "POST create" do
|
||||
before do
|
||||
@repo = {
|
||||
slug: 'vim',
|
||||
owner: "john"
|
||||
let(:bitbucket_username) { user.username }
|
||||
|
||||
let(:bitbucket_user) {
|
||||
{
|
||||
user: {
|
||||
username: bitbucket_username
|
||||
}
|
||||
}.with_indifferent_access
|
||||
}
|
||||
|
||||
let(:bitbucket_repo) {
|
||||
{
|
||||
slug: "vim",
|
||||
owner: bitbucket_username
|
||||
}.with_indifferent_access
|
||||
}
|
||||
|
||||
before do
|
||||
allow(Gitlab::BitbucketImport::KeyAdder).
|
||||
to receive(:new).with(bitbucket_repo, user).
|
||||
and_return(double(execute: true))
|
||||
|
||||
controller.stub_chain(:client, :user).and_return(bitbucket_user)
|
||||
controller.stub_chain(:client, :project).and_return(bitbucket_repo)
|
||||
end
|
||||
|
||||
it "takes already existing namespace" do
|
||||
namespace = create(:namespace, name: "john", owner: user)
|
||||
expect(Gitlab::BitbucketImport::KeyAdder).
|
||||
to receive(:new).with(@repo, user).
|
||||
and_return(double(execute: true))
|
||||
expect(Gitlab::BitbucketImport::ProjectCreator).
|
||||
to receive(:new).with(@repo, namespace, user).
|
||||
and_return(double(execute: true))
|
||||
controller.stub_chain(:client, :project).and_return(@repo)
|
||||
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
|
||||
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
|
||||
|
||||
@@ -57,18 +57,98 @@ describe Import::GithubController do
|
||||
end
|
||||
|
||||
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
|
||||
@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
|
||||
|
||||
it "takes already existing namespace" do
|
||||
namespace = create(:namespace, name: "john", owner: user)
|
||||
expect(Gitlab::GithubImport::ProjectCreator).
|
||||
to receive(:new).with(@repo, namespace, user).
|
||||
and_return(double(execute: true))
|
||||
controller.stub_chain(:client, :repo).and_return(@repo)
|
||||
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
|
||||
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
|
||||
|
||||
context "when the repository owner is not the GitHub user" do
|
||||
let(:other_username) { "someone_else" }
|
||||
|
||||
before do
|
||||
github_repo.owner = OpenStruct.new(login: other_username)
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -48,23 +48,105 @@ describe Import::GitlabController do
|
||||
end
|
||||
|
||||
describe "POST create" do
|
||||
before do
|
||||
@repo = {
|
||||
path: 'vim',
|
||||
path_with_namespace: 'asd/vim',
|
||||
owner: {name: "john"},
|
||||
namespace: {path: "john"}
|
||||
let(:gitlab_username) { user.username }
|
||||
|
||||
let(:gitlab_user) {
|
||||
{
|
||||
username: gitlab_username
|
||||
}.with_indifferent_access
|
||||
}
|
||||
|
||||
let(:gitlab_repo) {
|
||||
{
|
||||
path: 'vim',
|
||||
path_with_namespace: "#{gitlab_username}/vim",
|
||||
owner: { name: gitlab_username },
|
||||
namespace: { path: gitlab_username }
|
||||
}.with_indifferent_access
|
||||
}
|
||||
|
||||
before do
|
||||
controller.stub_chain(:client, :user).and_return(gitlab_user)
|
||||
controller.stub_chain(:client, :project).and_return(gitlab_repo)
|
||||
end
|
||||
|
||||
it "takes already existing namespace" do
|
||||
namespace = create(:namespace, name: "john", owner: user)
|
||||
expect(Gitlab::GitlabImport::ProjectCreator).
|
||||
to receive(:new).with(@repo, namespace, user).
|
||||
and_return(double(execute: true))
|
||||
controller.stub_chain(:client, :project).and_return(@repo)
|
||||
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
|
||||
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
|
||||
|
||||
context "when the repository owner is not the GitLab.com user" do
|
||||
let(:other_username) { "someone_else" }
|
||||
|
||||
before do
|
||||
gitlab_repo["namespace"]["path"] = other_username
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
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
|
||||
@@ -0,0 +1,65 @@
|
||||
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
|
||||
login_as :user
|
||||
end
|
||||
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}\"")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -700,7 +700,7 @@ describe GitlabMarkdownHelper do
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
it "should leave ref-like src of images untouched" do
|
||||
|
||||
@@ -7,7 +7,7 @@ describe IconsHelper do
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
it 'returns file-pdf-o class with .pdf' do
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Regex do
|
||||
describe 'path regex' do
|
||||
it { expect('gitlab-ce').to match(Gitlab::Regex.path_regex) }
|
||||
it { expect('gitlab_git').to match(Gitlab::Regex.path_regex) }
|
||||
it { expect('_underscore.js').to match(Gitlab::Regex.path_regex) }
|
||||
it { expect('100px.com').to match(Gitlab::Regex.path_regex) }
|
||||
it { expect('?gitlab').not_to match(Gitlab::Regex.path_regex) }
|
||||
it { expect('git lab').not_to match(Gitlab::Regex.path_regex) }
|
||||
it { expect('gitlab.git').not_to match(Gitlab::Regex.path_regex) }
|
||||
describe 'project path regex' do
|
||||
it { expect('gitlab-ce').to match(Gitlab::Regex.project_path_regex) }
|
||||
it { expect('gitlab_git').to match(Gitlab::Regex.project_path_regex) }
|
||||
it { expect('_underscore.js').to match(Gitlab::Regex.project_path_regex) }
|
||||
it { expect('100px.com').to match(Gitlab::Regex.project_path_regex) }
|
||||
it { expect('?gitlab').not_to match(Gitlab::Regex.project_path_regex) }
|
||||
it { expect('git lab').not_to match(Gitlab::Regex.project_path_regex) }
|
||||
it { expect('gitlab.git').not_to match(Gitlab::Regex.project_path_regex) }
|
||||
end
|
||||
|
||||
describe 'project name regex' do
|
||||
|
||||
@@ -33,8 +33,6 @@ describe Namespace do
|
||||
it { is_expected.to respond_to(:to_param) }
|
||||
end
|
||||
|
||||
it { expect(Namespace.global_id).to eq('GLN') }
|
||||
|
||||
describe :to_param do
|
||||
it { expect(namespace.to_param).to eq(namespace.path) }
|
||||
end
|
||||
@@ -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('unknown')).to eq(nil) }
|
||||
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
|
||||
|
||||
@@ -31,6 +31,7 @@ describe GitlabIssueTrackerService do
|
||||
|
||||
context 'with absolute urls' do
|
||||
before do
|
||||
GitlabIssueTrackerService.default_url_options[:script_name] = "/gitlab/root"
|
||||
@service = project.create_gitlab_issue_tracker_service(active: true)
|
||||
end
|
||||
|
||||
@@ -39,15 +40,15 @@ describe GitlabIssueTrackerService do
|
||||
end
|
||||
|
||||
it 'should give the correct path' do
|
||||
expect(@service.project_url).to eq("/#{project.path_with_namespace}/issues")
|
||||
expect(@service.new_issue_url).to eq("/#{project.path_with_namespace}/issues/new")
|
||||
expect(@service.issue_url(432)).to eq("/#{project.path_with_namespace}/issues/432")
|
||||
expect(@service.project_url).to eq("http://localhost/gitlab/root/#{project.path_with_namespace}/issues")
|
||||
expect(@service.new_issue_url).to eq("http://localhost/gitlab/root/#{project.path_with_namespace}/issues/new")
|
||||
expect(@service.issue_url(432)).to eq("http://localhost/gitlab/root/#{project.path_with_namespace}/issues/432")
|
||||
end
|
||||
end
|
||||
|
||||
context 'with enabled relative urls' do
|
||||
context 'with relative urls' 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)
|
||||
end
|
||||
|
||||
@@ -56,9 +57,9 @@ describe GitlabIssueTrackerService do
|
||||
end
|
||||
|
||||
it 'should give the correct path' do
|
||||
expect(@service.project_url).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.issue_url(432)).to eq("/gitlab/root/#{project.path_with_namespace}/issues/432")
|
||||
expect(@service.project_path).to eq("/gitlab/root/#{project.path_with_namespace}/issues")
|
||||
expect(@service.new_issue_path).to eq("/gitlab/root/#{project.path_with_namespace}/issues/new")
|
||||
expect(@service.issue_path(432)).to eq("/gitlab/root/#{project.path_with_namespace}/issues/432")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -320,16 +320,6 @@ describe User do
|
||||
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
|
||||
it { is_expected.to have_many(:keys).dependent(:destroy) }
|
||||
|
||||
|
||||
@@ -247,12 +247,12 @@ describe API::API, api: true do
|
||||
expect(json_response['message']['name']).to eq([
|
||||
'can\'t be blank',
|
||||
'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([
|
||||
'can\'t be blank',
|
||||
'is too short (minimum is 0 characters)',
|
||||
Gitlab::Regex.send(:default_regex_message)
|
||||
Gitlab::Regex.send(:project_path_regex_message)
|
||||
])
|
||||
end
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ describe API::API, api: true do
|
||||
expect(json_response['message']['projects_limit']).
|
||||
to eq(['must be greater than or equal to 0'])
|
||||
expect(json_response['message']['username']).
|
||||
to eq([Gitlab::Regex.send(:default_regex_message)])
|
||||
to eq([Gitlab::Regex.send(:namespace_regex_message)])
|
||||
end
|
||||
|
||||
it "shouldn't available for non admin users" do
|
||||
@@ -278,7 +278,7 @@ describe API::API, api: true do
|
||||
expect(json_response['message']['projects_limit']).
|
||||
to eq(['must be greater than or equal to 0'])
|
||||
expect(json_response['message']['username']).
|
||||
to eq([Gitlab::Regex.send(:default_regex_message)])
|
||||
to eq([Gitlab::Regex.send(:namespace_regex_message)])
|
||||
end
|
||||
|
||||
context "with existing user" do
|
||||
|
||||
@@ -73,41 +73,41 @@ end
|
||||
# help_markdown GET /help/markdown(.:format) help#markdown
|
||||
# help_ssh GET /help/ssh(.:format) help#ssh
|
||||
# help_raketasks GET /help/raketasks(.:format) help#raketasks
|
||||
describe HelpController, "routing" do
|
||||
it "to #index" do
|
||||
expect(get("/help")).to route_to('help#index')
|
||||
describe HelpController, 'routing' do
|
||||
it 'to #index' do
|
||||
expect(get('/help')).to route_to('help#index')
|
||||
end
|
||||
|
||||
it "to #permissions" do
|
||||
expect(get("/help/permissions/permissions")).to route_to('help#show', category: "permissions", file: "permissions")
|
||||
it 'to #permissions' do
|
||||
expect(get('/help/permissions/permissions')).to route_to('help#show', filepath: 'permissions/permissions')
|
||||
end
|
||||
|
||||
it "to #workflow" do
|
||||
expect(get("/help/workflow/README")).to route_to('help#show', category: "workflow", file: "README")
|
||||
it 'to #workflow' do
|
||||
expect(get('/help/workflow/README')).to route_to('help#show', filepath: 'workflow/README')
|
||||
end
|
||||
|
||||
it "to #api" do
|
||||
expect(get("/help/api/README")).to route_to('help#show', category: "api", file: "README")
|
||||
it 'to #api' do
|
||||
expect(get('/help/api/README')).to route_to('help#show', filepath: 'api/README')
|
||||
end
|
||||
|
||||
it "to #web_hooks" do
|
||||
expect(get("/help/web_hooks/web_hooks")).to route_to('help#show', category: "web_hooks", file: "web_hooks")
|
||||
it 'to #web_hooks' do
|
||||
expect(get('/help/web_hooks/web_hooks')).to route_to('help#show', filepath: 'web_hooks/web_hooks')
|
||||
end
|
||||
|
||||
it "to #system_hooks" do
|
||||
expect(get("/help/system_hooks/system_hooks")).to route_to('help#show', category: "system_hooks", file: "system_hooks")
|
||||
it 'to #system_hooks' do
|
||||
expect(get('/help/system_hooks/system_hooks')).to route_to('help#show', filepath: 'system_hooks/system_hooks')
|
||||
end
|
||||
|
||||
it "to #markdown" do
|
||||
expect(get("/help/markdown/markdown")).to route_to('help#show',category: "markdown", file: "markdown")
|
||||
it 'to #markdown' do
|
||||
expect(get('/help/markdown/markdown')).to route_to('help#show',filepath: 'markdown/markdown')
|
||||
end
|
||||
|
||||
it "to #ssh" do
|
||||
expect(get("/help/ssh/README")).to route_to('help#show', category: "ssh", file: "README")
|
||||
it 'to #ssh' do
|
||||
expect(get('/help/ssh/README')).to route_to('help#show', filepath: 'ssh/README')
|
||||
end
|
||||
|
||||
it "to #raketasks" do
|
||||
expect(get("/help/raketasks/README")).to route_to('help#show', category: "raketasks", file: "README")
|
||||
it 'to #raketasks' do
|
||||
expect(get('/help/raketasks/README')).to route_to('help#show', filepath: 'raketasks/README')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
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|
|
||||
TestEnv.init
|
||||
end
|
||||
end
|
||||
|
||||
ActiveRecord::Migration.maintain_test_schema!
|
||||
|
||||
@@ -85,7 +85,7 @@ module TestEnv
|
||||
end
|
||||
|
||||
# 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
|
||||
|
||||
def copy_repo(project)
|
||||
@@ -113,4 +113,10 @@ module TestEnv
|
||||
def factory_repo_name
|
||||
'gitlab-test'
|
||||
end
|
||||
|
||||
# Prevent developer git configurations from being persisted to test
|
||||
# repositories
|
||||
def git_env
|
||||
{'GIT_TEMPLATE_DIR' => ''}
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user