summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/api.rb1
-rw-r--r--lib/api/entities.rb10
-rw-r--r--lib/api/merge_requests.rb118
-rw-r--r--lib/event_filter.rb68
-rw-r--r--lib/gitlab/auth.rb4
-rw-r--r--lib/gitlab/backend/gitolite_config.rb5
-rw-r--r--lib/gitlab/backend/grack_auth.rb60
-rw-r--r--lib/gitlab/file_editor.rb58
-rw-r--r--lib/gitlab/graph/commit.rb48
-rw-r--r--lib/gitlab/graph/json_builder.rb172
-rw-r--r--lib/gitlab/graph_commit.rb185
-rw-r--r--lib/gitlab/merge.rb65
-rw-r--r--lib/gitlab/satellite.rb41
-rw-r--r--lib/gitlab/satellite/action.rb46
-rw-r--r--lib/gitlab/satellite/edit_file_action.rb57
-rw-r--r--lib/gitlab/satellite/merge_action.rb71
-rw-r--r--lib/gitlab/satellite/satellite.rb92
-rw-r--r--lib/tasks/bulk_import.rake83
-rw-r--r--lib/tasks/gitlab/import.rake54
-rw-r--r--lib/tasks/gitlab/status.rake2
20 files changed, 789 insertions, 451 deletions
diff --git a/lib/api.rb b/lib/api.rb
index 2890a8cc9e8..7a1845443e7 100644
--- a/lib/api.rb
+++ b/lib/api.rb
@@ -18,5 +18,6 @@ module Gitlab
mount Issues
mount Milestones
mount Session
+ mount MergeRequests
end
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index ee693de699e..9e605a607a2 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -63,5 +63,15 @@ module Gitlab
class SSHKey < Grape::Entity
expose :id, :title, :key
end
+
+ class MergeRequest < Grape::Entity
+ expose :id, :target_branch, :source_branch, :project_id, :title, :closed, :merged
+ expose :author, :assignee, using: Entities::UserBasic
+ end
+
+ class Note < Grape::Entity
+ expose :author, using: Entities::UserBasic
+ expose :note
+ end
end
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
new file mode 100644
index 00000000000..d8f2c51293a
--- /dev/null
+++ b/lib/api/merge_requests.rb
@@ -0,0 +1,118 @@
+module Gitlab
+ # MergeRequest API
+ class MergeRequests < Grape::API
+ before { authenticate! }
+
+ resource :projects do
+
+ # List merge requests
+ #
+ # Parameters:
+ # id (required) - The ID or code name of a project
+ #
+ # Example:
+ # GET /projects/:id/merge_requests
+ #
+ get ":id/merge_requests" do
+ authorize! :read_merge_request, user_project
+
+ present paginate(user_project.merge_requests), with: Entities::MergeRequest
+ end
+
+ # Show MR
+ #
+ # Parameters:
+ # id (required) - The ID or code name of a project
+ # merge_request_id (required) - The ID of MR
+ #
+ # Example:
+ # GET /projects/:id/merge_request/:merge_request_id
+ #
+ get ":id/merge_request/:merge_request_id" do
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
+
+ authorize! :read_merge_request, merge_request
+
+ present merge_request, with: Entities::MergeRequest
+ end
+
+ # Create MR
+ #
+ # Parameters:
+ #
+ # id (required) - The ID or code name of a project
+ # source_branch (required) - The source branch
+ # target_branch (required) - The target branch
+ # assignee_id - Assignee user ID
+ # title (required) - Title of MR
+ #
+ # Example:
+ # POST /projects/:id/merge_requests
+ #
+ post ":id/merge_requests" do
+ authorize! :write_merge_request, user_project
+
+ attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title]
+ merge_request = user_project.merge_requests.new(attrs)
+ merge_request.author = current_user
+
+ if merge_request.save
+ merge_request.reload_code
+ present merge_request, with: Entities::MergeRequest
+ else
+ not_found!
+ end
+ end
+
+ # Update MR
+ #
+ # Parameters:
+ # id (required) - The ID or code name of a project
+ # merge_request_id (required) - ID of MR
+ # source_branch - The source branch
+ # target_branch - The target branch
+ # assignee_id - Assignee user ID
+ # title - Title of MR
+ # closed - Status of MR. true - closed
+ # Example:
+ # PUT /projects/:id/merge_request/:merge_request_id
+ #
+ put ":id/merge_request/:merge_request_id" do
+ attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :closed]
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
+
+ authorize! :modify_merge_request, merge_request
+
+ if merge_request.update_attributes attrs
+ merge_request.reload_code
+ merge_request.mark_as_unchecked
+ present merge_request, with: Entities::MergeRequest
+ else
+ not_found!
+ end
+ end
+
+ # Post comment to merge request
+ #
+ # Parameters:
+ # id (required) - The ID or code name of a project
+ # merge_request_id (required) - ID of MR
+ # note (required) - Text of comment
+ # Examples:
+ # POST /projects/:id/merge_request/:merge_request_id/comments
+ #
+ post ":id/merge_request/:merge_request_id/comments" do
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
+ note = merge_request.notes.new(note: params[:note], project_id: user_project.id)
+ note.author = current_user
+
+ if note.save
+ present note, with: Entities::Note
+ else
+ not_found!
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/event_filter.rb b/lib/event_filter.rb
new file mode 100644
index 00000000000..14ab0193bf1
--- /dev/null
+++ b/lib/event_filter.rb
@@ -0,0 +1,68 @@
+class EventFilter
+ attr_accessor :params
+
+ class << self
+ def default_filter
+ %w{ push issues merge_requests team}
+ end
+
+ def push
+ 'push'
+ end
+
+ def merged
+ 'merged'
+ end
+
+ def comments
+ 'comments'
+ end
+
+ def team
+ 'team'
+ end
+ end
+
+ def initialize params
+ @params = if params
+ params.dup
+ else
+ []#EventFilter.default_filter
+ end
+ end
+
+ def apply_filter events
+ return events unless params.present?
+
+ filter = params.dup
+
+ actions = []
+ actions << Event::Pushed if filter.include? 'push'
+ actions << Event::Merged if filter.include? 'merged'
+
+ if filter.include? 'team'
+ actions << Event::Joined
+ actions << Event::Left
+ end
+
+ actions << Event::Commented if filter.include? 'comments'
+
+ events = events.where(action: actions)
+ end
+
+ def options key
+ filter = params.dup
+
+ if filter.include? key
+ filter.delete key
+ else
+ filter << key
+ end
+
+ filter
+ end
+
+ def active? key
+ params.include? key
+ end
+end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 500cb64df48..5a24c5d01b5 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -48,9 +48,13 @@ module Gitlab
def find_or_new_for_omniauth(auth)
provider, uid = auth.provider, auth.uid
+ email = auth.info.email.downcase unless auth.info.email.nil?
if @user = User.find_by_provider_and_extern_uid(provider, uid)
@user
+ elsif @user = User.find_by_email(email)
+ @user.update_attributes(:extern_uid => uid, :provider => provider)
+ @user
else
if Gitlab.config.omniauth['allow_single_sign_on']
@user = create_from_omniauth(auth)
diff --git a/lib/gitlab/backend/gitolite_config.rb b/lib/gitlab/backend/gitolite_config.rb
index d988164d439..1bef19a2a79 100644
--- a/lib/gitlab/backend/gitolite_config.rb
+++ b/lib/gitlab/backend/gitolite_config.rb
@@ -14,7 +14,10 @@ module Gitlab
end
def ga_repo
- @ga_repo ||= ::Gitolite::GitoliteAdmin.new(File.join(config_tmp_dir,'gitolite'))
+ @ga_repo ||= ::Gitolite::GitoliteAdmin.new(
+ File.join(config_tmp_dir,'gitolite'),
+ conf: Gitlab.config.gitolite_config_file
+ )
end
def apply
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index 43a75cc377b..dd5a9becafc 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -1,10 +1,11 @@
module Grack
class Auth < Rack::Auth::Basic
+ attr_accessor :user, :project
def valid?
# Authentication with username and password
email, password = @auth.credentials
- user = User.find_by_email(email)
+ self.user = User.find_by_email(email)
return false unless user.try(:valid_password?, password)
# Set GL_USER env variable
@@ -17,29 +18,44 @@ module Grack
@env['SCRIPT_NAME'] = ""
# Find project by PATH_INFO from env
- if m = /^\/([\w-]+).git/.match(@request.path_info).to_a
- return false unless project = Project.find_by_path(m.last)
+ if m = /^\/([\w\.-]+)\.git/.match(@request.path_info).to_a
+ self.project = Project.find_by_path(m.last)
+ return false unless project
end
# Git upload and receive
if @request.get?
- true
+ validate_get_request
elsif @request.post?
- if @request.path_info.end_with?('git-upload-pack')
- return project.dev_access_for?(user)
- elsif @request.path_info.end_with?('git-receive-pack')
- if project.protected_branches.map(&:name).include?(current_ref)
- project.master_access_for?(user)
- else
- project.dev_access_for?(user)
- end
- else
- false
- end
+ validate_post_request
else
false
end
- end# valid?
+ end
+
+ def validate_get_request
+ true
+ end
+
+ def validate_post_request
+ if @request.path_info.end_with?('git-upload-pack')
+ can?(user, :push_code, project)
+ elsif @request.path_info.end_with?('git-receive-pack')
+ action = if project.protected_branch?(current_ref)
+ :push_code_to_protected_branches
+ else
+ :push_code
+ end
+
+ can?(user, action, project)
+ else
+ false
+ end
+ end
+
+ def can?(object, action, subject)
+ abilities.allowed?(object, action, subject)
+ end
def current_ref
if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/
@@ -49,7 +65,17 @@ module Grack
end
# Need to reset seek point
@request.body.rewind
- /refs\/heads\/([\w-]+)/.match(input).to_a.first
+ /refs\/heads\/([\w\.-]+)/.match(input).to_a.first
+ end
+
+ protected
+
+ def abilities
+ @abilities ||= begin
+ abilities = Six.new
+ abilities << Ability
+ abilities
+ end
end
end# Auth
end# Grack
diff --git a/lib/gitlab/file_editor.rb b/lib/gitlab/file_editor.rb
deleted file mode 100644
index dc3f9480460..00000000000
--- a/lib/gitlab/file_editor.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-module Gitlab
- # GitLab file editor
- #
- # It gives you ability to make changes to files
- # & commit this changes from GitLab UI.
- class FileEditor
- attr_accessor :user, :project, :ref
-
- def initialize(user, project, ref)
- self.user = user
- self.project = project
- self.ref = ref
- end
-
- def update(path, content, commit_message, last_commit)
- return false unless can_edit?(path, last_commit)
-
- Grit::Git.with_timeout(10.seconds) do
- lock_file = Rails.root.join("tmp", "#{project.path}.lock")
-
- File.open(lock_file, "w+") do |f|
- f.flock(File::LOCK_EX)
-
- unless project.satellite.exists?
- raise "Satellite doesn't exist"
- end
-
- project.satellite.clear
-
- Dir.chdir(project.satellite.path) do
- r = Grit::Repo.new('.')
- r.git.sh "git reset --hard"
- r.git.sh "git fetch origin"
- r.git.sh "git config user.name \"#{user.name}\""
- r.git.sh "git config user.email \"#{user.email}\""
- r.git.sh "git checkout -b #{ref} origin/#{ref}"
- File.open(path, 'w'){|f| f.write(content)}
- r.git.sh "git add ."
- r.git.sh "git commit -am '#{commit_message}'"
- output = r.git.sh "git push origin #{ref}"
-
- if output =~ /reject/
- return false
- end
- end
- end
- end
- true
- end
-
- protected
-
- def can_edit?(path, last_commit)
- current_last_commit = @project.last_commit_for(ref, path).sha
- last_commit == current_last_commit
- end
- end
-end
diff --git a/lib/gitlab/graph/commit.rb b/lib/gitlab/graph/commit.rb
new file mode 100644
index 00000000000..e26dd4a6ab4
--- /dev/null
+++ b/lib/gitlab/graph/commit.rb
@@ -0,0 +1,48 @@
+require "grit"
+
+module Gitlab
+ module Graph
+ class Commit
+ include ActionView::Helpers::TagHelper
+
+ attr_accessor :time, :space, :refs
+
+ def initialize(commit)
+ @_commit = commit
+ @time = -1
+ @space = 0
+ end
+
+ def method_missing(m, *args, &block)
+ @_commit.send(m, *args, &block)
+ end
+
+ def to_graph_hash
+ h = {}
+ h[:parents] = self.parents.collect do |p|
+ [p.id,0,0]
+ end
+ h[:author] = Gitlab::Encode.utf8(author.name)
+ h[:time] = time
+ h[:space] = space
+ h[:refs] = refs.collect{|r|r.name}.join(" ") unless refs.nil?
+ h[:id] = sha
+ h[:date] = date
+ h[:message] = escape_once(Gitlab::Encode.utf8(message))
+ h[:login] = author.email
+ h
+ end
+
+ def add_refs(ref_cache, repo)
+ if ref_cache.empty?
+ repo.refs.each do |ref|
+ ref_cache[ref.commit.id] ||= []
+ ref_cache[ref.commit.id] << ref
+ end
+ end
+ @refs = ref_cache[@_commit.id] if ref_cache.include?(@_commit.id)
+ @refs ||= []
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graph/json_builder.rb b/lib/gitlab/graph/json_builder.rb
new file mode 100644
index 00000000000..c2c3fa662a6
--- /dev/null
+++ b/lib/gitlab/graph/json_builder.rb
@@ -0,0 +1,172 @@
+require "grit"
+
+module Gitlab
+ module Graph
+ class JsonBuilder
+ attr_accessor :days, :commits, :ref_cache, :repo
+
+ def self.max_count
+ @max_count ||= 650
+ end
+
+ def initialize project
+ @project = project
+ @repo = project.repo
+ @ref_cache = {}
+
+ @commits = collect_commits
+ @days = index_commits
+ end
+
+ def days_json
+ @days_json = @days.compact.map { |d| [d.day, d.strftime("%b")] }.to_json
+ end
+
+ def commits_json
+ @commits_json = @commits.map(&:to_graph_hash).to_json
+ end
+
+ protected
+
+ # Get commits from repository
+ #
+ def collect_commits
+ @commits = Grit::Commit.find_all(repo, nil, {max_count: self.class.max_count}).dup
+
+ # Decorate with app/models/commit.rb
+ @commits.map! { |commit| ::Commit.new(commit) }
+
+ # Decorate with lib/gitlab/graph/commit.rb
+ @commits.map! { |commit| Gitlab::Graph::Commit.new(commit) }
+
+ # add refs to each commit
+ @commits.each { |commit| commit.add_refs(ref_cache, repo) }
+
+ @commits
+ end
+
+ # Method is adding time and space on the
+ # list of commits. As well as returns date list
+ # corelated with time set on commits.
+ #
+ # @param [Array<Graph::Commit>] comits to index
+ #
+ # @return [Array<TimeDate>] list of commit dates corelated with time on commits
+ def index_commits
+ days, heads = [], []
+ map = {}
+
+ commits.reverse.each_with_index do |c,i|
+ c.time = i
+ days[i] = c.committed_date
+ map[c.id] = c
+ heads += c.refs unless c.refs.nil?
+ end
+
+ heads.select!{|h| h.is_a? Grit::Head or h.is_a? Grit::Remote}
+ # sort heads so the master is top and current branches are closer
+ heads.sort! do |a,b|
+ if a.name == "master"
+ -1
+ elsif b.name == "master"
+ 1
+ else
+ b.commit.committed_date <=> a.commit.committed_date
+ end
+ end
+
+ @_reserved = {}
+ days.each_index do |i|
+ @_reserved[i] = []
+ end
+
+ heads.each do |h|
+ if map.include? h.commit.id then
+ place_chain(map[h.commit.id], map)
+ end
+ end
+
+ days
+ end
+
+ # Add space mark on commit and its parents
+ #
+ # @param [Graph::Commit] the commit object.
+ # @param [Hash<String,Graph::Commit>] map of commits
+ def place_chain(commit, map, parent_time = nil)
+ leaves = take_left_leaves(commit, map)
+ if leaves.empty?
+ return
+ end
+ space = find_free_space(leaves.last.time..leaves.first.time)
+ leaves.each{|l| l.space = space}
+ # and mark it as reserved
+ min_time = leaves.last.time
+ parents = leaves.last.parents.collect
+ parents.each do |p|
+ if map.include? p.id
+ parent = map[p.id]
+ if parent.time < min_time
+ min_time = parent.time
+ end
+ end
+ end
+ if parent_time.nil?
+ max_time = leaves.first.time
+ else
+ max_time = parent_time - 1
+ end
+ mark_reserved(min_time..max_time, space)
+
+ # Visit branching chains
+ leaves.each do |l|
+ parents = l.parents.collect.select{|p| map.include? p.id and map[p.id].space == 0}
+ for p in parents
+ place_chain(map[p.id], map, l.time)
+ end
+ end
+ end
+
+ def mark_reserved(time_range, space)
+ for day in time_range
+ @_reserved[day].push(space)
+ end
+ end
+
+ def find_free_space(time_range)
+ reserved = []
+ for day in time_range
+ reserved += @_reserved[day]
+ end
+ space = 1
+ while reserved.include? space do
+ space += 1
+ end
+ space
+ end
+
+ # Takes most left subtree branch of commits
+ # which don't have space mark yet.
+ #
+ # @param [Graph::Commit] the commit object.
+ # @param [Hash<String,Graph::Commit>] map of commits
+ #
+ # @return [Array<Graph::Commit>] list of branch commits
+ def take_left_leaves(commit, map)
+ leaves = []
+ leaves.push(commit) if commit.space.zero?
+
+ while true
+ parent = commit.parents.collect.select do |p|
+ map.include? p.id and map[p.id].space == 0
+ end
+
+ return leaves if parent.count.zero?
+
+ commit = map[parent.first.id]
+ leaves.push(commit)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graph_commit.rb b/lib/gitlab/graph_commit.rb
deleted file mode 100644
index d3668a99107..00000000000
--- a/lib/gitlab/graph_commit.rb
+++ /dev/null
@@ -1,185 +0,0 @@
-require "grit"
-
-module Gitlab
- class GraphCommit
- attr_accessor :time, :space
- attr_accessor :refs
-
- include ActionView::Helpers::TagHelper
-
- def self.to_graph(project)
- @repo = project.repo
- commits = Grit::Commit.find_all(@repo, nil, {max_count: 650})
-
- ref_cache = {}
-
- commits.map! {|c| GraphCommit.new(Commit.new(c))}
- commits.each { |commit| commit.add_refs(ref_cache, @repo) }
-
- days = GraphCommit.index_commits(commits)
- @days_json = days.compact.collect{|d| [d.day, d.strftime("%b")] }.to_json
- @commits_json = commits.map(&:to_graph_hash).to_json
-
- return @days_json, @commits_json
- end
-
- # Method is adding time and space on the
- # list of commits. As well as returns date list
- # corelated with time set on commits.
- #
- # @param [Array<GraphCommit>] comits to index
- #
- # @return [Array<TimeDate>] list of commit dates corelated with time on commits
- def self.index_commits(commits)
- days, heads = [], []
- map = {}
-
- commits.reverse.each_with_index do |c,i|
- c.time = i
- days[i] = c.committed_date
- map[c.id] = c
- heads += c.refs unless c.refs.nil?
- end
-
- heads.select!{|h| h.is_a? Grit::Head or h.is_a? Grit::Remote}
- # sort heads so the master is top and current branches are closer
- heads.sort! do |a,b|
- if a.name == "master"
- -1
- elsif b.name == "master"
- 1
- else
- b.commit.committed_date <=> a.commit.committed_date
- end
- end
-
- @_reserved = {}
- days.each_index do |i|
- @_reserved[i] = []
- end
-
- heads.each do |h|
- if map.include? h.commit.id then
- place_chain(map[h.commit.id], map)
- end
- end
- days
- end
-
- # Add space mark on commit and its parents
- #
- # @param [GraphCommit] the commit object.
- # @param [Hash<String,GraphCommit>] map of commits
- def self.place_chain(commit, map, parent_time = nil)
- leaves = take_left_leaves(commit, map)
- if leaves.empty? then
- return
- end
- space = find_free_space(leaves.last.time..leaves.first.time)
- leaves.each{|l| l.space = space}
- # and mark it as reserved
- min_time = leaves.last.time
- parents = leaves.last.parents.collect
- parents.each do |p|
- if map.include? p.id then
- parent = map[p.id]
- if parent.time < min_time then
- min_time = parent.time
- end
- end
- end
- if parent_time.nil? then
- max_time = leaves.first.time
- else
- max_time = parent_time - 1
- end
- mark_reserved(min_time..max_time, space)
- # Visit branching chains
- leaves.each do |l|
- parents = l.parents.collect
- .select{|p| map.include? p.id and map[p.id].space == 0}
- for p in parents
- place_chain(map[p.id], map, l.time)
- end
- end
- end
-
- def self.mark_reserved(time_range, space)
- for day in time_range
- @_reserved[day].push(space)
- end
- end
-
- def self.find_free_space(time_range)
- reserved = []
- for day in time_range
- reserved += @_reserved[day]
- end
- space = 1
- while reserved.include? space do
- space += 1
- end
- space
- end
-
- # Takes most left subtree branch of commits
- # which don't have space mark yet.
- #
- # @param [GraphCommit] the commit object.
- # @param [Hash<String,GraphCommit>] map of commits
- #
- # @return [Array<GraphCommit>] list of branch commits
- def self.take_left_leaves(commit, map)
- leaves = []
- leaves.push(commit) if commit.space == 0
- while true
- parent = commit.parents.collect
- .select{|p| map.include? p.id and map[p.id].space == 0}
- if parent.count == 0 then
- return leaves
- else
- commit = map[parent.first.id]
- leaves.push(commit)
- end
- end
- end
-
-
- def initialize(commit)
- @_commit = commit
- @time = -1
- @space = 0
- end
-
- def method_missing(m, *args, &block)
- @_commit.send(m, *args, &block)
- end
-
- def to_graph_hash
- h = {}
- h[:parents] = self.parents.collect do |p|
- [p.id,0,0]
- end
- h[:author] = Gitlab::Encode.utf8(author.name)
- h[:time] = time
- h[:space] = space
- h[:refs] = refs.collect{|r|r.name}.join(" ") unless refs.nil?
- h[:id] = sha
- h[:date] = date
- h[:message] = escape_once(Gitlab::Encode.utf8(message))
- h[:login] = author.email
- h
- end
-
- def add_refs(ref_cache, repo)
- if ref_cache.empty?
- repo.refs.each do |ref|
- ref_cache[ref.commit.id] ||= []
- ref_cache[ref.commit.id] << ref
- end
- end
- @refs = ref_cache[@_commit.id] if ref_cache.include?(@_commit.id)
- @refs ||= []
- end
- end
-end
diff --git a/lib/gitlab/merge.rb b/lib/gitlab/merge.rb
deleted file mode 100644
index 79f86fad37a..00000000000
--- a/lib/gitlab/merge.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-module Gitlab
- class Merge
- attr_accessor :project, :merge_request, :user
-
- def initialize(merge_request, user)
- self.user = user
- self.merge_request = merge_request
- self.project = merge_request.project
- end
-
- def can_be_merged?
- result = false
- process do |repo, output|
- result = !(output =~ /CONFLICT/)
- end
- result
- end
-
- def merge
- process do |repo, output|
- if output =~ /CONFLICT/
- false
- else
- !!repo.git.push({}, "origin", merge_request.target_branch)
- end
- end
- end
-
- def process
- Grit::Git.with_timeout(30.seconds) do
- lock_file = Rails.root.join("tmp", "#{project.path}.lock")
-
- File.open(lock_file, "w+") do |f|
- f.flock(File::LOCK_EX)
-
- unless project.satellite.exists?
- raise "Satellite doesn't exist"
- end
-
- project.satellite.clear
-
- Dir.chdir(project.satellite.path) do
- merge_repo = Grit::Repo.new('.')
- merge_repo.git.sh "git reset --hard"
- merge_repo.git.sh "git fetch origin"
- merge_repo.git.sh "git config user.name \"#{user.name}\""
- merge_repo.git.sh "git config user.email \"#{user.email}\""
- merge_repo.git.sh "git checkout -b #{merge_request.target_branch} origin/#{merge_request.target_branch}"
- output = merge_repo.git.pull({}, "--no-ff", "origin", merge_request.source_branch)
-
- #remove source-branch
- if merge_request.should_remove_source_branch && !project.root_ref?(merge_request.source_branch)
- merge_repo.git.sh "git push origin :#{merge_request.source_branch}"
- end
-
- yield(merge_repo, output)
- end
- end
- end
-
- rescue Grit::Git::GitTimeout
- return false
- end
- end
-end
diff --git a/lib/gitlab/satellite.rb b/lib/gitlab/satellite.rb
deleted file mode 100644
index 9d8dfb8e0a4..00000000000
--- a/lib/gitlab/satellite.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-module Gitlab
- class Satellite
-
- PARKING_BRANCH = "__parking_branch"
-
- attr_accessor :project
-
- def initialize project
- self.project = project
- end
-
- def create
- `git clone #{project.url_to_repo} #{path}`
- end
-
- def path
- Rails.root.join("tmp", "repo_satellites", project.path)
- end
-
- def exists?
- File.exists? path
- end
-
- #will be deleted all branches except PARKING_BRANCH
- def clear
- Dir.chdir(path) do
- heads = Grit::Repo.new(".").heads.map{|head| head.name}
- if heads.include? PARKING_BRANCH
- `git checkout #{PARKING_BRANCH}`
- else
- `git checkout -b #{PARKING_BRANCH}`
- end
- heads.delete(PARKING_BRANCH)
- heads.each do |head|
- `git branch -D #{head}`
- end
- end
- end
-
- end
-end
diff --git a/lib/gitlab/satellite/action.rb b/lib/gitlab/satellite/action.rb
new file mode 100644
index 00000000000..ed2541f3998
--- /dev/null
+++ b/lib/gitlab/satellite/action.rb
@@ -0,0 +1,46 @@
+module Gitlab
+ module Satellite
+ class Action
+ DEFAULT_OPTIONS = { git_timeout: 30.seconds }
+
+ attr_accessor :options, :project, :user
+
+ def initialize(user, project, options = {})
+ @options = DEFAULT_OPTIONS.merge(options)
+ @project = project
+ @user = user
+ end
+
+ protected
+
+ # * Sets a 30s timeout for Git
+ # * Locks the satellite repo
+ # * Yields the prepared satellite repo
+ def in_locked_and_timed_satellite
+ Grit::Git.with_timeout(options[:git_timeout]) do
+ project.satellite.lock do
+ return yield project.satellite.repo
+ end
+ end
+ rescue Errno::ENOMEM => ex
+ Gitlab::GitLogger.error(ex.message)
+ return false
+ rescue Grit::Git::GitTimeout => ex
+ Gitlab::GitLogger.error(ex.message)
+ return false
+ end
+
+ # * Clears the satellite
+ # * Updates the satellite from Gitolite
+ # * Sets up Git variables for the user
+ #
+ # Note: use this within #in_locked_and_timed_satellite
+ def prepare_satellite!(repo)
+ project.satellite.clear_and_update!
+
+ repo.git.config({}, "user.name", user.name)
+ repo.git.config({}, "user.email", user.email)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/satellite/edit_file_action.rb b/lib/gitlab/satellite/edit_file_action.rb
new file mode 100644
index 00000000000..336afc881c2
--- /dev/null
+++ b/lib/gitlab/satellite/edit_file_action.rb
@@ -0,0 +1,57 @@
+module Gitlab
+ module Satellite
+ # GitLab server-side file update and commit
+ class EditFileAction < Action
+ attr_accessor :file_path, :ref
+
+ def initialize(user, project, ref, file_path)
+ super user, project, git_timeout: 10.seconds
+ @file_path = file_path
+ @ref = ref
+ end
+
+ # Updates the files content and creates a new commit for it
+ #
+ # Returns false if the ref has been updated while editing the file
+ # Returns false if commiting the change fails
+ # Returns false if pushing from the satellite to Gitolite failed or was rejected
+ # Returns true otherwise
+ def commit!(content, commit_message, last_commit)
+ return false unless can_edit?(last_commit)
+
+ in_locked_and_timed_satellite do |repo|
+ prepare_satellite!(repo)
+
+ # create target branch in satellite at the corresponding commit from Gitolite
+ repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}")
+
+ # update the file in the satellite's working dir
+ file_path_in_satellite = File.join(repo.working_dir, file_path)
+ File.open(file_path_in_satellite, 'w') { |f| f.write(content) }
+
+ # commit the changes
+ # will raise CommandFailed when commit fails
+ repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
+
+
+ # push commit back to Gitolite
+ # will raise CommandFailed when push fails
+ repo.git.push({raise: true, timeout: true}, :origin, ref)
+
+ # everything worked
+ true
+ end
+ rescue Grit::Git::CommandFailed => ex
+ Gitlab::GitLogger.error(ex.message)
+ false
+ end
+
+ protected
+
+ def can_edit?(last_commit)
+ current_last_commit = @project.last_commit_for(ref, file_path).sha
+ last_commit == current_last_commit
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/satellite/merge_action.rb b/lib/gitlab/satellite/merge_action.rb
new file mode 100644
index 00000000000..832db6621c4
--- /dev/null
+++ b/lib/gitlab/satellite/merge_action.rb
@@ -0,0 +1,71 @@
+module Gitlab
+ module Satellite
+ # GitLab server-side merge
+ class MergeAction < Action
+ attr_accessor :merge_request
+
+ def initialize(user, merge_request)
+ super user, merge_request.project
+ @merge_request = merge_request
+ end
+
+ # Checks if a merge request can be executed without user interaction
+ def can_be_merged?
+ in_locked_and_timed_satellite do |merge_repo|
+ merge_in_satellite!(merge_repo)
+ end
+ end
+
+ # Merges the source branch into the target branch in the satellite and
+ # pushes it back to Gitolite.
+ # It also removes the source branch if requested in the merge request.
+ #
+ # Returns false if the merge produced conflicts
+ # Returns false if pushing from the satellite to Gitolite failed or was rejected
+ # Returns true otherwise
+ def merge!
+ in_locked_and_timed_satellite do |merge_repo|
+ if merge_in_satellite!(merge_repo)
+ # push merge back to Gitolite
+ # will raise CommandFailed when push fails
+ merge_repo.git.push({raise: true, timeout: true}, :origin, merge_request.target_branch)
+
+ # remove source branch
+ if merge_request.should_remove_source_branch && !project.root_ref?(merge_request.source_branch)
+ # will raise CommandFailed when push fails
+ merge_repo.git.push({raise: true, timeout: true}, :origin, ":#{merge_request.source_branch}")
+ end
+
+ # merge, push and branch removal successful
+ true
+ end
+ end
+ rescue Grit::Git::CommandFailed => ex
+ Gitlab::GitLogger.error(ex.message)
+ false
+ end
+
+ private
+
+ # Merges the source_branch into the target_branch in the satellite.
+ #
+ # Note: it will clear out the satellite before doing anything
+ #
+ # Returns false if the merge produced conflicts
+ # Returns true otherwise
+ def merge_in_satellite!(repo)
+ prepare_satellite!(repo)
+
+ # create target branch in satellite at the corresponding commit from Gitolite
+ repo.git.checkout({raise: true, timeout: true, b: true}, merge_request.target_branch, "origin/#{merge_request.target_branch}")
+
+ # merge the source branch from Gitolite into the satellite
+ # will raise CommandFailed when merge fails
+ repo.git.pull({raise: true, timeout: true, no_ff: true}, :origin, merge_request.source_branch)
+ rescue Grit::Git::CommandFailed => ex
+ Gitlab::GitLogger.error(ex.message)
+ false
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/satellite/satellite.rb b/lib/gitlab/satellite/satellite.rb
new file mode 100644
index 00000000000..28b6f538d00
--- /dev/null
+++ b/lib/gitlab/satellite/satellite.rb
@@ -0,0 +1,92 @@
+module Gitlab
+ module Satellite
+ class Satellite
+ PARKING_BRANCH = "__parking_branch"
+
+ attr_accessor :project
+
+ def initialize(project)
+ @project = project
+ end
+
+ def clear_and_update!
+ raise "Satellite doesn't exist" unless exists?
+
+ delete_heads!
+ clear_working_dir!
+ update_from_source!
+ end
+
+ def create
+ `git clone #{project.url_to_repo} #{path}`
+ end
+
+ def exists?
+ File.exists? path
+ end
+
+ # * Locks the satellite
+ # * Changes the current directory to the satellite's working dir
+ # * Yields
+ def lock
+ raise "Satellite doesn't exist" unless exists?
+
+ File.open(lock_file, "w+") do |f|
+ f.flock(File::LOCK_EX)
+
+ Dir.chdir(path) do
+ return yield
+ end
+ end
+ end
+
+ def lock_file
+ Rails.root.join("tmp", "#{project.path}.lock")
+ end
+
+ def path
+ Rails.root.join("tmp", "repo_satellites", project.path)
+ end
+
+ def repo
+ raise "Satellite doesn't exist" unless exists?
+
+ @repo ||= Grit::Repo.new(path)
+ end
+
+ private
+
+ # Clear the working directory
+ def clear_working_dir!
+ repo.git.reset(hard: true)
+ end
+
+ # Deletes all branches except the parking branch
+ #
+ # This ensures we have no name clashes or issues updating branches when
+ # working with the satellite.
+ def delete_heads!
+ heads = repo.heads.map(&:name)
+
+ # update or create the parking branch
+ if heads.include? PARKING_BRANCH
+ repo.git.checkout({}, PARKING_BRANCH)
+ else
+ repo.git.checkout({b: true}, PARKING_BRANCH)
+ end
+
+ # remove the parking branch from the list of heads ...
+ heads.delete(PARKING_BRANCH)
+ # ... and delete all others
+ heads.each { |head| repo.git.branch({D: true}, head) }
+ end
+
+ # Updates the satellite from Gitolite
+ #
+ # Note: this will only update remote branches (i.e. origin/*)
+ def update_from_source!
+ repo.git.fetch({timeout: true}, :origin)
+ end
+ end
+ end
+end
diff --git a/lib/tasks/bulk_import.rake b/lib/tasks/bulk_import.rake
deleted file mode 100644
index 914f920a8d7..00000000000
--- a/lib/tasks/bulk_import.rake
+++ /dev/null
@@ -1,83 +0,0 @@
-desc "Imports existing Git repos from a directory into new projects in git_base_path"
-task :import_projects, [:directory,:email] => :environment do |t, args|
- user_email, import_directory = args.email, args.directory
- repos_to_import = Dir.glob("#{import_directory}/*")
- git_base_path = Gitlab.config.git_base_path
- imported_count, skipped_count, failed_count = 0
-
- puts "Found #{repos_to_import.size} repos to import"
-
- repos_to_import.each do |repo_path|
- repo_name = File.basename repo_path
- clone_path = "#{git_base_path}#{repo_name}.git"
-
- puts " Processing #{repo_name}"
-
- if Dir.exists? clone_path
- if Project.find_by_code(repo_name)
- puts " INFO: #{clone_path} already exists in repositories directory, skipping."
- skipped_count += 1
- next
- else
- puts " INFO: Project doesn't exist for #{repo_name} (but the repo does)."
- end
- else
- # Clone the repo
- unless clone_bare_repo_as_git(repo_path, clone_path)
- failed_count += 1
- next
- end
- end
-
- # Create the project and repo
- if create_repo_project(repo_name, user_email)
- imported_count += 1
- else
- failed_count += 1
- end
- end
-
- puts "Finished importing #{imported_count} projects (skipped #{skipped_count}, failed #{failed_count})."
-end
-
-# Clones a repo as bare git repo using the git_user
-def clone_bare_repo_as_git(existing_path, new_path)
- git_user = Gitlab.config.ssh_user
- begin
- sh "sudo -u #{git_user} -i git clone --bare '#{existing_path}' #{new_path}"
- rescue Exception => msg
- puts " ERROR: Failed to clone #{existing_path} to #{new_path}"
- puts " Make sure #{git_user} can reach #{existing_path}"
- puts " Exception-MSG: #{msg}"
- end
-end
-
-# Creates a project in GitLab given a `project_name` to use
-# (for name, web url, and code url) and a `user_email` that will be
-# assigned as the owner of the project.
-def create_repo_project(project_name, user_email)
- if user = User.find_by_email(user_email)
- # Using find_by_code since that's the most important identifer to be unique
- if Project.find_by_code(project_name)
- puts " INFO: Project #{project_name} already exists in Gitlab, skipping."
- else
- project = Project.create(
- name: project_name,
- code: project_name,
- path: project_name,
- owner: user,
- description: "Automatically created from 'import_projects' rake task on #{Time.now}"
- )
-
- if project.valid?
- # Add user as admin for project
- project.users_projects.create!(:project_access => UsersProject::MASTER, :user => user)
- project.update_repository
- else
- puts " ERROR: Failed to create project #{project} because #{project.errors.first}"
- end
- end
- else
- puts " ERROR: user with #{user_email} not found, skipping"
- end
-end
diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake
new file mode 100644
index 00000000000..09f0dc9e459
--- /dev/null
+++ b/lib/tasks/gitlab/import.rake
@@ -0,0 +1,54 @@
+namespace :gitlab do
+ namespace :import do
+ # How to use:
+ #
+ # 1. copy your bare repos under git base_path
+ # 2. run bundle exec rake gitlab:import:repos RAILS_ENV=production
+ #
+ # Notes:
+ # * project owner will be a first admin
+ # * existing projects will be skipped
+ #
+ desc "GITLAB | Import bare repositories from git_host -> base_path into GitLab project instance"
+ task :repos => :environment do
+
+ git_base_path = Gitlab.config.git_base_path
+ repos_to_import = Dir.glob(git_base_path + '/*')
+
+ repos_to_import.each do |repo_path|
+ repo_name = File.basename repo_path
+
+ # skip gitolite admin
+ next if repo_name == 'gitolite-admin.git'
+
+ path = repo_name.sub(/\.git$/, '')
+
+ project = Project.find_by_path(path)
+
+ puts "Processing #{repo_name}".yellow
+
+ if project
+ puts " * #{project.name} (#{repo_name}) exists"
+ else
+ user = User.admins.first
+
+ project_params = {
+ :name => path,
+ :code => path,
+ :path => path,
+ }
+
+ project = Project.create_by_user(project_params, user)
+
+ if project.valid?
+ puts " * Created #{project.name} (#{repo_name})".green
+ else
+ puts " * Failed trying to create #{project.name} (#{repo_name})".red
+ end
+ end
+ end
+
+ puts "Done!".green
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/status.rake b/lib/tasks/gitlab/status.rake
index 302f417c01a..3878823cc94 100644
--- a/lib/tasks/gitlab/status.rake
+++ b/lib/tasks/gitlab/status.rake
@@ -49,7 +49,7 @@ namespace :gitlab do
end
print "UMASK for .gitolite.rc is 0007? ............"
- if open("#{git_base_path}/../.gitolite.rc").grep(/UMASK([ \t]*)=([ \t>]*)0007/).any?
+ if open(File.absolute_path("#{git_base_path}/../.gitolite.rc")).grep(/UMASK([ \t]*)=([ \t>]*)0007/).any?
puts "YES".green
else
puts "NO".red