Commit 230900b9 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'upstream-ce' into 'master'

Upstream changes from CE

See merge request !262
parents d092d271 2f006f89
v 7.6.0
- Fork repository to groups
- New rugged version
- Add CRON=1 backup setting for quiet backups
- Fix failing wiki restore
-
- Add optional Sidekiq MemoryKiller middleware (enabled via SIDEKIQ_MAX_RSS env variable)
-
-
- Create project with repository in synchrony
- Added ability to create empty repo or import existing one if project does not have repository
-
-
- Reactivate highlight.js language autodetection
- Mobile UI improvements
-
- Change maximum avatar file size from 100KB to 200KB
-
-
- In the docker directory is a container template based on the Omnibus packages.
- Update Sidekiq to version 2.17.8
-
- Atom feed for user activity
v 7.5.2
- Don't log Sidekiq arguments by default
v 7.5.0
- API: Add support for Hipchat (Kevin Houdebert)
- Add time zone configuration on gitlab.yml (Sullivan Senechal)
- Add time zone configuration in gitlab.yml (Sullivan Senechal)
- Fix LDAP authentication for Git HTTP access
- Run 'GC.start' after every EmailsOnPushWorker job
- Fix LDAP config lookup for provider 'ldap'
......@@ -22,9 +49,9 @@ v 7.5.0
- Added a password strength indicator
- Change project name and path in one form
- Display renamed files in diff views (Vinnie Okada)
- Add timezone configuration to gitlab.yml
- Fix raw view for public snippets
- Use secret token with GitLab internal API.
- Add missing timestamps to 'members' table
v 7.4.3
- Fix raw snippets view
......@@ -51,6 +78,7 @@ v 7.4.0
- Do not delete tmp/repositories itself during clean-up, only its contents
- Support for backup uploads to remote storage
- Prevent notes polling when there are not notes
- Internal ForkService: Prepare support for fork to a given namespace
- API: Add support for forking a project via the API (Bernhard Kaindl)
- API: filter project issues by milestone (Julien Bianchi)
- Fail harder in the backup script
......
......@@ -80,15 +80,33 @@ The **official merge window** is in the beginning of the month from the 1st to t
Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? Can you do part of the refactor? The increased reviewability of small MR's that leads to higher code quality is more important to us than having a minimal commit log. The smaller a MR is the more likely it is it will be merged (quickly), after that you can send more MR's to enhance it.
For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). If you would like quick feedback on your merge request feel free to mention one of the Merge Marshalls of [the core-team](https://about.gitlab.com/core-team/). Please ensure that your merge request meets the following contribution acceptance criteria.
For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). If you would like quick feedback on your merge request feel free to mention one of the Merge Marshalls of [the core-team](https://about.gitlab.com/core-team/). Please ensure that your merge request meets the contribution acceptance criteria.
**Please format your merge request description as follows:**
## Definition of done
If you contribute to GitLab please know that changes involve more than just code.
We have the following [definition of done](http://guide.agilealliance.org/guide/definition-of-done.html).
Please ensure you support the feature you contribute through all of these steps.
1. Description explaning the relevancy (see following item)
1. Working and clean code that is commented where needed
1. Unit and integration tests that pass on the CI server
1. Documented in the /doc directory
1. Changelog entry added
1. Reviewed and any concerns are addressed
1. Merged by the project lead
1. Added to the release blog article
1. Added to [the website](https://gitlab.com/gitlab-com/www-gitlab-com/) if relevant
1. Community questions answered
1. Answers to questions radiated (in docs/wiki/etc.)
## Merge request description format
1. What does this MR do?
1. Are there points in the code the reviewer needs to double check?
1. Why was this MR needed?
1. What are the relevant issue numbers / [Feature requests](http://feedback.gitlab.com/)?
1. Screenshots (If appropriate)
1. Screenshots (if relevant)
## Contribution acceptance criteria
......
......@@ -31,7 +31,7 @@ gem 'omniauth-shibboleth'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
gem "gitlab_git", '7.0.0.rc11'
gem "gitlab_git", '7.0.0.rc12'
# Ruby/Rack Git Smart-HTTP Server Handler
gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack'
......@@ -113,7 +113,7 @@ gem "acts-as-taggable-on"
# Background jobs
gem 'slim'
gem 'sinatra', require: nil
gem 'sidekiq', '2.17.0'
gem 'sidekiq', '2.17.8'
gem 'sidetiq', '0.6.1'
# HTTP requests
......@@ -136,7 +136,7 @@ gem "redis-rails"
gem 'tinder', '~> 1.9.2'
# HipChat integration
gem "hipchat", "~> 0.14.0"
gem "hipchat", "~> 1.4.0"
# Flowdock integration
gem "gitlab-flowdock-git-hook", "~> 0.4.2"
......
......@@ -78,7 +78,7 @@ GEM
coffee-script-source (1.6.3)
colored (1.2)
colorize (0.5.8)
connection_pool (1.2.0)
connection_pool (2.1.0)
coveralls (0.7.0)
multi_json (~> 1.3)
rest-client
......@@ -179,11 +179,11 @@ GEM
mime-types (~> 1.19)
gitlab_emoji (0.0.1.1)
emoji (~> 1.0.1)
gitlab_git (7.0.0.rc11)
gitlab_git (7.0.0.rc12)
activesupport (~> 4.0)
charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0)
rugged (~> 0.21.0)
rugged (~> 0.21.2)
gitlab_meta (7.0)
gitlab_omniauth-ldap (1.2.0)
net-ldap (~> 0.9)
......@@ -235,8 +235,7 @@ GEM
railties (>= 4.0.1)
hashie (2.1.2)
hike (1.2.3)
hipchat (0.14.0)
httparty
hipchat (1.4.0)
httparty
html-pipeline (1.11.0)
activesupport (>= 2)
......@@ -404,7 +403,7 @@ GEM
rdoc (3.12.2)
json (~> 1.4)
redcarpet (3.1.2)
redis (3.0.6)
redis (3.1.0)
redis-actionpack (4.0.0)
actionpack (~> 4)
redis-rack (~> 1.5.0)
......@@ -412,8 +411,8 @@ GEM
redis-activesupport (4.0.0)
activesupport (~> 4)
redis-store (~> 1.1.0)
redis-namespace (1.4.1)
redis (~> 3.0.4)
redis-namespace (1.5.1)
redis (~> 3.0, >= 3.0.4)
redis-rack (1.5.0)
rack (~> 1.5)
redis-store (~> 1.1.0)
......@@ -448,7 +447,7 @@ GEM
ruby-progressbar (1.2.0)
rubyntlm (0.4.0)
rubypants (0.2.0)
rugged (0.21.0)
rugged (0.21.2)
safe_yaml (0.9.7)
sanitize (2.1.0)
nokogiri (>= 1.4.4)
......@@ -472,12 +471,12 @@ GEM
sexp_processor (4.4.0)
shoulda-matchers (2.1.0)
activesupport (>= 3.0.0)
sidekiq (2.17.0)
celluloid (>= 0.15.2)
connection_pool (>= 1.0.0)
sidekiq (2.17.8)
celluloid (= 0.15.2)
connection_pool (~> 2.0)
json
redis (>= 3.0.4)
redis-namespace (>= 1.3.1)
redis (~> 3.1)
redis-namespace (~> 1.3)
sidetiq (0.6.1)
celluloid (>= 0.14.1)
ice_cube (~> 0.12.0)
......@@ -630,7 +629,7 @@ DEPENDENCIES
gitlab-grack (~> 2.0.0.pre)
gitlab-linguist (~> 3.0.0)
gitlab_emoji (~> 0.0.1.1)
gitlab_git (= 7.0.0.rc11)
gitlab_git (= 7.0.0.rc12)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (= 1.2.0)
gollum-lib (~> 3.0.0)
......@@ -641,7 +640,7 @@ DEPENDENCIES
guard-rspec
guard-spinach
haml-rails
hipchat (~> 0.14.0)
hipchat (~> 1.4.0)
html-pipeline-gitlab (~> 0.1.0)
httparty
jasmine (= 2.0.2)
......@@ -691,7 +690,7 @@ DEPENDENCIES
semantic-ui-sass (~> 0.16.1.0)
settingslogic
shoulda-matchers (~> 2.1.0)
sidekiq (= 2.17.0)
sidekiq (= 2.17.8)
sidetiq (= 0.6.1)
simplecov
sinatra
......
7.5.0.pre-ee
7.6.0.pre-ee
......@@ -75,6 +75,8 @@ class Dispatcher
# Ensure we don't create a particular shortcut handler here. This is
# already created, where the network graph is created.
shortcut_handler = true
when 'projects:forks:new'
new ProjectFork()
when 'users:show'
new User()
......
class @ProjectFork
constructor: ->
$('.fork-thumbnail a').on 'click', ->
$('.fork-namespaces').hide()
$('.save-project-loader').show()
......@@ -339,10 +339,6 @@ table {
}
}
@media (max-width: $screen-xs-max) {
.container .content { margin-top: 20px; }
}
.wiki .highlight, .note-body .highlight {
margin-bottom: 9px;
}
......
......@@ -42,7 +42,6 @@
}
.file-content {
background: #fff;
font-size: 11px;
&.image_file {
background: #eee;
......@@ -54,8 +53,6 @@
}
&.wiki {
font-size: 14px;
line-height: 1.6;
padding: 25px;
.highlight {
......
......@@ -59,6 +59,10 @@
pre {
white-space: pre;
word-wrap: normal;
code {
font-family: $monospace_font;
}
}
}
}
......@@ -113,6 +113,11 @@
padding: 10px 15px;
}
.cross-project-ref {
float: left;
padding: 10px 15px;
}
.creator {
float: right;
padding: 10px 15px;
......
/** Common mobile (screen XS) styles **/
@media (max-width: $screen-xs-max) {
.container .content {
margin-top: 20px;
}
.nav.nav-tabs > li > a {
padding: 10px;
font-size: 12px;
margin-right: 3px;
.badge {
display: none;
}
}
}
......@@ -75,3 +75,20 @@
}
}
}
@media (max-width: $screen-xs-max) {
.timeline {
&:before {
background: none;
}
.timeline-entry .timeline-entry-inner {
.timeline-icon {
display: none;
}
.timeline-content {
margin-left: 0;
}
}
}
}
......@@ -58,8 +58,8 @@
}
@mixin md-typography {
font-size: 14px;
line-height: 1.6;
font-size: 15px;
line-height: 1.5;
img {
max-width: 100%;
......@@ -93,7 +93,7 @@
blockquote p {
color: #888;
font-size: 14px;
font-size: 15px;
line-height: 1.5;
}
......
......@@ -186,7 +186,24 @@
}
@media (max-width: $screen-xs-max) {
.event-item .event-title {
@include str-truncated(65%);
.event-item {
.event-title {
white-space: normal;
overflow: visible;
max-width: 100%;
}
.avatar {
display: none;
}
.event-body {
margin: 0;
border-left: 2px solid #DDD;
padding-left: 10px;
}
.event-item-timestamp {
display: none;
}
}
}
......@@ -59,6 +59,7 @@ header {
}
.navbar-collapse {
margin-top: 47px;
padding-right: 0;
padding-left: 0;
}
......
......@@ -151,4 +151,14 @@ form.edit-issue {
}
}
}
.issue {
&:hover .issue-actions {
display: none !important;
}
.issue-updated-at {
display: none;
}
}
}
......@@ -63,7 +63,6 @@
@media (max-width: $screen-xs-max) {
font-size: 18px;
margin: 0;
max-height: none;
&, .container {
......@@ -86,6 +85,7 @@
color: #fff;
font-weight: normal;
text-shadow: none;
border: none;
&:after { display: none; }
}
......
......@@ -36,13 +36,16 @@ ul.notes {
font-size: 13px;
}
.author {
color: #555;
color: #333;
font-weight: bold;
font-size: 14px;
&:hover {
color: $link_hover_color;
color: $link_color;
}
}
.author-username {
font-size: 14px;
}
}
.discussion {
......
......@@ -270,3 +270,41 @@ ul.nav.nav-projects-tabs {
color: #999;
}
}
.fork-namespaces {
.thumbnail {
&.fork-exists-thumbnail {
border-color: #EEE;
.caption {
color: #999;
}
}
&.fork-thumbnail {
border-color: #AAA;
&:hover {
background-color: $hover;
}
}
a {
text-decoration: none;
}
}
}
@media (max-width: $screen-xs-max) {
.project-home-panel {
.star-fork-buttons {
padding-top: 10px;
padding-right: 15px;
}
}
.project-home-links {
display: none;
}
}
......@@ -42,10 +42,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def handle_omniauth
if current_user
# Change a logged-in user's authentication method:
current_user.extern_uid = oauth['uid']
current_user.provider = oauth['provider']
current_user.save
# Add new authentication method
current_user.identities.find_or_create_by(extern_uid: oauth['uid'], provider: oauth['provider'])
redirect_to profile_path
else
@user = Gitlab::OAuth::User.new(oauth)
......@@ -67,8 +65,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
end
end
rescue StandardError
flash[:notice] = "There's no such user!"
rescue ForbiddenAction => e
flash[:notice] = e.message
redirect_to new_user_session_path
end
......
class Projects::ForksController < Projects::ApplicationController
# Authorize
before_filter :authorize_download_code!
before_filter :require_non_empty_project
def new
@namespaces = current_user.manageable_namespaces
@namespaces.delete(@project.namespace)
end
def create
namespace = Namespace.find(params[:namespace_id])
@forked_project = ::Projects::ForkService.new(project, current_user, namespace: namespace).execute
if @forked_project.saved? && @forked_project.forked?
redirect_to(@forked_project, notice: 'Project was successfully forked.')
else
@title = 'Fork project'
render :error
end
end
end
......@@ -26,6 +26,7 @@ class Projects::HooksController < Projects::ApplicationController
def test
if !@project.empty_repo?
status = TestHookService.new.execute(hook, current_user)
if status
flash[:notice] = 'Hook successfully executed.'
else
......
class Projects::ImportsController < Projects::ApplicationController
# Authorize
before_filter :authorize_admin_project!
before_filter :require_no_repo
before_filter :redirect_if_progress, except: :show
def new
end
def create
@project.import_url = params[:project][:import_url]
if @project.save
@project.reload
if @project.import_failed?
@project.import_retry
else
@project.import_start
end
end
redirect_to project_import_path(@project)
end
def show
unless @project.import_in_progress?
if @project.import_finished?
redirect_to(@project) and return
else
redirect_to new_project_import_path(@project) and return
end
end
end
private
def require_no_repo
if @project.repository_exists?
redirect_to(@project) and return
end
end
def redirect_if_progress
if @project.import_in_progress?
redirect_to project_import_path(@project) and return
end
end
end
......@@ -103,7 +103,9 @@ class Projects::MilestonesController < Projects::ApplicationController
end
def module_enabled
return render_404 unless @project.issues_enabled
unless @project.issues_enabled || @project.merge_requests_enabled
return render_404
end
end
def milestone_params
......
class Projects::RepositoriesController < Projects::ApplicationController
# Authorize
before_filter :authorize_download_code!
before_filter :require_non_empty_project
before_filter :require_non_empty_project, except: :create
before_filter :authorize_admin_project!, only: :create
def create
@project.create_repository
redirect_to @project
end
def archive
unless can?(current_user, :download_code, @project)
......
......@@ -42,7 +42,7 @@ class Projects::ServicesController < Projects::ApplicationController
:title, :token, :type, :active, :api_key, :subdomain,
:room, :recipients, :project_url, :webhook,
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
:build_key
:build_key, :server
)
end
end
......@@ -4,7 +4,7 @@ class ProjectsController < ApplicationController
before_filter :repository, except: [:new, :create]
# Authorize
before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive, :retry_import]
before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive]
layout 'navless', only: [:new, :create, :fork]
before_filter :set_title, only: [:new, :create]
......@@ -19,10 +19,11 @@ class ProjectsController < ApplicationController
def create
@project = ::Projects::CreateService.new(current_user, project_params).execute
flash[:notice] = 'Project was successfully created.' if @project.saved?
respond_to do |format|
format.js
if @project.saved?
redirect_to project_path(@project), notice: 'Project was successfully created.'
else
render 'new'
end
end
......@@ -47,7 +48,7 @@ class ProjectsController < ApplicationController
def show
if @project.import_in_progress?
redirect_to import_project_path(@project)
redirect_to project_import_path(@project)
return
end
......@@ -60,37 +61,20 @@ class ProjectsController < ApplicationController
respond_to do |format|
format.html do
if @project.empty_repo?
render "projects/empty", layout: user_layout
if @project.repository_exists?
if @project.empty_repo?
render "projects/empty", layout: user_layout
else
@last_push = current_user.recent_push(@project.id) if current_user
render :show, layout: user_layout
end
else
@last_push = current_user.recent_push(@project.id) if current_user
render :show, layout: user_layout
render "projects/no_repo", layout: user_layout
end
end
format.json { pager_json("events/_events", @events.count) }
end
end
def import
if @project.import_finished?
redirect_to @project
return
end
end
def retry_import
unless @project.import_failed?
redirect_to import_project_path(@project)
end
@project.import_url = project_params[:import_url]
if @project.save
@project.reload
@project.import_retry
format.json { pager_json("events/_events", @events.count) }
end
redirect_to import_project_path(@project)
end
def destroy
......@@ -111,22 +95,6 @@ class ProjectsController < ApplicationController
end
end
def fork
@forked_project = ::Projects::ForkService.new(project, current_user).execute
respond_to do |format|
format.html do
if @forked_project.saved? && @forked_project.forked?
redirect_to(@forked_project, notice: 'Project was successfully forked.')
else
@title = 'Fork project'
render "fork"
end
end
format.js
end
end
def autocomplete_sources
note_type = params['type']
note_id = params['type_id']
......
......@@ -20,9 +20,14 @@ class UsersController < ApplicationController
# Get user activity feed for projects common for both users
@events = @user.recent_events.
where(project_id: authorized_projects_ids).limit(20)
where(project_id: authorized_projects_ids).limit(30)
@title = @user.name
respond_to do |format|
format.html
format.atom { render layout: false }
end
end
def determine_layout
......
module BlobHelper
def highlightjs_class(blob_name)
if blob_name.include?('.')
ext = blob_name.split('.').last
return 'language-' + ext
if no_highlight_files.include?(blob_name.downcase)
'no-highlight'
else
if no_highlight_files.include?(blob_name.downcase)
'no-highlight'
else
blob_name.downcase
end
blob_name.downcase
end
end
......
......@@ -145,4 +145,26 @@ module EventsHelper
rescue
"--broken encoding"
end
def event_to_atom(xml, event)
if event.proper?
xml.entry do
event_link = event_feed_url(event)
event_title = event_feed_title(event)
event_summary = event_feed_summary(event)
xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
xml.link href: event_link
xml.title truncate(event_title, length: 80)
xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(event.author_email)
xml.author do |author|
xml.name event.author_name
xml.email event.author_email
end
xml.summary(type: "xhtml") { |x| x << event_summary unless event_summary.nil? }
end
end
end
end
module GitHelper
def strip_gpg_signature(text)
text.gsub(/-----BEGIN PGP SIGNATURE-----(.*)-----END PGP SIGNATURE-----/m, "")
end
end
......@@ -254,4 +254,16 @@ module GitlabMarkdownHelper
truncated
end
end
def cross_project_reference(project, entity)
path = project.path_with_namespace
if entity.kind_of?(Issue)
[path, entity.iid].join('#')
elsif entity.kind_of?(MergeRequest)
[path, entity.iid].join('!')
else
raise 'Not supported type'
end
end
end
......@@ -113,4 +113,19 @@ module IssuesHelper
'issue-box-open'
end
end
def issue_to_atom(xml, issue)
xml.entry do
xml.id project_issue_url(issue.project, issue)
xml.link href: project_issue_url(issue.project, issue)
xml.title truncate(issue.title, length: 80)
xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(issue.author_email)
xml.author do |author|
xml.name issue.author_name
xml.email issue.author_email
end
xml.summary issue.title
end
end
end
......@@ -25,4 +25,12 @@ module NamespacesHelper
hidden_field_tag(id, value, class: css_class)
end
def namespace_icon(namespace, size = 40)
if namespace.kind_of?(Group)
group_icon(namespace.path)
else
avatar_icon(namespace.owner.email, size)
end
end
end
......@@ -16,4 +16,8 @@ module OauthHelper
[:twitter, :github, :google_oauth2].include?(name.to_sym)
end
end
def additional_providers
enabled_oauth_providers.reject{|provider| provider.to_s.starts_with?('ldap')}
end
end
module ProfileHelper
def oauth_active_class(provider)
if current_user.provider == provider.to_s
if current_user.identities.exists?(provider: provider.to_s)
'active'
end
end
......@@ -10,10 +10,10 @@ module ProfileHelper
end
def show_profile_social_tab?
enabled_social_providers.any? && !current_user.ldap_user?
enabled_social_providers.any?
end
def show_profile_remove_tab?
gitlab_config.signup_enabled && !current_user.ldap_user?
gitlab_config.signup_enabled
end
end
module SortingHelper
def sort_title_oldest_updated
'Oldest updated'
end
def sort_title_recently_updated
'Recently updated'
end
def sort_title_oldest_created
'Oldest created'
end
def sort_title_recently_created
'Recently created'
end
end
module Emails
module Profile
def new_user_email(user_id, password, token = nil)
def new_user_email(user_id, token = nil)
@user = User.find(user_id)
@password = password
@target_url = user_url(@user)
@token = token
mail(to: @user.email, subject: subject("Account was created for you"))
......
......@@ -27,6 +27,14 @@ class Notify < ActionMailer::Base
delay_for(2.seconds)
end
def test_email(recepient_email, subject, body)
mail(to: recepient_email,
subject: subject,
body: body.html_safe,
content_type: 'text/html'
)
end
private
# The default email address to send emails from
......
......@@ -32,7 +32,10 @@ class WebHook < ActiveRecord::Base
def execute(data)
parsed_url = URI.parse(url)
if parsed_url.userinfo.blank?
WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }, verify: false)
WebHook.post(url,
body: data.to_json,
headers: { "Content-Type" => "application/json" },
verify: false)
else
post_url = url.gsub("#{parsed_url.userinfo}@", "")
auth = {
......@@ -45,6 +48,9 @@ class WebHook < ActiveRecord::Base
verify: false,
basic_auth: auth)
end
rescue SocketError, Errno::ECONNREFUSED => e
logger.error("WebHook Error => #{e}")
false
end
def async_execute(data)
......
class Identity < ActiveRecord::Base
belongs_to :user
validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider}
end
\ No newline at end of file
......@@ -26,8 +26,7 @@ class GroupMember < Member
scope :with_group, ->(group) { where(source_id: group.id) }
scope :with_user, ->(user) { where(user_id: user.id) }
scope :with_ldap_dn, -> { references(:user).includes(:user).
where(users: { provider: 'ldap' }) }
scope :with_ldap_dn, -> { joins(user: :identities).where("identities.provider LIKE ?", 'ldap%') }
after_create :notify_create
after_update :notify_update
......
......@@ -90,4 +90,8 @@ class Namespace < ActiveRecord::Base
def kind
type == 'Group' ? 'group' : 'user'
end
def find_fork_of(project)
projects.joins(:forked_project_link).where('forked_project_links.forked_from_project_id = ?', project.id).first
end
end
......@@ -143,7 +143,7 @@ class Project < ActiveRecord::Base
state_machine :import_status, initial: :none do
event :import_start do
transition :none => :started
transition [:none, :finished] => :started
end
event :import_finish do
......@@ -609,4 +609,25 @@ class Project < ActiveRecord::Base
false
end
end
def create_repository
if gitlab_shell.add_repository(path_with_namespace)
true
else
errors.add(:base, "Failed to create repository")
false
end
end
def repository_exists?
!!repository.exists?
end
def create_wiki
ProjectWiki.new(self, self.owner).wiki
true
rescue ProjectWiki::CouldNotCreateWikiError => ex
errors.add(:base, "Failed create wiki")
false
end
end
......@@ -15,11 +15,11 @@
class HipchatService < Service
MAX_COMMITS = 3
prop_accessor :token, :room
prop_accessor :token, :room, :server
validates :token, presence: true, if: :activated?
def title
'Hipchat'
'HipChat'
end
def description
......@@ -33,7 +33,9 @@ class HipchatService < Service
def fields
[
{ type: 'text', name: 'token', placeholder: '' },
{ type: 'text', name: 'room', placeholder: '' }
{ type: 'text', name: 'room', placeholder: '' },
{ type: 'text', name: 'server',
placeholder: 'Leave blank for default. https://chat.hipchat.com' }
]
end
......@@ -44,7 +46,9 @@ class HipchatService < Service
private
def gate
@gate ||= HipChat::Client.new(token)
options = { api_version: 'v2' }
options[:server_url] = server unless server.nil?
@gate ||= HipChat::Client.new(token, options)
end
def create_message(push)
......
......@@ -79,6 +79,7 @@ class User < ActiveRecord::Base
# Profile
has_many :keys, dependent: :destroy
has_many :emails, dependent: :destroy
has_many :identities, dependent: :destroy
# Groups
has_many :members, dependent: :destroy
......@@ -113,7 +114,6 @@ class User < ActiveRecord::Base
validates :name, presence: true
validates :email, presence: true, email: {strict_mode: true}, uniqueness: true
validates :bio, length: { maximum: 255 }, allow_blank: true
validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider}
validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0}
validates :username, presence: true, uniqueness: { case_sensitive: false },
exclusion: { in: Gitlab::Blacklist.path },
......@@ -124,7 +124,7 @@ class User < ActiveRecord::Base
validate :namespace_uniq, if: ->(user) { user.username_changed? }
validate :avatar_type, if: ->(user) { user.avatar_changed? }
validate :unique_email, if: ->(user) { user.email_changed? }
validates :avatar, file_size: { maximum: 100.kilobytes.to_i }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
before_validation :generate_password, on: :create
before_validation :sanitize_attrs
......@@ -179,7 +179,7 @@ class User < ActiveRecord::Base
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') }
scope :subscribed_for_admin_email, -> { where(admin_email_unsubscribed_at: nil) }
scope :ldap, -> { where('provider LIKE ?', 'ldap%') }
scope :ldap, -> { joins(:identities).where('identities.provider LIKE ?', 'ldap%') }
scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active }
#
......@@ -413,7 +413,11 @@ class User < ActiveRecord::Base
end
def ldap_user?
extern_uid && provider.start_with?('ldap')
identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"])
end
def ldap_identity
@ldap_identity ||= identities.find_by(["provider LIKE ?", "ldap%"])
end
def accessible_deploy_keys
......@@ -561,4 +565,14 @@ class User < ActiveRecord::Base
UsersStarProject.create!(project: project, user: self)
end
end
def manageable_namespaces
@manageable_namespaces ||=
begin
namespaces = []
namespaces << namespace
namespaces += owned_groups
namespaces += masters_groups
end
end
end
......@@ -3,6 +3,7 @@ module MergeRequests
def execute(oldrev, newrev, ref)
return true unless ref =~ /heads/
@oldrev, @newrev = oldrev, newrev
@branch_name = ref.gsub("refs/heads/", "")
@fork_merge_requests = @project.fork_merge_requests.opened
@commits = @project.repository.commits_between(oldrev, newrev)
......@@ -35,6 +36,10 @@ module MergeRequests
end
end
def force_push?
Gitlab::ForcePushCheck.force_push?(@project, @oldrev, @newrev)
end
# Refresh merge request diff if we push to source or target branch of merge request
# Note: we should update merge requests from forks too
def reload_merge_requests
......@@ -43,8 +48,22 @@ module MergeRequests
merge_requests = filter_merge_requests(merge_requests)
merge_requests.each do |merge_request|
merge_request.reload_code
merge_request.mark_as_unchecked
if merge_request.source_branch == @branch_name || force_push?
merge_request.reload_code
merge_request.mark_as_unchecked
else
mr_commit_ids = merge_request.commits.map(&:id)
push_commit_ids = @commits.map(&:id)
matches = mr_commit_ids & push_commit_ids
if matches.any?
merge_request.reload_code
merge_request.mark_as_unchecked
else
merge_request.mark_as_unchecked
end
end
end
end
......
......@@ -107,7 +107,7 @@ class NotificationService
# Notify new user with email after creation
def new_user(user, token = nil)
# Don't email omniauth created users
mailer.new_user_email(user.id, user.password, token) unless user.extern_uid?
mailer.new_user_email(user.id, token) unless user.identities.any?
end
# Notify users on new note in system
......
......@@ -37,35 +37,22 @@ module Projects
@project.creator = current_user
if @project.save
log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"")
system_hook_service.execute_hooks_for(@project, :create)
Project.transaction do
@project.save
unless @project.group
@project.team << [current_user, :master]
end
@project.update_column(:last_activity_at, @project.created_at)
if @project.import?
@project.import_start
else
GitlabShellWorker.perform_async(
:add_repository,
@project.path_with_namespace
)
unless @project.import?
unless @project.create_repository
raise 'Failed to create repository'
end
end
end
if @project.persisted?
if @project.wiki_enabled?
begin
# force the creation of a wiki,
ProjectWiki.new(@project, @project.owner).wiki
rescue ProjectWiki::CouldNotCreateWikiError => ex
# Prevent project observer crash
# if failed to create wiki
nil
end
@project.create_wiki
end
after_create_actions
end
@project
......@@ -84,5 +71,20 @@ module Projects
namespace = Namespace.find_by(id: namespace_id)
current_user.can?(:create_projects, namespace)
end
def after_create_actions
log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"")
system_hook_service.execute_hooks_for(@project, :create)
unless @project.group
@project.team << [current_user, :master]
end
@project.update_column(:last_activity_at, @project.created_at)
if @project.import?
@project.import_start
end
end
end
end
......@@ -2,11 +2,9 @@ module Projects
class ForkService < BaseService
include Gitlab::ShellAdapter
def initialize(project, user)
@from_project, @current_user = project, user
end
def execute
@from_project = @project
project_params = {
visibility_level: @from_project.visibility_level,
description: @from_project.description,
......@@ -15,8 +13,18 @@ module Projects
project = Project.new(project_params)
project.name = @from_project.name
project.path = @from_project.path
project.namespace = current_user.namespace
project.creator = current_user
project.creator = @current_user
if namespace = @params[:namespace]
project.namespace = namespace
else
project.namespace = @current_user.namespace
end
unless @current_user.can?(:create_projects, project.namespace)
project.errors.add(:namespace, 'insufficient access rights')
return project
end
# If the project cannot save, we do not want to trigger the project destroy
# as this can have the side effect of deleting a repo attached to an existing
......@@ -27,7 +35,7 @@ module Projects
#First save the DB entries as they can be rolled back if the repo fork fails
project.build_forked_project_link(forked_to_project_id: project.id, forked_from_project_id: @from_project.id)
if project.save
project.team << [current_user, :master]
project.team << [@current_user, :master]
end
#Now fork the repo
unless gitlab_shell.fork_repository(@from_project.path_with_namespace, project.namespace.path)
......@@ -42,8 +50,8 @@ module Projects
else
project.errors.add(:base, "Invalid fork destination")
end
project
project
end
end
end
......@@ -2,8 +2,5 @@ class TestHookService
def execute(hook, current_user)
data = GitPushService.new.sample_data(hook.project, current_user)
hook.execute(data)
true
rescue SocketError
false
end
end
......@@ -56,13 +56,13 @@
= link_to admin_projects_path(sort: nil) do
Name
= link_to admin_projects_path(sort: 'newest') do
Newest
= sort_title_recently_created
= link_to admin_projects_path(sort: 'oldest') do
Oldest
= sort_title_oldest_created
= link_to admin_projects_path(sort: 'recently_updated') do
Recently updated
= sort_title_recently_updated
= link_to admin_projects_path(sort: 'last_updated') do
Last updated
= sort_title_oldest_updated
= link_to admin_projects_path(sort: 'largest_repository') do
Largest repository
= link_to 'New Project', new_project_path, class: "btn btn-new"
......
......@@ -50,9 +50,10 @@
= link_to admin_users_path(sort: 'oldest_sign_in') do
Oldest sign in
= link_to admin_users_path(sort: 'recently_created') do
Recently created
= sort_title_recently_created
= link_to admin_users_path(sort: 'late_created') do
Late created
= sort_title_oldest_created
= link_to 'New User', new_admin_user_path, class: "btn btn-new"
%ul.well-list
- @users.each do |user|
......
......@@ -95,7 +95,7 @@
%li
%span.light LDAP uid:
%strong
= @user.extern_uid
= @user.ldap_identity.extern_uid
- if @user.created_by
%li
......
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do
xml.title "#{current_user.name} issues"
xml.link :href => issues_dashboard_url(:atom, :private_token => current_user.private_token), :rel => "self", :type => "application/atom+xml"
xml.link :href => issues_dashboard_url(:private_token => current_user.private_token), :rel => "alternate", :type => "text/html"
xml.id issues_dashboard_url(:private_token => current_user.private_token)
xml.link href: issues_dashboard_url(:atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml"
xml.link href: issues_dashboard_url(private_token: current_user.private_token), rel: "alternate", type: "text/html"
xml.id issues_dashboard_url(private_token: current_user.private_token)
xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
@issues.each do |issue|
xml.entry do
xml.id project_issue_url(issue.project, issue)
xml.link :href => project_issue_url(issue.project, issue)
xml.title truncate(issue.title, :length => 80)
xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email)
xml.author do |author|
xml.name issue.author_name
xml.email issue.author_email
end
xml.summary issue.title
end
issue_to_atom(xml, issue)
end
end
......@@ -14,13 +14,14 @@
= link_to projects_dashboard_filter_path(sort: nil) do
Name
= link_to projects_dashboard_filter_path(sort: 'newest') do
Newest
= sort_title_recently_created
= link_to projects_dashboard_filter_path(sort: 'oldest') do
Oldest
= sort_title_oldest_created
= link_to projects_dashboard_filter_path(sort: 'recently_updated') do
Recently updated
= sort_title_recently_updated
= link_to projects_dashboard_filter_path(sort: 'last_updated') do
Last updated
= sort_title_oldest_updated
%p.light
All projects you have access to are listed here. Public projects are not included here unless you are a member
%hr
......
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do
xml.title "Dashboard feed#{" - #{current_user.name}" if current_user.name.present?}"
xml.link :href => dashboard_url(:atom), :rel => "self", :type => "application/atom+xml"
xml.link :href => dashboard_url, :rel => "alternate", :type => "text/html"
xml.link href: dashboard_url(:atom), rel: "self", type: "application/atom+xml"
xml.link href: dashboard_url, rel: "alternate", type: "text/html"
xml.id projects_url
xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
@events.each do |event|
if event.proper?
xml.entry do
event_link = event_feed_url(event)
event_title = event_feed_title(event)
event_summary = event_feed_summary(event)
xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
xml.link :href => event_link
xml.title truncate(event_title, :length => 80)
xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(event.author_email)
xml.author do |author|
xml.name event.author_name
xml.email event.author_email
end
xml.summary(:type => "xhtml") { |x| x << event_summary unless event_summary.nil? }
end
end
event_to_atom(xml, event)
end
end
- providers = (enabled_oauth_providers - [:ldap])
- providers = additional_providers
- if providers.present?
.bs-callout.bs-callout-info{:'data-no-turbolink' => 'data-no-turbolink'}
%span Sign in with: &nbsp;
......
......@@ -20,13 +20,13 @@
= link_to explore_groups_path(sort: nil) do
Name
= link_to explore_groups_path(sort: 'newest') do
Newest
= sort_title_recently_created
= link_to explore_groups_path(sort: 'oldest') do
Oldest
= sort_title_oldest_created
= link_to explore_groups_path(sort: 'recently_updated') do
Recently updated
= sort_title_recently_updated
= link_to explore_groups_path(sort: 'last_updated') do
Last updated
= sort_title_oldest_updated
%hr
......
......@@ -20,13 +20,13 @@
= link_to explore_projects_path(sort: nil) do
Name
= link_to explore_projects_path(sort: 'newest') do
Newest
= sort_title_recently_created
= link_to explore_projects_path(sort: 'oldest') do
Oldest
= sort_title_oldest_created
= link_to explore_projects_path(sort: 'recently_updated') do
Recently updated
= sort_title_recently_updated
= link_to explore_projects_path(sort: 'last_updated') do
Last updated
= sort_title_oldest_updated
%hr
.public-projects
......
......@@ -7,18 +7,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
@issues.each do |issue|
xml.entry do
xml.id project_issue_url(issue.project, issue)
xml.link :href => project_issue_url(issue.project, issue)
xml.title truncate(issue.title, :length => 80)
xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email)
xml.author do |author|
xml.name issue.author_name
xml.email issue.author_email
end
xml.summary issue.title
end
issue_to_atom(xml, issue)
end
end
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do
xml.title "Group feed - #{@group.name}"
xml.link :href => group_path(@group, :atom), :rel => "self", :type => "application/atom+xml"
xml.link :href => group_path(@group), :rel => "alternate", :type => "text/html"
xml.link href: group_path(@group, :atom), rel: "self", type: "application/atom+xml"
xml.link href: group_path(@group), rel: "alternate", type: "text/html"
xml.id projects_url
xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
@events.each do |event|
if event.proper?
xml.entry do
event_link = event_feed_url(event)
event_title = event_feed_title(event)
xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
xml.link :href => event_link
xml.title truncate(event_title, :length => 80)
xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(event.author_email)
xml.author do |author|
xml.name event.author_name
xml.email event.author_email
end
xml.summary event_title
end
end
event_to_atom(xml, event)
end
end
......@@ -4,7 +4,16 @@
= hidden_field_tag :group_id, @group.try(:id)
- if @project && @project.persisted?
= hidden_field_tag :project_id, @project.id
= hidden_field_tag :search_code, true
- if current_controller?(:issues)
= hidden_field_tag :scope, 'issues'
- elsif current_controller?(:merge_requests)
= hidden_field_tag :scope, 'merge_requests'
- elsif current_controller?(:wikis)
= hidden_field_tag :scope, 'wiki_blobs'
- else
= hidden_field_tag :search_code, true
- if @snippet || @snippets
= hidden_field_tag :snippets, true
= hidden_field_tag :repository_ref, @ref
......
......@@ -83,7 +83,7 @@
&nbsp;
%span.file_name.js-avatar-filename File name...
= f.file_field :avatar, class: "js-user-avatar-input hidden"
.light The maximum file size allowed is 100KB.
.light The maximum file size allowed is 200KB.
- if @user.avatar?
%hr
= link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar"
......
......@@ -16,11 +16,11 @@
- unless @project.empty_repo?
.fork-buttons
- if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace
- if current_user.already_forked?(@project)
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to project_path(current_user.fork_of(@project)), title: 'Go to my fork' do
= link_to_toggle_fork
- else
= link_to fork_project_path(@project), title: "Fork project", method: "POST" do
= link_to new_project_fork_path(@project), title: "Fork project" do
= link_to_toggle_fork
.star-buttons
......@@ -31,7 +31,7 @@
- else
= link_to_toggle_star('You must sign in to star a project.', false, false)
.project-home-row
.project-home-row.hidden-xs
- if current_user && !empty_repo
.project-home-dropdown
= render "dropdown"
......
......@@ -49,7 +49,7 @@
- else
%span.light No open milestones available.
&nbsp;
= link_to 'Create new milestone', new_project_milestone_path(issuable.project)
= link_to 'Create new milestone', new_project_milestone_path(issuable.project), target: :blank
.form-group
= f.label :label_ids, class: 'control-label' do
%i.icon-tag
......@@ -61,7 +61,7 @@
- else
%span.light No labels yet.
&nbsp;
= link_to 'Create new label', new_project_label_path(issuable.project)
= link_to 'Create new label', new_project_label_path(issuable.project), target: :blank
.form-actions
- if issuable.new_record?
......
%ul.nav.nav-tabs
= nav_link(controller: :issues) do
= link_to project_issues_path(@project), class: "tab" do
Browse Issues
- if project_nav_tab? :issues
= nav_link(controller: :issues) do
= link_to project_issues_path(@project), class: "tab" do
Issues
- if project_nav_tab? :merge_requests
= nav_link(controller: :merge_requests) do
= link_to project_merge_requests_path(@project), class: "tab" do
Merge Requests
= nav_link(controller: :milestones) do
= link_to 'Milestones', project_milestones_path(@project), class: "tab"
= nav_link(controller: :labels) do
......@@ -14,7 +19,7 @@
- if current_controller?(:issues)
- if current_user
%li
%li.hidden-xs
= link_to project_issues_path(@project, :atom, { private_token: current_user.private_token }) do
%i.fa.fa-rss
......@@ -22,15 +27,29 @@
.pull-right
%button.btn.btn-default.sidebar-expand-button
%i.icon.fa.fa-list
= form_tag project_issues_path(@project), method: :get, id: "issue_search_form", class: 'pull-left issue-search-form' do
.append-right-10.hidden-xs.hidden-sm
= search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' }
= hidden_field_tag :state, params['state']
= hidden_field_tag :scope, params['scope']
= hidden_field_tag :assignee_id, params['assignee_id']
= hidden_field_tag :milestone_id, params['milestone_id']
= hidden_field_tag :label_id, params['label_id']
.pull-left
= form_tag project_issues_path(@project), method: :get, id: "issue_search_form", class: 'pull-left issue-search-form' do
.append-right-10.hidden-xs.hidden-sm
= search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' }
= hidden_field_tag :state, params['state']
= hidden_field_tag :scope, params['scope']
= hidden_field_tag :assignee_id, params['assignee_id']
= hidden_field_tag :milestone_id, params['milestone_id']
= hidden_field_tag :label_id, params['label_id']
- if can? current_user, :write_issue, @project
= link_to new_project_issue_path(@project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do
%i.fa.fa-plus
New Issue
- if current_controller?(:merge_requests)
%li.pull-right
.pull-right
%button.btn.btn-default.sidebar-expand-button
%i.icon.fa.fa-list
- if can? current_user, :write_merge_request, @project
= link_to new_project_merge_request_path(@project), class: "btn btn-new pull-left", title: "New Merge Request" do
%i.fa.fa-plus
New Merge Request
......@@ -20,9 +20,9 @@
= link_to project_branches_path(sort: nil) do
Name
= link_to project_branches_path(sort: 'recently_updated') do
Recently updated
= sort_title_recently_updated
= link_to project_branches_path(sort: 'last_updated') do
Last updated
= sort_title_oldest_updated
%hr
- unless @branches.empty?
%ul.bordered-list.top-list.all-branches
......
- if @project.saved?
- if @project.import?
:plain
location.href = "#{import_project_path(@project)}";
- else
:plain
location.href = "#{project_path(@project)}";
- else
:plain
$(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
$('.project-submit').enable();
$('.save-project-loader').hide();
$('.project-edit-container').show();
.alert.alert-danger.alert-block
%h4
%i.fa.fa-code-fork
Fork Error!
%p
You tried to fork
= link_to_project @project
but it failed for the following reason:
- if @forked_project && @forked_project.errors.any?
%p
&ndash;
= @forked_project.errors.full_messages.first
%p
= link_to fork_project_path(@project), title: "Fork", class: "btn", method: "POST" do
%i.fa.fa-code-fork
Try to Fork again
- if @forked_project && !@forked_project.saved?
.alert.alert-danger.alert-block
%h4
%i.fa.fa-code-fork
Fork Error!
%p
You tried to fork
= link_to_project @project
but it failed for the following reason:
- if @forked_project && @forked_project.errors.any?
%p
&ndash;
= @forked_project.errors.full_messages.first
%p
= link_to new_project_fork_path(@project), title: "Fork", class: "btn" do
%i.fa.fa-code-fork
Try to Fork again
%h3.page-title Fork project
%p.lead Select namespace where to fork this project
%hr
.fork-namespaces
- @namespaces.in_groups_of(6, false) do |group|
.row
- group.each do |namespace|
.col-md-2.col-sm-3
- if fork = namespace.find_fork_of(@project)
.thumbnail.fork-exists-thumbnail
= link_to project_path(fork), title: "Visit project fork", class: 'has_tooltip' do
= image_tag namespace_icon(namespace, 200)
.caption
%h4=namespace.human_name
%p
= namespace.path
- else
.thumbnail.fork-thumbnail
= link_to project_fork_path(@project, namespace_id: namespace.id), title: "Fork here", method: "POST", class: 'has_tooltip' do
= image_tag namespace_icon(namespace, 200)
.caption
%h4=namespace.human_name
%p
= namespace.path
%p.light
Fork is a copy of a project repository.
%br
Forking a repository allows you to do changes without affecting the original project.
.save-project-loader.hide
.center
%h2
%i.fa.fa-spinner.fa-spin
Forking repository
%p Please wait a moment, this page will automatically refresh when ready.
- if @project.import_in_progress?
.save-project-loader
.center
%h2
%i.fa.fa-spinner.fa-spin
Import in progress.
%p.monospace git clone --bare #{hidden_pass_url(@project.import_url)}
%p Please wait while we import the repository for you. Refresh at will.
:javascript
new ProjectImport();
- elsif @project.import_failed?
.save-project-loader
.center
%h2
Import failed. Retry?
%hr
- if can?(current_user, :admin_project, @project)
= form_for @project, url: retry_import_project_path(@project), method: :put, html: { class: 'form-horizontal' } do |f|
.form-group.import-url-data
= f.label :import_url, class: 'control-label' do
%span Import existing git repo
.col-sm-10
= f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git'
.bs-callout.bs-callout-info
This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
%br
The import will time out after 4 minutes. For big repositories, use a clone/push combination.
For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"}
.form-actions
= f.submit 'Retry import', class: "btn btn-create", tabindex: 4
%h3.page-title
- if @project.import_failed?
Import failed. Retry?
- else
Import repository
%hr
= form_for @project, url: project_import_path(@project), method: :post, html: { class: 'form-horizontal' } do |f|
.form-group.import-url-data
= f.label :import_url, class: 'control-label' do
%span Import existing git repo
.col-sm-10
= f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git'
.bs-callout.bs-callout-info
This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
%br
The import will time out after 4 minutes. For big repositories, use a clone/push combination.
For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"}
.form-actions
= f.submit 'Start import', class: "btn btn-create", tabindex: 4
.save-project-loader
.center
%h2
%i.fa.fa-spinner.fa-spin
Import in progress.
%p.monospace git clone --bare #{hidden_pass_url(@project.import_url)}
%p Please wait while we import the repository for you. Refresh at will.
:javascript
new ProjectImport();
......@@ -4,7 +4,6 @@
= check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue", disabled: !can?(current_user, :modify_issue, issue)
.issue-title
%span.light= "##{issue.iid}"
%span.str-truncated
= link_to_gfm issue.title, project_issue_path(issue.project, issue), class: "row_title"
- if issue.closed?
......@@ -12,6 +11,7 @@
CLOSED
.issue-info
%span.light= "##{issue.iid}"
- if issue.assignee
assigned to #{link_to_member(@project, issue.assignee)}
- if issue.votes_count > 0
......@@ -28,7 +28,7 @@
%span.task-status
= issue.task_status
.pull-right
.pull-right.issue-updated-at
%small updated #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_update_ago')}
.issue-labels
......
......@@ -7,17 +7,6 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
@issues.each do |issue|
xml.entry do
xml.id project_issue_url(@project, issue)
xml.link :href => project_issue_url(@project, issue)
xml.title truncate(issue.title, :length => 80)
xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email)
xml.author do |author|
xml.name issue.author_name
xml.email issue.author_email
end
xml.summary issue.title
end
issue_to_atom(xml, issue)
end
end
= render "head"
= render "projects/issues_nav"
.row
.fixed.fixed.sidebar-expand-button.hidden-lg.hidden-md.hidden-xs
%i.fa.fa-list.fa-2x
......
......@@ -38,6 +38,10 @@
- else
Open
.cross-project-ref
%i.fa.fa-link.has_tooltip{:"data-original-title" => 'Cross-project reference'}
= cross_project_reference(@project, @issue)
.creator
Created by #{link_to_member(@project, @issue.author)} #{issue_timestamp(@issue)}
......
= render "projects/issues/head"
= render "projects/issues_nav"
- if can? current_user, :admin_label, @project
= link_to new_project_label_path(@project), class: "pull-right btn btn-new" do
......
%li{ class: mr_css_classes(merge_request) }
.merge-request-title
%span.light= "##{merge_request.iid}"
= link_to_gfm truncate(merge_request.title, length: 80), project_merge_request_path(merge_request.target_project, merge_request), class: "row_title"
- if merge_request.merged?
%small.pull-right
%i.fa.fa-check
MERGED
- else
%span.pull-right
%span.pull-right.hidden-xs
- if merge_request.for_fork?
%span.light
#{merge_request.source_project_namespace}:
......@@ -15,6 +14,7 @@
%i.fa.fa-angle-right.light
= merge_request.target_branch
.merge-request-info
%span.light= "##{merge_request.iid}"
- if merge_request.author
authored by #{link_to_member(merge_request.source_project, merge_request.author)}
- if merge_request.votes_count > 0
......@@ -31,7 +31,7 @@
%span.task-status
= merge_request.task_status
.pull-right
.pull-right.hidden-xs
%small updated #{time_ago_with_tooltip(merge_request.updated_at, 'bottom', 'merge_request_updated_ago')}
.merge-request-labels
......
- if can? current_user, :write_merge_request, @project
= link_to new_project_merge_request_path(@project), class: "pull-right btn btn-new", title: "New Merge Request" do
%i.fa.fa-plus
New Merge Request
%h3.page-title
Merge Requests
%hr
= render "projects/issues_nav"
.row
.fixed.sidebar-expand-button.hidden-lg.hidden-md
%i.fa.fa-list.fa-2x
.col-md-3.responsive-side
= render 'shared/project_filter', project_entities_path: project_merge_requests_path(@project),
labels: true, redirect: 'merge_requests', entity: 'merge_request'
......
......@@ -8,6 +8,10 @@
- else
Open
.cross-project-ref
%i.fa.fa-link.has_tooltip{:"data-original-title" => 'Cross-project reference'}
= cross_project_reference(@project, @merge_request)
.creator
Created by #{link_to_member(@project, @merge_request.author)} #{time_ago_with_tooltip(@merge_request.created_at)}
......
= render "projects/issues/head"
= render "projects/issues_nav"
.milestones_content
%h3.page-title
Milestones
......
= render "projects/issues/head"
= render "projects/issues_nav"
%h3.page-title
Milestone ##{@milestone.iid}
.pull-right
......
......@@ -3,7 +3,7 @@
= render 'projects/errors'
.project-edit-content
= form_for @project, remote: true, html: { class: 'new_project form-horizontal' } do |f|
= form_for @project, html: { class: 'new_project form-horizontal' } do |f|
.form-group.project-name-holder
= f.label :name, class: 'control-label' do
%strong Project name
......
%h2
%i.fa.fa-warning
No repository
%p.slead
The repository for this project does not exist.
%br
This means you can not push code until you create an empty repository or import existing one.
%hr
.no-repo-actions
= link_to project_repository_path(@project), method: :post, class: 'btn btn-primary' do
Create empty bare repository
%strong.prepend-left-10.append-right-10 or
= link_to new_project_import_path(@project), class: 'btn' do
Import repository
- if can? current_user, :remove_project, @project
.prepend-top-20
= link_to 'Remove project', @project, data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right"
......@@ -29,7 +29,7 @@
= yield(:note_actions)
%a.btn.grouped.js-close-discussion-note-form Cancel
.note-form-option
.note-form-option.hidden-xs
%a.choose-btn.btn.js-choose-note-attachment-button
%i.fa.fa-paperclip
%span Choose File ...
......
......@@ -18,6 +18,8 @@
%i.fa.fa-trash-o.cred
Remove
= link_to_member(@project, note.author, avatar: false)
%span.author-username
= '@' + note.author.username
%span.note-last-update
= note_timestamp(note)
......
......@@ -6,7 +6,7 @@
= tag.name
- if tag.message.present?
&nbsp;
= tag.message
= strip_gpg_signature(tag.message)
.pull-right
- if can? current_user, :download_code, @project
= render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-small'
......
......@@ -25,6 +25,7 @@
= @search_results.notes_count
%li{class: ("active" if @scope == 'wiki_blobs')}
= link_to search_filter_path(scope: 'wiki_blobs') do
%i.fa.fa-book
Wiki
.pull-right
= @search_results.wiki_blobs_count
......
......@@ -4,4 +4,4 @@
&nbsp;
%span.file_name.js-avatar-filename File name...
= f.file_field :avatar, class: 'js-group-avatar-input hidden'
.light The maximum file size allowed is 100KB.
.light The maximum file size allowed is 200KB.
.gitlab-promo
= link_to 'Homepage', promo_url
= link_to "Blog", promo_url + '/blog/'
= link_to "@gitlabhq", "https://twitter.com/gitlabhq"
= link_to "@gitlab", "https://twitter.com/gitlab"
= link_to "Requests", "http://feedback.gitlab.com/"
......@@ -9,13 +9,13 @@
%ul.dropdown-menu
%li
= link_to project_filter_path(sort: 'newest') do
Newest
= sort_title_recently_created
= link_to project_filter_path(sort: 'oldest') do
Oldest
= sort_title_oldest_created
= link_to project_filter_path(sort: 'recently_updated') do
Recently updated
= sort_title_recently_updated
= link_to project_filter_path(sort: 'last_updated') do
Last updated
= sort_title_oldest_updated
= link_to project_filter_path(sort: 'milestone_due_soon') do
Milestone due soon
= link_to project_filter_path(sort: 'milestone_due_later') do
......
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do
xml.title "Activity feed for #{@user.name}"
xml.link href: user_url(@user, :atom), rel: "self", type: "application/atom+xml"
xml.link href: user_url(@user), rel: "alternate", type: "text/html"
xml.id projects_url
xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
@events.each do |event|
event_to_atom(xml, event)
end
end
......@@ -18,7 +18,15 @@
%h4 Groups:
= render 'groups', groups: @groups
%hr
%h4 User Activity:
%h4
User Activity:
- if current_user
%span.rss-icon.pull-right
= link_to user_path(@user, :atom, { private_token: current_user.private_token }) do
%strong
%i.fa.fa-rss
= render @events
.col-md-4
= render 'profile', user: @user
......
......@@ -92,5 +92,8 @@ module Gitlab
redis_config_hash[:namespace] = 'cache:gitlab'
config.cache_store = :redis_store, redis_config_hash
# This is needed for gitlab-shell
ENV['GITLAB_PATH_OUTSIDE_HOOK'] = ENV['PATH']
end
end
......@@ -35,7 +35,7 @@ production: &base
## Date & Time settings
# Uncomment and customize if you want to change the default time zone of GitLab application.
# To see all available zones, run `bundle exec rake time:zones:all`
# To see all available zones, run `bundle exec rake time:zones:all RAILS_ENV=production`
# time_zone: 'UTC'
## Email settings
......
......@@ -14,7 +14,8 @@ Sidekiq.configure_server do |config|
}
config.server_middleware do |chain|
chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger
chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS']
chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MAX_RSS']
end
end
......
......@@ -146,7 +146,8 @@ Gitlab::Application.routes.draw do
end
end
match "/u/:username" => "users#show", as: :user, constraints: { username: /.*/ }, via: :get
match "/u/:username" => "users#show", as: :user,
constraints: {username: /(?:[^.]|\.(?!atom$))+/, format: /atom/}, via: :get
#
# Dashboard Area
......@@ -203,14 +204,11 @@ Gitlab::Application.routes.draw do
resources :projects, constraints: { id: /[a-zA-Z.0-9_\-]+\/[a-zA-Z.0-9_\-]+/ }, except: [:new, :create, :index], path: "/" do
member do
put :transfer
post :fork
post :archive
post :unarchive
post :upload_image
post :toggle_star
get :autocomplete_sources
get :import
put :retry_import
end
scope module: :projects do
......@@ -236,11 +234,11 @@ Gitlab::Application.routes.draw do
match "/compare/:from...:to" => "compare#show", as: "compare", via: [:get, :post], constraints: {from: /.+/, to: /.+/}
resources :snippets, constraints: {id: /\d+/} do
member do
get "raw"
end
resources :snippets, constraints: {id: /\d+/} do
member do
get "raw"
end
end
resources :wikis, only: [:show, :edit, :destroy, :create], constraints: {id: /[a-zA-Z.0-9_\-\/]+/} do
collection do
......@@ -254,7 +252,10 @@ Gitlab::Application.routes.draw do
end
end
resource :repository, only: [:show] do
resource :fork, only: [:new, :create]
resource :import, only: [:new, :create, :show]
resource :repository, only: [:show, :create] do
member do
get "archive", constraints: { format: Gitlab::Regex.archive_formats_regex }
end
......
# In 20140914145549_migrate_to_new_members_model.rb we forgot to set the
# created_at and updated_at times for new records in the 'members' table. This
# became a problem after commit c8e78d972a5a628870eefca0f2ccea0199c55bda which
# was added in GitLab 7.5. With this migration we ensure that all rows in
# 'members' have at least some created_at and updated_at timestamp.
class AddTimestampsToMembers < ActiveRecord::Migration
def up
execute "UPDATE members SET created_at = NOW() WHERE created_at is NULL"
execute "UPDATE members SET updated_at = NOW() WHERE updated_at is NULL"
end
def down
# no change
end
end
class AddIdentityTable < ActiveRecord::Migration
def up
create_table :identities do |t|
t.string :extern_uid
t.string :provider
t.references :user
end
add_index :identities, :user_id
User.where("provider IS NOT NULL").find_each do |user|
execute "INSERT INTO identities(provider, extern_uid, user_id) VALUES('#{user.provider}', '#{user.extern_uid}', '#{user.id}')"
end
remove_column :users, :extern_uid
remove_column :users, :provider
end
def down
add_column :users, :extern_uid, :string
add_column :users, :provider, :string
User.where("id IN(SELECT user_id FROM identities)").find_each do |user|
identity = user.identities.last
user.extern_uid = identity.extern_uid
user.provider = identity.provider
user.save
end
drop_table :identities
end
end
......@@ -110,6 +110,14 @@ ActiveRecord::Schema.define(version: 20141126120926) do
t.string "file_name_regex"
end
create_table "identities", force: true do |t|
t.string "extern_uid"
t.string "provider"
t.integer "user_id"
end
add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree
create_table "issues", force: true do |t|
t.string "title"
t.integer "assignee_id"
......@@ -253,9 +261,9 @@ ActiveRecord::Schema.define(version: 20141126120926) do
t.datetime "updated_at"
t.string "type"
t.string "description", default: "", null: false
t.string "avatar"
t.string "ldap_cn"
t.integer "ldap_access"
t.string "avatar"
end
add_index "namespaces", ["name"], name: "index_namespaces_on_name", using: :btree
......@@ -316,8 +324,8 @@ ActiveRecord::Schema.define(version: 20141126120926) do
t.boolean "archived", default: false, null: false
t.string "import_status"
t.float "repository_size", default: 0.0
t.integer "star_count", default: 0, null: false
t.text "merge_requests_template"
t.integer "star_count", default: 0, null: false
t.boolean "merge_requests_rebase_enabled", default: false
end
......@@ -407,8 +415,6 @@ ActiveRecord::Schema.define(version: 20141126120926) do
t.string "bio"
t.integer "failed_attempts", default: 0
t.datetime "locked_at"
t.string "extern_uid"
t.string "provider"
t.string "username"
t.boolean "can_create_group", default: true, null: false
t.boolean "can_create_team", default: true, null: false
......@@ -417,6 +423,7 @@ ActiveRecord::Schema.define(version: 20141126120926) do
t.integer "notification_level", default: 1, null: false
t.datetime "password_expires_at"
t.integer "created_by_id"
t.datetime "last_credential_check_at"
t.string "avatar"
t.string "confirmation_token"
t.datetime "confirmed_at"
......@@ -424,7 +431,6 @@ ActiveRecord::Schema.define(version: 20141126120926) do
t.string "unconfirmed_email"
t.boolean "hide_no_ssh_key", default: false
t.string "website_url", default: "", null: false
t.datetime "last_credential_check_at"
t.datetime "admin_email_unsubscribed_at"
end
......@@ -433,7 +439,6 @@ ActiveRecord::Schema.define(version: 20141126120926) do
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
add_index "users", ["extern_uid", "provider"], name: "index_users_on_extern_uid_and_provider", unique: true, using: :btree
add_index "users", ["name"], name: "index_users_on_name", using: :btree
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
add_index "users", ["username"], name: "index_users_on_username", using: :btree
......
......@@ -2,22 +2,22 @@
## User documentation
- [API](api/README.md) Explore how you can access GitLab via a simple and powerful API.
- [Markdown](markdown/markdown.md) Learn what you can do with GitLab's advanced formatting system.
- [API](api/README.md) Automate GitLab via a simple and powerful API.
- [Markdown](markdown/markdown.md) GitLab's advanced formatting system.
- [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do.
- [Project Services](project_services/project_services.md) Explore how project services can integrate a project with external services, such as for CI.
- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to a project.
- [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat.
- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects.
- [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
- [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
- [Workflow](workflow/README.md) Learn how to use Git and GitLab together.
- [Workflow](workflow/README.md) Learn how to get the maximum out of GitLab.
## Administrator documentation
- [Install](install/README.md) Requirements, directory structures and manual installation.
- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter.
- [Raketasks](raketasks/README.md) Explore what GitLab has in store for you to make administration easier.
- [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects.
- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough.
- [System hooks](system_hooks/system_hooks.md) Let GitLab notify you when certain management tasks need to be carried out.
- [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed.
- [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
- [Update](update/README.md) Update guides to upgrade your installation.
- [Audit Events](administration/audit_events.md) Check how user access changed in projects and groups.
......
......@@ -260,12 +260,14 @@ GET /user/keys
{
"id": 1,
"title": "Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
"created_at": "2014-08-01T14:47:39.080Z"
},
{
"id": 3,
"title": "Another Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
"created_at": "2014-08-01T14:47:39.080Z"
}
]
```
......@@ -302,7 +304,8 @@ Parameters:
{
"id": 1,
"title": "Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
"created_at": "2014-08-01T14:47:39.080Z"
}
```
......
......@@ -4,3 +4,4 @@
- [Shell commands](shell_commands.md) in the GitLab codebase
- [Rake tasks](rake_tasks.md) for development
- [CI setup](ci_setup.md) for testing GitLab
- [Sidekiq debugging](sidekiq_debugging.md)
......@@ -8,6 +8,38 @@ EE releases are available not long after CE releases. To obtain the GitLab EE th
Both EE and CE require an add-on component called gitlab-shell. It is obtained from the [gitlab-shell repository](https://gitlab.com/gitlab-org/gitlab-shell/tree/master). New versions are usually tags but staying on the master branch will give you the latest stable version. New releases are generally around the same time as GitLab CE releases with exception for informal security updates deemed critical.
## Physical office analogy
You can imagine GitLab as a physical office.
**The repositories** are the goods GitLab handling.
They can be stored in a warehouse.
This can be either a hard disk, or something more complex, such as a NFS filesystem;
**NginX** acts like the front-desk.
Users come to NginX and request actions to be done by workers in the office;
**The database** is a series of metal file cabinets with information on:
- The goods in the warehouse (metadata, issues, merge requests etc);
- The users coming to the front desk (permissions)
**Redis** is a communication board with “cubby holes” that can contain tasks for office workers;
**Sidekiq** is a worker that primarily handles sending out emails.
It takes tasks from the Redis communication board;
**A Unicorn worker** is a worker that handles quick/mundane tasks.
They work with the communication board (Redis).
Their job description:
- check permissions by checking the user session stored in a Redis “cubby hole”;
- make tasks for Sidekiq;
- fetch stuff from the warehouse or move things around in there;
**Gitlab-shell** is a third kind of worker that takes orders from a fax machine (SSH) instead of the front desk (HTTP).
Gitlab-shell communicates with Sidekiq via the “communication board” (Redis), and asks quick questions of the Unicorn workers either directly or via the front desk.
**GitLab Enterprise Edition (the application)** is the collection of processes and business practices that the office is run by.
## System Layout
When referring to ~git in the pictures it means the home directory of the git user which is typically /home/git.
......
# Rake tasks for developers
## Setup db with developer seeds:
## Setup db with developer seeds
Note that if your db user does not have advanced privileges you must create the db manually before running this command.
......@@ -8,6 +8,10 @@ Note that if your db user does not have advanced privileges you must create the
bundle exec rake setup
```
The `setup` task is a alias for `gitlab:setup`.
This tasks calls `db:setup` to create the database, calls `add_limits_mysql` that adds limits to the database schema in case of a MySQL database and fianlly it calls `db:seed_fu` to seed the database.
Note: `db:setup` calls `db:seed` but this does nothing.
## Run tests
This runs all test suites present in GitLab.
......
......@@ -6,9 +6,9 @@ Since a manual installation is a lot of work and error prone we strongly recomme
## Select Version to Install
Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the branch (version) of GitLab you would like to install. In most cases this should be the highest numbered stable branch (example shown below).
![Select latest branch](https://i.imgur.com/Lrdxk1k.png)
Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the tag (version) of GitLab you would like to install.
In most cases this should be the highest numbered production tag (without rc in it).
You can select the tag in the version dropdown in the top left corner of GitLab (below the menu bar).
If the highest number stable branch is unclear please check the [GitLab Blog](https://about.gitlab.com/blog/) for installation guide links by version.
......
......@@ -50,6 +50,12 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab
### Memory
- 512MB is the absolute minimum but we do not recommend this amount of memory.
You will need to configure minimum 1.5GB of swap space.
With 1.5GB of swap space you must configure only one unicorn worker.
With one unicorn worker only git over ssh access will work because the git over HTTP access requires two running workers (one worker to receive the user request and one worker for the authorization check).
If you use SSD storage and configure 1.5GB of swap space you can use two Unicorn workers, this will allow HTTP access but it will still be slow.
Consider installing GitLab on Ubuntu as installation on CentOS could be unsuccessful with this amount of memory.
- 1GB RAM + 1GB swap supports up to 100 users
- **2GB RAM** is the **recommended** memory size and supports up to 500 users
- 4GB RAM supports up to 2,000 users
......@@ -85,7 +91,7 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o
## Supported web browsers
- Chrome (Latest stable version)
- Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/))
- Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/))
- Safari 7+ (known problem: required fields in html5 do not work)
- Opera (Latest released version)
- IE 10+
......@@ -9,3 +9,20 @@ If correctly setup, emails that require an action will be marked in Gmail.
To get this functioning, you need to be registered with Google.
[See how to register with google in this document.](https://developers.google.com/gmail/markup/registering-with-google)
To aid the registering with google, GitLab offers a rake task that will send an email to google whitelisting email address from your GitLab server.
To check what would be sent to the google email address, run the rake task:
```bash
bundle exec rake gitlab:mail_google_schema_whitelisting RAILS_ENV=production
```
**This will not send the email but give you the output of how the mail will look.**
Copy the output of the rake task to [google email markup tester](https://www.google.com/webmasters/markup-tester/u/0/) and press "Validate".
If you receive "No errors detected" message from the tester you can send the email using:
```bash
bundle exec rake gitlab:mail_google_schema_whitelisting RAILS_ENV=production SEND=true
``
......@@ -19,6 +19,7 @@ If a user is a GitLab administrator they receive all permissions.
| Create new merge request | | | ✓ | ✓ | ✓ |
| Create new branches | | | ✓ | ✓ | ✓ |
| Push to non-protected branches | | | ✓ | ✓ | ✓ |
| Force push to non-protected branches | | | ✓ | ✓ | ✓ |
| Remove non-protected branches | | | ✓ | ✓ | ✓ |
| Add tags | | | ✓ | ✓ | ✓ |
| Write a wiki | | | ✓ | ✓ | ✓ |
......@@ -35,6 +36,8 @@ If a user is a GitLab administrator they receive all permissions.
| Switch visibility level | | | | | ✓ |
| Transfer project to another namespace | | | | | ✓ |
| Remove project | | | | | ✓ |
| Force push to protected branches | | | | | |
| Remove protected branches | | | | | |
## Group
......
......@@ -14,7 +14,7 @@ You can only restore a backup to exactly the same version of GitLab that you cre
sudo gitlab-rake gitlab:backup:create
# if you've installed GitLab from source or using the cookbook
bundle exec rake gitlab:backup:create RAILS_ENV=production
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
Example output:
......@@ -203,5 +203,8 @@ Add the following lines at the bottom:
```
# Create a full backup of the GitLab repositories and SQL database every day at 4am
0 4 * * * cd /home/git/gitlab && PATH=/usr/local/bin:/usr/bin:/bin bundle exec rake gitlab:backup:create RAILS_ENV=production
0 4 * * * cd /home/git/gitlab && PATH=/usr/local/bin:/usr/bin:/bin bundle exec rake gitlab:backup:create RAILS_ENV=production CRON=1
```
The `CRON=1` environment setting tells the backup script to suppress all progress output if there are no errors.
This is recommended to reduce cron spam.
......@@ -122,3 +122,27 @@ sudo -u git -H mkdir -p /home/git/gitlab-satellites
sudo -u git -H bundle exec rake gitlab:satellites:create RAILS_ENV=production
sudo chmod u+rwx,g=rx,o-rwx /home/git/gitlab-satellites
```
## Rebuild authorized_keys file
In some case it is necessary to rebuild the `authorized_keys` file.
For Omnibus-packages:
```
sudo gitlab-rake gitlab:shell:setup
```
For installations from source:
```
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:shell:setup RAILS_ENV=production
```
```
This will rebuild an authorized_keys file.
You will lose any data stored in authorized_keys file.
Do you want to continue (yes/no)? yes
............................
```
......@@ -8,7 +8,9 @@ NOTE: This is a guide for GitLab developers.
### **2. Release Manager**
A release manager is selected that coordinates the entire release of this version. The release manager has to make sure all the steps below are done and delegated where necessary. This person should also make sure this document is kept up to date and issues are created and updated.
A release manager is selected that coordinates all releases the coming month, including the patch releases for previous releases.
The release manager has to make sure all the steps below are done and delegated where necessary.
This person should also make sure this document is kept up to date and issues are created and updated.
### **3. Create an overall issue**
......@@ -18,36 +20,40 @@ Replace the dates with actual dates based on the number of workdays before the r
```
Xth:
* Update the changelog (#LINK)
* Triage the omnibus-gitlab milestone
- [ ] Update the CE changelog (#LINK)
- [ ] Update the EE changelog (#LINK)
- [ ] Update the CI changelog (#LINK)
- [ ] Triage the omnibus-gitlab milestone
Xth:
* Merge CE in to EE (#LINK)
* Close the omnibus-gitlab milestone
- [ ] Merge CE in to EE (#LINK)
- [ ] Close the omnibus-gitlab milestone
Xth:
* Create x.x.0.rc1 (#LINK)
* Build package for GitLab.com (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#build-a-package)
- [ ] Create x.x.0.rc1 (#LINK)
- [ ] Create x.x.0.rc1-ee (#LINK)
- [ ] Create CI y.y.0.rc1 (#LINK)
- [ ] Build package for GitLab.com (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#build-a-package)
Xth:
* Update GitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package)
* Regression issue and tweet about rc1 (#LINK)
* Start blog post (#LINK)
- [ ] Update GitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package)
- [ ] Regression issues (CE, CI) and tweet about rc1 (#LINK)
- [ ] Start blog post (#LINK)
Xth:
* Do QA and fix anything coming out of it (#LINK)
- [ ] Do QA and fix anything coming out of it (#LINK)
22nd:
* Release CE and EE (#LINK)
- [ ] Release CE, EE and CI (#LINK)
Xth:
* * Deploy to GitLab.com (#LINK)
- [ ] Deploy to GitLab.com (#LINK)
```
......@@ -55,6 +61,8 @@ Xth:
Any changes not yet added to the changelog are added by lead developer and in that merge request the complete team is asked if there is anything missing.
There are three changelogs that need to be updated: CE, EE and CI.
### **5. Take weekend and vacations into account**
Ensure that there is enough time to incorporate the findings of the release candidate, etc.
......@@ -79,6 +87,7 @@ The RC1 release comes with the task to update the installation and upgrade docs.
1. Create: CE update guide from previous version. Like `7.3-to-7.4.md`
1. Create: CE to EE update guide in EE repository for latest version.
1. Update: `6.x-or-7.x-to-7.x.md` to latest version.
1. Create: CI update guide from previous version
It's best to copy paste the previous guide and make changes where necessary.
The typical steps are listed below with any points you should specifically look at.
......@@ -171,6 +180,24 @@ Now developers can use master for merging new features.
So you should use stable branch for future code chages related to release.
### 5. Release GitLab CI RC1
Add to your local `gitlab-ci/.git/config`:
```
[remote "public"]
url = none
pushurl = git@dev.gitlab.org:gitlab/gitlab-ci.git
pushurl = git@gitlab.com:gitlab-org/gitlab-ci.git
pushurl = git@github.com:gitlabhq/gitlab-ci.git
```
* Create a stable branch `x-y-stable`
* Bump VERSION to `x.y.0.rc1`
* `git tag -a v$(cat VERSION) -m "Version $(cat VERSION)"
* `git push public x-y-stable v$(cat VERSION)`
# **4 workdays before release - Release RC1**
### **1. Determine QA person
......@@ -189,6 +216,8 @@ It is important to do this as soon as possible, so we can catch any errors befor
- Start with a complete copy of the [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md) and fill it out.
- Check the changelog of CE and EE for important changes.
- Also check the CI changelog
- Add a proposed tweet text to the blog post WIP MR description.
- Create a WIP MR for the blog post
- Ask Dmitriy to add screenshots to the WIP MR.
- Decide with team who will be the MVP user.
......@@ -236,7 +265,7 @@ Create an issue with description of a problem, if it is quick fix fix it yoursel
**NOTE** If there is a problem that cannot be fixed in a timely manner, reverting the feature is an option! If the feature is reverted,
create an issue about it in order to discuss the next steps after the release.
# **22nd - Release CE and EE**
# **22nd - Release CE, EE and CI**
**Make sure EE `x-x-stable-ee` has latest changes from CE `x-x-stable`**
......@@ -256,6 +285,11 @@ Bump version, create release tag and push to remotes:
bundle exec rake release["x.x.0"]
```
Also perform these steps for GitLab CI:
- bump version in the stable branch
- create annotated tag
- push the stable branch and the annotated tag to the public repositories
### **2. Update installation.md**
......@@ -286,17 +320,4 @@ Proposed tweet for CE "GitLab X.X is released! It brings *** <link-to-blogpost>"
# **1 workday after release - Update GitLab.com**
- Build a package for gitlab.com based on the official release instead of RC1
- Deploy the package
# **25th - Release GitLab CI**
- Create the update guid `doc/x.x-to-x.x.md`.
- Update CHANGELOG
- Bump version
- Create annotated tags `git tag -a vx.x.0 -m 'Version x.x.0' xxxxx`
- Create stable branch `x-x-stable`
- Create GitHub release post
- Post to blog about release
- Post to twitter
- Deploy the package (should not need downtime because of the small difference with RC1)
......@@ -17,6 +17,7 @@ Otherwise include it in the monthly release and note there was a regression fix
1. Create an issue on private GitLab development server
1. Name the issue "Release X.X.X CE and X.X.X EE", this will make searching easier
1. Fix the issue on a feature branch, do this on the private GitLab development server
1. If it is a security issue, then assign it to the release manager and apply a 'security' label
1. Consider creating and testing workarounds
1. After the branch is merged into master, cherry pick the commit(s) into the current stable branch
1. Make sure that the build has passed and all tests are passing
......
......@@ -14,11 +14,12 @@ Please report suspected security vulnerabilities in private to <support@gitlab.c
1. Verify that the issue can be reproduced
1. Acknowledge the issue to the researcher that disclosed it
1. Inform the release manager that there needs to be a security release
1. Do the steps from [patch release document](doc/release/patch.md), starting with "Create an issue on private GitLab development server"
1. The MR with the security fix should get a 'security' label and be assigned to the release manager
1. Create feature branches for the blog post on GitLab.com and link them from the code branch
1. Merge and publish the blog posts
1. Send tweets about the release from `@gitlabhq`
1. Send out an email to the 'GitLab Newsletter' mailing list on MailChimp (or the 'Subscribers' list if the security fix is for EE only)
1. Send out an email to [the community google mailing list](https://groups.google.com/forum/#!forum/gitlabhq)
1. Post a signed copy of our complete announcement to [oss-security](http://www.openwall.com/lists/oss-security/) and request a CVE number
1. Add the security researcher to the [Security Researcher Acknowledgments list](http://about.gitlab.com/vulnerability-acknowledgements/)
......
# Sidekiq debugging
## Log arguments to Sidekiq jobs
If you want to see what arguments are being passed to Sidekiq jobs you can set
the SIDEKIQ_LOG_ARGUMENTS environment variable.
```
SIDEKIQ_LOG_ARGUMENTS=1 bundle exec foreman start
```
It is not recommend to enable this setting in production because some Sidekiq
jobs (such as sending a password reset email) take secret arguments (for
example the password reset token).
......@@ -35,8 +35,6 @@ sudo -u git -H git checkout 7-4-stable-ee
### 3. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
# MySQL installations (note: the line below states '--without ... postgres')
sudo -u git -H bundle install --without development test postgres --deployment
......@@ -167,6 +165,10 @@ mysql> \q
# Set production -> password: the password your replaced $password with earlier
sudo -u git -H editor /home/git/gitlab/config/database.yml
# Start GitLab
sudo service gitlab start
sudo service nginx restart
# Run thorough check
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
```
......
......@@ -32,7 +32,15 @@ For GitLab Enterprise Edition:
sudo -u git -H git checkout 7-5-stable-ee
```
### 3. Install libs, migrations, etc.
### 3. Update gitlab-shell
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
sudo -u git -H git checkout v2.2.0
```
### 4. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
......@@ -53,7 +61,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
```
### 4. Update config files
### 5. Update config files
#### New configuration options for gitlab.yml
......@@ -79,12 +87,12 @@ sudo -u git -H editor config/unicorn.rb
* Add `collation: utf8_general_ci` to config/database.yml as seen in [config/database.yml.mysql](config/database.yml.mysql)
### 5. Start application
### 6. Start application
sudo service gitlab start
sudo service nginx restart
### 6. Check application status
### 7. Check application status
Check if GitLab and its environment are configured correctly:
......@@ -97,7 +105,7 @@ To make sure you didn't miss anything run a more thorough check with:
If all items are green, then congratulations upgrade is complete!
### 7. Optional optimizations for GitLab setups with MySQL databases
### 8. Optional optimizations for GitLab setups with MySQL databases
Only applies if running MySQL database created with GitLab 6.7 or earlier. If you are not experiencing any issues you may not need the following instructions however following them will bring your database in line with the latest recommended installation configuration and help avoid future issues. Be sure to follow these directions exactly. These directions should be safe for any MySQL instance but to be sure make a current MySQL database backup beforehand.
......@@ -185,7 +193,3 @@ cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
# From 7.5 to 7.6
**7.6 is not yet released. This is a preliminary upgrade guide.**
### 0. Stop server
sudo service gitlab stop
### 1. Backup
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
### 2. Get latest code
```bash
sudo -u git -H git fetch --all
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
```
For GitLab Community Edition:
```bash
sudo -u git -H git checkout 7-6-stable
```
OR
For GitLab Enterprise Edition:
```bash
sudo -u git -H git checkout 7-6-stable-ee
```
### 3. Update gitlab-shell
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
sudo -u git -H git checkout v2.2.0
```
### 4. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
# MySQL installations (note: the line below states '--without ... postgres')
sudo -u git -H bundle install --without development test postgres --deployment
# PostgreSQL installations (note: the line below states '--without ... mysql')
sudo -u git -H bundle install --without development test mysql --deployment
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
# Clean up assets and cache
sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
# Update init.d script
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
```
### 5. Update config files
#### New configuration options for `gitlab.yml`
There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
```
git diff origin/7-5-stable:config/gitlab.yml.example origin/7-6-stable:config/gitlab.yml.example
```
#### Change Nginx settings
* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings
* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your setting
#### Setup time zone (optional)
Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`](config/application.rb) (unlikely), unset it.
### 6. Start application
sudo service gitlab start
sudo service nginx restart
### 7. Check application status
Check if GitLab and its environment are configured correctly:
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
To make sure you didn't miss anything run a more thorough check with:
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
If all items are green, then congratulations upgrade is complete!
## Things went south? Revert to previous version (7.5)
### 1. Revert the code to the previous version
Follow the [upgrade guide from 7.4 to 7.5](7.4-to-7.5.md), except for the database migration
(The backup is already migrated to the previous version)
### 2. Restore from the backup:
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
......@@ -10,6 +10,8 @@ If you have local changes to your GitLab repository the script will stash them a
**GitLab Upgrader is available only for GitLab version 6.4.2 or higher.**
**This script does NOT update gitlab-shell, it needs manual update. See step 5 below.**
## 0. Backup
cd /home/git/gitlab
......
FROM ubuntu:14.04
# Install required packages
RUN apt-get update -q \
&& DEBIAN_FRONTEND=noninteractive apt-get install -qy \
openssh-server \
wget \
&& apt-get clean
# Download & Install GitLab
# 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.5.2-omnibus.5.2.1.ci-1_amd64.deb \
&& dpkg -i $TMP_FILE \
&& rm -f $TMP_FILE
# Manage SSHD through runit
RUN mkdir -p /opt/gitlab/sv/sshd/supervise \
&& mkfifo /opt/gitlab/sv/sshd/supervise/ok \
&& printf "#!/bin/sh\nexec 2>&1\numask 077\nexec /usr/sbin/sshd -D" > /opt/gitlab/sv/sshd/run \
&& chmod a+x /opt/gitlab/sv/sshd/run \
&& ln -s /opt/gitlab/sv/sshd /opt/gitlab/service \
&& mkdir -p /var/run/sshd
# Expose web & ssh
EXPOSE 80 22
# Volume & configuration
VOLUME ["/var/opt/gitlab", "/var/log/gitlab", "/etc/gitlab"]
ADD gitlab.rb /etc/gitlab/
# Default is to run runit & reconfigure
CMD gitlab-ctl reconfigure & /opt/gitlab/embedded/bin/runsvdir-start
What is GitLab?
===============
GitLab offers git repository management, code reviews, issue tracking, activity feeds, wikis. It has LDAP/AD integration, handles 25,000 users on a single server but can also run on a highly available active/active cluster. A subscription gives you access to our support team and to GitLab Enterprise Edition that contains extra features aimed at larger organizations.
<https://about.gitlab.com>
![GitLab Logo](https://gitlab.com/uploads/appearance/logo/1/brand_logo-c37eb221b456bb4b472cc1084480991f.png)
How to use this image
======================
At this moment GitLab doesn't have official Docker images.
Build your own based on the Omnibus packages with the following command (it assumes you're in the GitLab repo root directory):
```bash
sudo docker build --tag gitlab_image docker/
```
We assume using a data volume container, this will simplify migrations and backups.
This empty container will exist to persist as volumes the 3 directories used by GitLab, so remember not to delete it.
The directories on data container are:
- `/var/opt/gitlab` for application data
- `/var/log/gitlab` for logs
- `/etc/gitlab` for configuration
Create the data container with:
```bash
sudo docker run --name gitlab_data gitlab_image /bin/true
```
After creating this run GitLab:
```bash
sudo docker run --detach --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image
```
It might take a while before the docker container is responding to queries. You can follow the configuration process with `docker logs -f gitlab`.
You can then go to `http://localhost:8080/` (or `http://192.168.59.103:8080/` if you use boot2docker).
You can login with username `root` and password `5iveL!fe`.
Next time, you can just use `sudo docker start gitlab_app` and `sudo docker stop gitlab_app`.
How to configure GitLab
========================
This container uses the official Omnibus GitLab distribution, so all configuration is done in the unique configuration file `/etc/gitlab/gitlab.rb`.
To access GitLab configuration, you can start an interactive command line in a new container using the shared data volume container, you will be able to browse the 3 directories and use your favorite text editor:
```bash
docker run -ti -e TERM=linux --rm --volumes-from gitlab_data ubuntu
vi /etc/gitlab/gitlab.rb
```
**Note** that GitLab will reconfigure itself **at each container start.** You will need to restart the container to reconfigure your GitLab.
You can find all available options in [Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration).
Troubleshooting
=========================
Please see the [troubleshooting](troubleshooting.md) file in this directory.
# External URL should be your Docker instance.
# By default, this example is the "standard" boot2docker IP.
# Always use port 80 here to force the internal nginx to bind port 80,
# even if you intend to use another port in Docker.
external_url "http://192.168.59.103/"
# Prevent Postgres from trying to allocate 25% of total memory
postgresql['shared_buffers'] = '1MB'
# Configure GitLab to redirect PostgreSQL logs to the data volume
postgresql['log_directory'] = '/var/log/gitlab/postgresql'
# Some configuration of GitLab
# You can find more at https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration
gitlab_rails['gitlab_email_from'] = 'gitlab@example.com'
gitlab_rails['gitlab_support_email'] = 'support@example.com'
gitlab_rails['time_zone'] = 'Europe/Paris'
# SMTP settings
# You must use an external server, the Docker container does not install an SMTP server
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.example.com"
gitlab_rails['smtp_port'] = 587
gitlab_rails['smtp_user_name'] = "user"
gitlab_rails['smtp_password'] = "password"
gitlab_rails['smtp_domain'] = "example.com"
gitlab_rails['smtp_authentication'] = "plain"
gitlab_rails['smtp_enable_starttls_auto'] = true
# Enable LDAP authentication
# gitlab_rails['ldap_enabled'] = true
# gitlab_rails['ldap_host'] = 'ldap.example.com'
# gitlab_rails['ldap_port'] = 389
# gitlab_rails['ldap_method'] = 'plain' # 'ssl' or 'plain'
# gitlab_rails['ldap_allow_username_or_email_login'] = false
# gitlab_rails['ldap_uid'] = 'uid'
# gitlab_rails['ldap_base'] = 'ou=users,dc=example,dc=com'
# Troubleshooting
This is to troubleshoot https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/245
But it might contain useful commands for other cases as well.
The configuration to add the postgres log in vim is:
postgresql['log_directory'] = '/var/log/gitlab/postgresql'
# Commands
```bash
sudo docker build --tag gitlab_image docker/
sudo docker rm -f gitlab_app
sudo docker rm -f gitlab_data
sudo docker run --name gitlab_data gitlab_image /bin/true
sudo docker run -ti --rm --volumes-from gitlab_data ubuntu apt-get update && sudo apt-get install -y vim && sudo vim /etc/gitlab/gitlab.rb
sudo docker run --detach --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image
sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/reconfigure.log
sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/postgresql/current
sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers
sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /etc/gitlab/gitlab.rb
```
# Interactively
```bash
# First start a GitLab container without starting GitLab
# This is almost the same as starting the GitLab container except:
# - we run interactively (-t -i)
# - we define TERM=linux because it allows to use arrow keys in vi (!!!)
# - we choose another startup command (bash)
sudo docker run -ti -e TERM=linux --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image bash
# Configure GitLab to redirect PostgreSQL logs
echo "postgresql['log_directory'] = '/var/log/gitlab/postgresql'" >> /etc/gitlab/gitlab.rb
# Prevent Postgres from allocating 25% of total memory
echo "postgresql['shared_buffers'] = '1MB'" >> /etc/gitlab/gitlab.rb
# You can now start GitLab manually from Bash (in the background)
# Maybe the command below is still missing something to run in the background
gitlab-ctl reconfigure > /var/log/gitlab/reconfigure.log & /opt/gitlab/embedded/bin/runsvdir-start &
# Inspect PostgreSQL config
cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers
# And tail the logs (PostgreSQL log may not exist immediately)
tail -f /var/log/gitlab/reconfigure.log /var/log/gitlab/postgresql/current
# And get the memory
cat /proc/meminfo
head /proc/sys/kernel/shmmax /proc/sys/kernel/shmall
free -m
```
......@@ -110,7 +110,7 @@ Feature: Project Active Tab
Scenario: On Project Issues/Browse
Given I visit my project's issues page
Then the active sub tab should be Browse Issues
Then the active sub tab should be Issues
And no other sub tabs should be active
And the active main tab should be Issues
......
......@@ -6,9 +6,11 @@ Feature: Project Fork
Scenario: User fork a project
Given I click link "Fork"
When I fork to my namespace
Then I should see the forked project page
Scenario: User already has forked the project
Given I already have a project named "Shop" in my namespace
And I click link "Fork"
When I fork to my namespace
Then I should see a "Name has already been taken" warning
......@@ -19,6 +19,12 @@ Feature: Project Services
And I fill hipchat settings
Then I should see hipchat service settings saved
Scenario: Activate hipchat service with custom server
When I visit project "Shop" services page
And I click hipchat service link
And I fill hipchat settings with custom server
Then I should see hipchat service settings with custom server saved
Scenario: Activate pivotaltracker service
When I visit project "Shop" services page
And I click pivotaltracker service link
......
......@@ -170,7 +170,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step "I am not an ldap user" do
current_user.update_attributes(extern_uid: nil, provider: '')
current_user.identities.delete
current_user.ldap_user?.should be_false
end
......
......@@ -89,8 +89,8 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
click_link('Labels')
end
step 'the active sub tab should be Browse Issues' do
ensure_active_sub_tab('Browse Issues')
step 'the active sub tab should be Issues' do
ensure_active_sub_tab('Issues')
end
step 'the active sub tab should be Milestones' do
......
......@@ -25,4 +25,10 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
step 'I should see a "Name has already been taken" warning' do
page.should have_content "Name has already been taken"
end
step 'I fork to my namespace' do
within '.fork-namespaces' do
click_link current_user.name
end
end
end
......@@ -8,7 +8,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
milestone = @project.milestones.find_by(title: "v2.2")
page.should have_content(milestone.title[0..10])
page.should have_content(milestone.expires_at)
page.should have_content("Browse Issues")
page.should have_content("Issues")
end
step 'I click link "v2.2"' do
......@@ -28,7 +28,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
milestone = @project.milestones.find_by(title: "v2.3")
page.should have_content(milestone.title[0..10])
page.should have_content(milestone.expires_at)
page.should have_content("Browse Issues")
page.should have_content("Issues")
end
step 'project "Shop" has milestone "v2.2"' do
......
......@@ -10,7 +10,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
step 'I should see list of available services' do
page.should have_content 'Project services'
page.should have_content 'Campfire'
page.should have_content 'Hipchat'
page.should have_content 'HipChat'
page.should have_content 'GitLab CI'
page.should have_content 'Assembla'
page.should have_content 'Pushover'
......@@ -33,7 +33,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
end
step 'I click hipchat service link' do
click_link 'Hipchat'
click_link 'HipChat'
end
step 'I fill hipchat settings' do
......@@ -47,6 +47,17 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
find_field('Room').value.should == 'gitlab'
end
step 'I fill hipchat settings with custom server' do
check 'Active'
fill_in 'Room', with: 'gitlab_custom'
fill_in 'Token', with: 'secretCustom'
fill_in 'Server', with: 'https://chat.example.com'
click_button 'Save'
end
step 'I should see hipchat service settings with custom server saved' do
find_field('Server').value.should == 'https://chat.example.com'
end
step 'I click pivotaltracker service link' do
click_link 'PivotalTracker'
......
......@@ -131,7 +131,7 @@ module SharedProject
end
step 'public empty project "Empty Public Project"' do
create :empty_project, :public, name: "Empty Public Project"
create :project_empty_repo, :public, name: "Empty Public Project"
end
step 'project "Community" has comments' do
......
......@@ -14,10 +14,14 @@ module API
expose :bio, :skype, :linkedin, :twitter, :website_url
end
class Identity < Grape::Entity
expose :provider, :extern_uid
end
class UserFull < User
expose :email
expose :theme_id, :color_scheme_id, :extern_uid, :provider, \
:projects_limit
expose :theme_id, :color_scheme_id, :projects_limit
expose :identities, using: Entities::Identity
expose :can_create_group?, as: :can_create_group
expose :can_create_project?, as: :can_create_project
end
......
......@@ -33,15 +33,20 @@ module API
end
project = Project.find_with_namespace(project_path)
return false unless project
unless project
return Gitlab::GitAccessStatus.new(false, 'No such project')
end
actor = if params[:key_id]
Key.find(params[:key_id])
Key.find_by(id: params[:key_id])
elsif params[:user_id]
User.find(params[:user_id])
User.find_by(id: params[:user_id])
end
return false unless actor
unless actor
return Gitlab::GitAccessStatus.new(false, 'No such user or key')
end
access.check(
actor,
......
......@@ -62,10 +62,16 @@ module API
post do
authenticated_as_admin!
required_attributes! [:email, :password, :name, :username]
attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin]
attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :can_create_group, :admin]
user = User.build_user(attrs)
admin = attrs.delete(:admin)
user.admin = admin unless admin.nil?
identity_attrs = attributes_for_keys [:provider, :extern_uid]
if identity_attrs.any?
user.identities.build(identity_attrs)
end
if user.save
present user, with: Entities::UserFull
else
......@@ -92,8 +98,6 @@ module API
# twitter - Twitter account
# website_url - Website url
# projects_limit - Limit projects each user can create
# extern_uid - External authentication provider UID
# provider - External provider
# bio - Bio
# admin - User is admin - true or false (default)
# can_create_group - User can create groups - true or false
......@@ -102,7 +106,7 @@ module API
put ":id" do
authenticated_as_admin!
attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin]
attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :bio, :can_create_group, :admin]
user = User.find(params[:id])
not_found!('User') unless user
......
......@@ -13,10 +13,10 @@ module Backup
def dump
success = case config["adapter"]
when /^mysql/ then
print "Dumping MySQL database #{config['database']} ... "
$progress.print "Dumping MySQL database #{config['database']} ... "
system('mysqldump', *mysql_args, config['database'], out: db_file_name)
when "postgresql" then
print "Dumping PostgreSQL database #{config['database']} ... "
$progress.print "Dumping PostgreSQL database #{config['database']} ... "
pg_env
system('pg_dump', config['database'], out: db_file_name)
end
......@@ -27,10 +27,10 @@ module Backup
def restore
success = case config["adapter"]
when /^mysql/ then
print "Restoring MySQL database #{config['database']} ... "
$progress.print "Restoring MySQL database #{config['database']} ... "
system('mysql', *mysql_args, config['database'], in: db_file_name)
when "postgresql" then
print "Restoring PostgreSQL database #{config['database']} ... "
$progress.print "Restoring PostgreSQL database #{config['database']} ... "
# Drop all tables because PostgreSQL DB dumps do not contain DROP TABLE
# statements like MySQL.
Rake::Task["gitlab:db:drop_all_tables"].invoke
......@@ -69,9 +69,9 @@ module Backup
def report_success(success)
if success
puts '[DONE]'.green
$progress.puts '[DONE]'.green
else
puts '[FAILED]'.red
$progress.puts '[FAILED]'.red
end
end
end
......
......@@ -18,11 +18,11 @@ module Backup
end
# create archive
print "Creating backup archive: #{tar_file} ... "
$progress.print "Creating backup archive: #{tar_file} ... "
if Kernel.system('tar', '-cf', tar_file, *BACKUP_CONTENTS)
puts "done".green
$progress.puts "done".green
else
puts "failed".red
puts "creating archive #{tar_file} failed".red
abort 'Backup failed'
end
......@@ -31,37 +31,37 @@ module Backup
def upload(tar_file)
remote_directory = Gitlab.config.backup.upload.remote_directory
print "Uploading backup archive to remote storage #{remote_directory} ... "
$progress.print "Uploading backup archive to remote storage #{remote_directory} ... "
connection_settings = Gitlab.config.backup.upload.connection
if connection_settings.blank?
puts "skipped".yellow
$progress.puts "skipped".yellow
return
end
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)
puts "done".green
$progress.puts "done".green
else
puts "failed".red
puts "uploading backup to #{remote_directory} failed".red
abort 'Backup failed'
end
end
def cleanup
print "Deleting tmp directories ... "
$progress.print "Deleting tmp directories ... "
if Kernel.system('rm', '-rf', *BACKUP_CONTENTS)
puts "done".green
$progress.puts "done".green
else
puts "failed".red
puts "deleting tmp directory failed".red
abort 'Backup failed'
end
end
def remove_old
# delete backups
print "Deleting old backups ... "
$progress.print "Deleting old backups ... "
keep_time = Gitlab.config.backup.keep_time.to_i
path = Gitlab.config.backup.path
......@@ -76,9 +76,9 @@ module Backup
end
end
end
puts "done. (#{removed} removed)".green
$progress.puts "done. (#{removed} removed)".green
else
puts "skipping".yellow
$progress.puts "skipping".yellow
end
end
......@@ -101,12 +101,12 @@ module Backup
exit 1
end
print "Unpacking backup ... "
$progress.print "Unpacking backup ... "
unless Kernel.system(*%W(tar -xf #{tar_file}))
puts "failed".red
puts "unpacking backup failed".red
exit 1
else
puts "done".green
$progress.puts "done".green
end
settings = YAML.load_file("backup_information.yml")
......
......@@ -8,19 +8,21 @@ module Backup
prepare
Project.find_each(batch_size: 1000) do |project|
print " * #{project.path_with_namespace} ... "
$progress.print " * #{project.path_with_namespace} ... "
# Create namespace dir if missing
FileUtils.mkdir_p(File.join(backup_repos_path, project.namespace.path)) if project.namespace
if project.empty_repo?
puts "[SKIPPED]".cyan
$progress.puts "[SKIPPED]".cyan
else
output, status = Gitlab::Popen.popen(%W(git --git-dir=#{path_to_repo(project)} bundle create #{path_to_bundle(project)} --all))
cmd = %W(git --git-dir=#{path_to_repo(project)} bundle create #{path_to_bundle(project)} --all)
output, status = Gitlab::Popen.popen(cmd)
if status.zero?
puts "[DONE]".green
$progress.puts "[DONE]".green
else
puts "[FAILED]".red
puts "failed: #{cmd.join(' ')}"
puts output
abort 'Backup failed'
end
......@@ -29,15 +31,17 @@ module Backup
wiki = ProjectWiki.new(project)
if File.exists?(path_to_repo(wiki))
print " * #{wiki.path_with_namespace} ... "
$progress.print " * #{wiki.path_with_namespace} ... "
if wiki.repository.empty?
puts " [SKIPPED]".cyan
$progress.puts " [SKIPPED]".cyan
else
output, status = Gitlab::Popen.popen(%W(git --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all))
cmd = %W(git --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all)
output, status = Gitlab::Popen.popen(cmd)
if status.zero?
puts " [DONE]".green
$progress.puts " [DONE]".green
else
puts " [FAILED]".red
puts "failed: #{cmd.join(' ')}"
abort 'Backup failed'
end
end
......@@ -55,7 +59,7 @@ module Backup
FileUtils.mkdir_p(repos_path)
Project.find_each(batch_size: 1000) do |project|
print "#{project.path_with_namespace} ... "
$progress.print " * #{project.path_with_namespace} ... "
project.namespace.ensure_dir_exist if project.namespace
......@@ -66,30 +70,41 @@ module Backup
end
if system(*cmd, silent)
puts "[DONE]".green
$progress.puts "[DONE]".green
else
puts "[FAILED]".red
puts "failed: #{cmd.join(' ')}"
abort 'Restore failed'
end
wiki = ProjectWiki.new(project)
if File.exists?(path_to_bundle(wiki))
print " * #{wiki.path_with_namespace} ... "
if system(*%W(git clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)}), silent)
puts " [DONE]".green
$progress.print " * #{wiki.path_with_namespace} ... "
# If a wiki bundle exists, first remove the empty repo
# that was initialized with ProjectWiki.new() and then
# try to restore with 'git clone --bare'.
FileUtils.rm_rf(path_to_repo(wiki))
cmd = %W(git clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)})
if system(*cmd, silent)
$progress.puts " [DONE]".green
else
puts " [FAILED]".red
puts "failed: #{cmd.join(' ')}"
abort 'Restore failed'
end
end
end
print 'Put GitLab hooks in repositories dirs'.yellow
if system("#{Gitlab.config.gitlab_shell.path}/bin/create-hooks")
puts " [DONE]".green
$progress.print 'Put GitLab hooks in repositories dirs'.yellow
cmd = "#{Gitlab.config.gitlab_shell.path}/bin/create-hooks"
if system(cmd)
$progress.puts " [DONE]".green
else
puts " [FAILED]".red
puts "failed: #{cmd}"
end
end
......
module Gitlab
class ForcePushCheck
def self.force_push?(project, oldrev, newrev)
return false if project.empty_repo?
if oldrev != Gitlab::Git::BLANK_SHA && newrev != Gitlab::Git::BLANK_SHA
missed_refs = IO.popen(%W(git --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev})).read
missed_refs.split("\n").size > 0
else
false
end
end
end
end
......@@ -8,15 +8,7 @@ module Gitlab
def check(actor, cmd, project, changes = nil)
case cmd
when *DOWNLOAD_COMMANDS
if actor.is_a? User
download_access_check(actor, project)
elsif actor.is_a? DeployKey
actor.projects.include?(project)
elsif actor.is_a? Key
download_access_check(actor.user, project)
else
raise 'Wrong actor'
end
download_access_check(actor, project)
when *PUSH_COMMANDS
if actor.is_a? User
push_access_check(actor, project, changes)
......@@ -32,7 +24,23 @@ module Gitlab
end
end
def download_access_check(user, project)
def download_access_check(actor, project)
if actor.is_a?(User)
user_download_access_check(actor, project)
elsif actor.is_a?(DeployKey)
if actor.projects.include?(project)
build_status_object(true)
else
build_status_object(false, "Deploy key not allowed to access this project")
end
elsif actor.is_a? Key
user_download_access_check(actor.user, project)
else
raise 'Wrong actor'
end
end
def user_download_access_check(user, project)
if user && user_allowed?(user) && user.can?(:download_code, project)
build_status_object(true)
else
......@@ -78,21 +86,20 @@ module Gitlab
:push_code
end
# Stop execution if user has no access to this project
unless user.can?(action, project)
return build_status_object(false, "You don't have permission")
end
# Return build_status_object(true) if all git hook checks passed successfully
# or build_status_object(false) if any hook fails
pass_git_hooks?(user, project, ref, oldrev, newrev)
end
def forced_push?(project, oldrev, newrev)
return false if project.empty_repo?
if oldrev != Gitlab::Git::BLANK_SHA && newrev != Gitlab::Git::BLANK_SHA
missed_refs = IO.popen(%W(git --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev})).read
missed_refs.split("\n").size > 0
else
false
end
Gitlab::ForcePushCheck.force_push?(project, oldrev, newrev)
end
def pass_git_hooks?(user, project, ref, oldrev, newrev)
......@@ -182,6 +189,5 @@ module Gitlab
def build_status_object(status, message = '')
GitAccessStatus.new(status, message)
end
end
end
......@@ -9,7 +9,7 @@ module Gitlab
attr_reader :adapter, :provider, :user, :ldap_user
def self.open(user, &block)
Gitlab::LDAP::Adapter.open(user.provider) do |adapter|
Gitlab::LDAP::Adapter.open(user.ldap_identity.provider) do |adapter|
block.call(self.new(user, adapter))
end
end
......@@ -31,13 +31,13 @@ module Gitlab
def initialize(user, adapter=nil)
@adapter = adapter
@user = user
@provider = user.provider
@provider = user.ldap_identity.provider
end
def allowed?
if Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter)
if Gitlab::LDAP::Person.find_by_dn(user.ldap_identity.extern_uid, adapter)
return true unless ldap_config.active_directory
!Gitlab::LDAP::Person.disabled_via_active_directory?(user.extern_uid, adapter)
!Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter)
else
false
end
......@@ -54,7 +54,7 @@ module Gitlab
end
def ldap_user
@ldap_user ||= Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter)
@ldap_user ||= Gitlab::LDAP::Person.find_by_dn(user.ldap_identity.extern_uid, adapter)
end
def update_permissions
......@@ -110,7 +110,7 @@ module Gitlab
def update_admin_status
admin_group = Gitlab::LDAP::Group.find_by_cn(ldap_config.admin_group, adapter)
if admin_group.has_member?(Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter))
if admin_group.has_member?(Gitlab::LDAP::Person.find_by_dn(user.ldap_identity.extern_uid, adapter))
unless user.admin?
user.admin = true
user.save
......
......@@ -12,9 +12,10 @@ module Gitlab
class << self
def find_by_uid_and_provider(uid, provider)
# LDAP distinguished name is case-insensitive
::User.
identity = ::Identity.
where(provider: [provider, :ldap]).
where('lower(extern_uid) = ?', uid.downcase).last
identity && identity.user
end
end
......@@ -34,15 +35,13 @@ module Gitlab
end
def find_by_email
model.find_by(email: auth_hash.email)
::User.find_by(email: auth_hash.email)
end
def update_user_attributes
gl_user.attributes = {
extern_uid: auth_hash.uid,
provider: auth_hash.provider,
email: auth_hash.email
}
gl_user.email = auth_hash.email
gl_user.identities.build(provider: auth_hash.provider, extern_uid: auth_hash.uid)
gl_user
end
def changed?
......
......@@ -5,6 +5,8 @@
#
module Gitlab
module OAuth
class ForbiddenAction < StandardError; end
class User
attr_accessor :auth_hash, :gl_user
......@@ -70,24 +72,24 @@ module Gitlab
end
def find_by_uid_and_provider
model.where(provider: auth_hash.provider, extern_uid: auth_hash.uid).last
identity = Identity.find_by(provider: auth_hash.provider, extern_uid: auth_hash.uid)
identity && identity.user
end
def build_new_user
model.new(user_attributes).tap do |user|
user.skip_confirmation!
end
user = ::User.new(user_attributes)
user.skip_confirmation!
user.identities.new(extern_uid: auth_hash.uid, provider: auth_hash.provider)
user
end
def user_attributes
{
extern_uid: auth_hash.uid,
provider: auth_hash.provider,
name: auth_hash.name,
username: auth_hash.username,
email: auth_hash.email,
password: auth_hash.password,
password_confirmation: auth_hash.password,
password_confirmation: auth_hash.password
}
end
......@@ -95,12 +97,8 @@ module Gitlab
Gitlab::AppLogger
end
def model
::User
end
def raise_unauthorized_to_create
raise StandardError.new("Unauthorized to create user, signup disabled for #{auth_hash.provider}")
def unauthorized_to_create
raise ForbiddenAction.new("Unauthorized to create user, signup disabled for #{auth_hash.provider}")
end
end
end
......
module Gitlab
module SidekiqMiddleware
class MemoryKiller
# Wait 30 seconds for running jobs to finish during graceful shutdown
GRACEFUL_SHUTDOWN_WAIT = 30
def call(worker, job, queue)
yield
current_rss = get_rss
return unless max_rss > 0 && current_rss > max_rss
Sidekiq.logger.warn "current RSS #{current_rss} exceeds maximum RSS "\
"#{max_rss}"
Sidekiq.logger.warn "sending SIGUSR1 to PID #{Process.pid}"
# SIGUSR1 tells Sidekiq to stop accepting new jobs
Process.kill('SIGUSR1', Process.pid)
Sidekiq.logger.warn "spawning thread that will send SIGTERM to PID "\
"#{Process.pid} in #{graceful_shutdown_wait} seconds"
# Send the final shutdown signal to Sidekiq from a separate thread so
# that the current job can finish
Thread.new do
sleep(graceful_shutdown_wait)
Process.kill('SIGTERM', Process.pid)
end
end
private
def get_rss
output, status = Gitlab::Popen.popen(%W(ps -o rss= -p #{Process.pid}))
return 0 unless status.zero?
output.to_i
end
def max_rss
@max_rss ||= ENV['SIDEKIQ_MAX_RSS'].to_s.to_i
end
def graceful_shutdown_wait
@graceful_shutdown_wait ||= (
ENV['SIDEKIQ_GRACEFUL_SHUTDOWN_WAIT'] || GRACEFUL_SHUTDOWN_WAIT
).to_i
end
end
end
end
......@@ -6,6 +6,7 @@ namespace :gitlab do
desc "GITLAB | Create a backup of the GitLab system"
task create: :environment do
warn_user_is_not_gitlab
configure_cron_mode
Rake::Task["gitlab:backup:db:create"].invoke
Rake::Task["gitlab:backup:repo:create"].invoke
......@@ -21,6 +22,7 @@ namespace :gitlab do
desc "GITLAB | Restore a previously created backup"
task restore: :environment do
warn_user_is_not_gitlab
configure_cron_mode
backup = Backup::Manager.new
backup.unpack
......@@ -35,43 +37,54 @@ namespace :gitlab do
namespace :repo do
task create: :environment do
puts "Dumping repositories ...".blue
$progress.puts "Dumping repositories ...".blue
Backup::Repository.new.dump
puts "done".green
$progress.puts "done".green
end
task restore: :environment do
puts "Restoring repositories ...".blue
$progress.puts "Restoring repositories ...".blue
Backup::Repository.new.restore
puts "done".green
$progress.puts "done".green
end
end
namespace :db do
task create: :environment do
puts "Dumping database ... ".blue
$progress.puts "Dumping database ... ".blue
Backup::Database.new.dump
puts "done".green
$progress.puts "done".green
end
task restore: :environment do
puts "Restoring database ... ".blue
$progress.puts "Restoring database ... ".blue
Backup::Database.new.restore
puts "done".green
$progress.puts "done".green
end
end
namespace :uploads do
task create: :environment do
puts "Dumping uploads ... ".blue
$progress.puts "Dumping uploads ... ".blue
Backup::Uploads.new.dump
puts "done".green
$progress.puts "done".green
end
task restore: :environment do
puts "Restoring uploads ... ".blue
$progress.puts "Restoring uploads ... ".blue
Backup::Uploads.new.restore
puts "done".green
$progress.puts "done".green
end
end
def configure_cron_mode
if ENV['CRON']
# We need an object we can say 'puts' and 'print' to; let's use a
# StringIO.
require 'stringio'
$progress = StringIO.new
else
$progress = $stdout
end
end
end # namespace end: backup
......
require "#{Rails.root}/app/helpers/emails_helper"
require 'action_view/helpers'
extend ActionView::Helpers
include ActionView::Context
include EmailsHelper
namespace :gitlab do
desc "Email google whitelisting email with example email for actions in inbox"
task mail_google_schema_whitelisting: :environment do
subject = "Rails | Implemented feature"
url = "#{Gitlab.config.gitlab.url}/base/rails-project/issues/#{rand(1..100)}#note_#{rand(10..1000)}"
schema = email_action(url)
body = email_template(schema, url)
mail = Notify.test_email("schema.whitelisting+sample@gmail.com", subject, body.html_safe)
if send_now
mail.deliver
else
puts "WOULD SEND:"
end
puts mail
end
def email_template(schema, url)
"<html lang='en'>
<head>
<meta content='text/html; charset=utf-8' http-equiv='Content-Type'>
<title>
GitLab
</title>
</meta>
</head>
<style>
img {
max-width: 100%;
height: auto;
}
p.details {
font-style:italic;
color:#777
}
.footer p {
font-size:small;
color:#777
}
</style>
<body>
<div class='content'>
<div>
<p>I like it :+1: </p>
</div>
</div>
<div class='footer' style='margin-top: 10px;'>
<p>
<br>
You're receiving this notification because you are a member of the Base / Rails Project project team.
<a href=\"#{url}\">View it on GitLab</a>
#{schema}
</p>
</div>
</body>
</html>"
end
def send_now
if ENV['SEND'] == "true"
true
else
false
end
end
end
......@@ -24,9 +24,18 @@ FactoryGirl.define do
admin true
end
trait :ldap do
provider 'ldapmain'
extern_uid 'my-ldap-id'
factory :omniauth_user do
ignore do
extern_uid '123456'
provider 'ldapmain'
end
after(:create) do |user, evaluator|
user.identities << create(:identity,
provider: evaluator.provider,
extern_uid: evaluator.extern_uid
)
end
end
factory :admin, traits: [:admin]
......@@ -189,4 +198,9 @@ FactoryGirl.define do
provider 'ldapmain'
group
end
factory :identity do
provider 'ldapmain'
extern_uid 'my-ldap-id'
end
end
......@@ -27,6 +27,10 @@
#
FactoryGirl.define do
# Project without repository
#
# Project does not have bare repository.
# Use this factory if you dont need repository in tests
factory :empty_project, class: 'Project' do
sequence(:name) { |n| "project#{n}" }
path { name.downcase.gsub(/\s/, '_') }
......@@ -47,6 +51,20 @@ FactoryGirl.define do
end
end
# Project with empty repository
#
# This is a case when you just created a project
# but not pushed any code there yet
factory :project_empty_repo, parent: :empty_project do
after :create do |project|
project.create_repository
end
end
# Project with test repository
#
# Test repository source can be found at
# https://gitlab.com/gitlab-org/gitlab-test
factory :project, parent: :empty_project do
path { 'gitlabhq' }
......
require 'spec_helper'
describe "User Feed", feature: true do
describe "GET /" do
let!(:user) { create(:user) }
context "user atom feed via private token" do
it "should render user atom feed" do
visit user_path(user, :atom, private_token: user.private_token)
body.should have_selector("feed title")
end
end
context 'feed content' do
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project, author: user, description: '') }
let(:note) { create(:note, noteable: issue, author: user, note: 'Bug confirmed', project: project) }
before do
project.team << [user, :master]
issue_event(issue, user)
note_event(note, user)
visit user_path(user, :atom, private_token: user.private_token)
end
it "should have issue opened event" do
body.should have_content("#{user.name} opened issue ##{issue.iid}")
end
it "should have issue comment event" do
body.should have_content("#{user.name} commented on issue ##{issue.iid}")
end
end
end
def issue_event(issue, user)
EventCreateService.new.open_issue(issue, user)
end
def note_event(note, user)
EventCreateService.new.leave_note(note, user)
end
end
require "spec_helper"
describe OauthHelper do
describe "additional_providers" do
it 'returns all enabled providers' do
allow(helper).to receive(:enabled_oauth_providers) { [:twitter, :github] }
helper.additional_providers.should include(*[:twitter, :github])
end
it 'does not return ldap provider' do
allow(helper).to receive(:enabled_oauth_providers) { [:twitter, :ldapmain] }
helper.additional_providers.should include(:twitter)
end
it 'returns empty array' do
allow(helper).to receive(:enabled_oauth_providers) { [] }
helper.additional_providers.should == []
end
end
end
\ No newline at end of file
......@@ -46,6 +46,25 @@ describe Gitlab::GitAccess do
it { subject.allowed?.should be_false }
end
end
describe 'deploy key permissions' do
let(:key) { create(:deploy_key) }
context 'pull code' do
context 'allowed' do
before { key.projects << project }
subject { access.download_access_check(key, project) }
it { subject.allowed?.should be_true }
end
context 'denied' do
subject { access.download_access_check(key, project) }
it { subject.allowed?.should be_false }
end
end
end
end
describe 'push_access_check' do
......
......@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::LDAP::Access do
let(:access) { Gitlab::LDAP::Access.new user }
let(:user) { create(:user, :ldap) }
let(:user) { create(:omniauth_user) }
describe :allowed? do
subject { access.allowed? }
......@@ -129,7 +129,7 @@ describe Gitlab::LDAP::Access do
let(:entry) { Net::LDAP::Entry.new }
before do
access.stub ldap_user: Gitlab::LDAP::Person.new(entry, user.provider)
access.stub ldap_user: Gitlab::LDAP::Person.new(entry, user.ldap_identity.provider)
end
it "should not update email if email attribute is not set" do
......@@ -157,7 +157,7 @@ describe Gitlab::LDAP::Access do
before do
access.stub(admin_group: "GLAdmins")
ldap_user_entry = Net::LDAP::Entry.new
Gitlab::LDAP::Adapter.any_instance.stub(:user) { Gitlab::LDAP::Person.new(ldap_user_entry, user.provider) }
Gitlab::LDAP::Adapter.any_instance.stub(:user) { Gitlab::LDAP::Person.new(ldap_user_entry, user.ldap_identity.provider) }
Gitlab::LDAP::Person.any_instance.stub(:uid) { 'admin2' }
end
......@@ -319,7 +319,7 @@ objectclass: posixGroup
Gitlab::LDAP::Group.new(ldap_group_response_2)
]
end
let(:ldap_user) { Gitlab::LDAP::Person.new(Net::LDAP::Entry.new, user.provider) }
let(:ldap_user) { Gitlab::LDAP::Person.new(Net::LDAP::Entry.new, user.ldap_identity.provider) }
before do
access.stub(ldap_user: ldap_user)
......
......@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::LDAP::Authentication do
let(:klass) { Gitlab::LDAP::Authentication }
let(:user) { create(:user, :ldap, extern_uid: dn) }
let(:user) { create(:omniauth_user, extern_uid: dn) }
let(:dn) { 'uid=john,ou=people,dc=example,dc=com' }
let(:login) { 'john' }
let(:password) { 'password' }
......
......@@ -15,18 +15,18 @@ describe Gitlab::LDAP::User do
describe :find_or_create do
it "finds the user if already existing" do
existing_user = create(:user, extern_uid: 'my-uid', provider: 'ldapmain')
existing_user = create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain')
expect{ gl_user.save }.to_not change{ User.count }
end
it "connects to existing non-ldap user if the email matches" do
existing_user = create(:user, email: 'john@example.com')
existing_user = create(:omniauth_user, email: 'john@example.com', provider: "twitter")
expect{ gl_user.save }.to_not change{ User.count }
existing_user.reload
expect(existing_user.extern_uid).to eql 'my-uid'
expect(existing_user.provider).to eql 'ldapmain'
expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid'
expect(existing_user.ldap_identity.provider).to eql 'ldapmain'
end
it "creates a new user if not found" do
......
......@@ -15,7 +15,7 @@ describe Gitlab::OAuth::User do
end
describe :persisted? do
let!(:existing_user) { create(:user, extern_uid: 'my-uid', provider: 'my-provider') }
let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') }
it "finds an existing user based on uid and provider (facebook)" do
auth = double(info: double(name: 'John'), uid: 'my-uid', provider: 'my-provider')
......@@ -39,8 +39,9 @@ describe Gitlab::OAuth::User do
oauth_user.save
expect(gl_user).to be_valid
expect(gl_user.extern_uid).to eql uid
expect(gl_user.provider).to eql 'twitter'
identity = gl_user.identities.first
expect(identity.extern_uid).to eql uid
expect(identity.provider).to eql 'twitter'
end
end
......
......@@ -46,7 +46,7 @@ describe Notify do
token = 'kETLwRaayvigPq_x3SNM'
subject { Notify.new_user_email(new_user.id, new_user.password, token) }
subject { Notify.new_user_email(new_user.id, token) }
it_behaves_like 'an email sent from GitLab'
......@@ -83,7 +83,7 @@ describe Notify do
let(:example_site_path) { root_path }
let(:new_user) { create(:user, email: 'newguy@example.com', password: "securePassword") }
subject { Notify.new_user_email(new_user.id, new_user.password) }
subject { Notify.new_user_email(new_user.id) }
it_behaves_like 'an email sent from GitLab'
......
......@@ -62,6 +62,7 @@ describe User do
it { should have_many(:assigned_issues).dependent(:destroy) }
it { should have_many(:merge_requests).dependent(:destroy) }
it { should have_many(:assigned_merge_requests).dependent(:destroy) }
it { should have_many(:identities).dependent(:destroy) }
end
describe "Mass assignment" do
......@@ -361,24 +362,29 @@ describe User do
end
describe :ldap_user? do
let(:user) { build(:user, :ldap) }
it "is true if provider name starts with ldap" do
user.provider = 'ldapmain'
user = create(:omniauth_user, provider: 'ldapmain')
expect( user.ldap_user? ).to be_true
end
it "is false for other providers" do
user.provider = 'other-provider'
user = create(:omniauth_user, provider: 'other-provider')
expect( user.ldap_user? ).to be_false
end
it "is false if no extern_uid is provided" do
user.extern_uid = nil
user = create(:omniauth_user, extern_uid: nil)
expect( user.ldap_user? ).to be_false
end
end
describe :ldap_identity do
it "returns ldap identity" do
user = create :omniauth_user
user.ldap_identity.provider.should_not be_empty
end
end
describe '#full_website_url' do
let(:user) { create(:user) }
......
......@@ -26,7 +26,7 @@ describe API::API, api: true do
end
end
describe "GET /internal/allowed" do
describe "POST /internal/allowed" do
context "access granted" do
before do
project.team << [user, :developer]
......@@ -140,7 +140,7 @@ describe API::API, api: true do
archive(key, project)
response.status.should == 200
response.body.should == 'true'
JSON.parse(response.body)["status"].should be_true
end
end
......@@ -149,10 +149,28 @@ describe API::API, api: true do
archive(key, project)
response.status.should == 200
response.body.should == 'false'
JSON.parse(response.body)["status"].should be_false
end
end
end
context 'project does not exist' do
it do
pull(key, OpenStruct.new(path_with_namespace: 'gitlab/notexists'))
response.status.should == 200
JSON.parse(response.body)["status"].should be_false
end
end
context 'user does not exist' do
it do
pull(OpenStruct.new(id: 0), project)
response.status.should == 200
JSON.parse(response.body)["status"].should be_false
end
end
end
def pull(key, project)
......
......@@ -33,7 +33,7 @@ describe API::API, api: true do
response.status.should == 200
json_response.should be_an Array
json_response.first.keys.should include 'email'
json_response.first.keys.should include 'extern_uid'
json_response.first.keys.should include 'identities'
json_response.first.keys.should include 'can_create_project'
end
end
......
......@@ -55,7 +55,6 @@ end
# projects POST /projects(.:format) projects#create
# new_project GET /projects/new(.:format) projects#new
# fork_project POST /:id/fork(.:format) projects#fork
# files_project GET /:id/files(.:format) projects#files
# edit_project GET /:id/edit(.:format) projects#edit
# project GET /:id(.:format) projects#show
......@@ -70,10 +69,6 @@ describe ProjectsController, "routing" do
get("/projects/new").should route_to('projects#new')
end
it "to #fork" do
post("/gitlab/gitlabhq/fork").should route_to('projects#fork', id: 'gitlab/gitlabhq')
end
it "to #edit" do
get("/gitlab/gitlabhq/edit").should route_to('projects#edit', id: 'gitlab/gitlabhq')
end
......@@ -462,3 +457,13 @@ describe Projects::GraphsController, "routing" do
get("/gitlab/gitlabhq/graphs/master").should route_to('projects/graphs#show', project_id: 'gitlab/gitlabhq', id: 'master')
end
end
describe Projects::ForksController, "routing" do
it "to #new" do
get("/gitlab/gitlabhq/fork/new").should route_to("projects/forks#new", project_id: 'gitlab/gitlabhq')
end
it "to #create" do
post("/gitlab/gitlabhq/fork").should route_to("projects/forks#create", project_id: 'gitlab/gitlabhq')
end
end
......@@ -4,8 +4,8 @@ describe LdapGroupResetService do
# TODO: refactor to multi-ldap setup
let(:group) { create(:group) }
let(:user) { create(:user) }
let(:ldap_user) { create(:user, extern_uid: 'john', provider: 'ldap', last_credential_check_at: Time.now) }
let(:ldap_user_2) { create(:user, extern_uid: 'mike', provider: 'ldap', last_credential_check_at: Time.now) }
let(:ldap_user) { create(:omniauth_user, extern_uid: 'john', provider: 'ldap', last_credential_check_at: Time.now) }
let(:ldap_user_2) { create(:omniauth_user, extern_uid: 'mike', provider: 'ldap', last_credential_check_at: Time.now) }
before do
group.add_owner(user)
......
......@@ -42,10 +42,54 @@ describe Projects::ForkService do
end
end
def fork_project(from_project, user, fork_success = true)
context = Projects::ForkService.new(from_project, user)
shell = double("gitlab_shell")
shell.stub(fork_repository: fork_success)
describe :fork_to_namespace do
before do
@group_owner = create(:user)
@developer = create(:user)
@project = create(:project, creator_id: @group_owner.id,
star_count: 777,
description: 'Wow, such a cool project!')
@group = create(:group)
@group.add_user(@group_owner, GroupMember::OWNER)
@group.add_user(@developer, GroupMember::DEVELOPER)
@opts = { namespace: @group }
end
context 'fork project for group' do
it 'group owner successfully forks project into the group' do
to_project = fork_project(@project, @group_owner, true, @opts)
to_project.owner.should == @group
to_project.namespace.should == @group
to_project.name.should == @project.name
to_project.path.should == @project.path
to_project.description.should == @project.description
to_project.star_count.should be_zero
end
end
context 'fork project for group when user not owner' do
it 'group developer should fail to fork project into the group' do
to_project = fork_project(@project, @developer, true, @opts)
to_project.errors[:namespace].should == ['insufficient access rights']
end
end
context 'project already exists in group' do
it 'should fail due to validation, not transaction failure' do
existing_project = create(:project, name: @project.name,
namespace: @group)
to_project = fork_project(@project, @group_owner, true, @opts)
existing_project.persisted?.should be_true
to_project.errors[:base].should == ['Invalid fork destination']
to_project.errors[:name].should == ['has already been taken']
to_project.errors[:path].should == ['has already been taken']
end
end
end
def fork_project(from_project, user, fork_success = true, params = {})
context = Projects::ForkService.new(from_project, user, params)
shell = double('gitlab_shell').stub(fork_repository: fork_success)
context.stub(gitlab_shell: shell)
context.execute
end
......
require 'spec_helper'
require 'rake'
describe 'gitlab:mail_google_schema_whitelisting rake task' do
before :all do
Rake.application.rake_require "tasks/gitlab/task_helpers"
Rake.application.rake_require "tasks/gitlab/mail_google_schema_whitelisting"
# empty task as env is already loaded
Rake::Task.define_task :environment
end
describe 'call' do
before do
# avoid writing task output to spec progress
$stdout.stub :write
end
let :run_rake_task do
Rake::Task["gitlab:mail_google_schema_whitelisting"].reenable
Rake.application.invoke_task "gitlab:mail_google_schema_whitelisting"
end
it 'should run the task without errors' do
expect { run_rake_task }.to_not raise_error
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment