Commit 50a6c614b5a9eeb2faae2c5a2a71365b7d35d41a

Authored by Dmitriy Zaporozhets
2 parents 78a64ca1 904615c1

Merge pull request #1818 from riyad/refactor-satellite-actions

Refactor Satellite Code
app/controllers/tree_controller.rb
@@ -26,15 +26,14 @@ class TreeController < ProjectResourceController @@ -26,15 +26,14 @@ class TreeController < ProjectResourceController
26 end 26 end
27 27
28 def update 28 def update
29 - file_editor = Gitlab::FileEditor.new(current_user, @project, @ref)  
30 - update_status = file_editor.update(  
31 - @path, 29 + edit_file_action = Gitlab::Satellite::EditFileAction.new(current_user, @project, @ref, @path)
  30 + updated_successfully = edit_file_action.commit!(
32 params[:content], 31 params[:content],
33 params[:commit_message], 32 params[:commit_message],
34 params[:last_commit] 33 params[:last_commit]
35 ) 34 )
36 35
37 - if update_status 36 + if updated_successfully
38 redirect_to project_tree_path(@project, @id), notice: "Your changes have been successfully commited" 37 redirect_to project_tree_path(@project, @id), notice: "Your changes have been successfully commited"
39 else 38 else
40 flash[:notice] = "Your changes could not be commited, because the file has been changed" 39 flash[:notice] = "Your changes could not be commited, because the file has been changed"
app/models/merge_request.rb
@@ -60,7 +60,7 @@ class MergeRequest < ActiveRecord::Base @@ -60,7 +60,7 @@ class MergeRequest < ActiveRecord::Base
60 end 60 end
61 61
62 def check_if_can_be_merged 62 def check_if_can_be_merged
63 - self.state = if Gitlab::Merge.new(self, self.author).can_be_merged? 63 + self.state = if Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged?
64 CAN_BE_MERGED 64 CAN_BE_MERGED
65 else 65 else
66 CANNOT_BE_MERGED 66 CANNOT_BE_MERGED
@@ -167,7 +167,7 @@ class MergeRequest < ActiveRecord::Base @@ -167,7 +167,7 @@ class MergeRequest < ActiveRecord::Base
167 end 167 end
168 168
169 def automerge!(current_user) 169 def automerge!(current_user)
170 - if Gitlab::Merge.new(self, current_user).merge! && self.unmerged_commits.empty? 170 + if Gitlab::Satellite::MergeAction.new(current_user, self).merge! && self.unmerged_commits.empty?
171 self.merge!(current_user.id) 171 self.merge!(current_user.id)
172 true 172 true
173 end 173 end
app/roles/repository.rb
@@ -41,7 +41,7 @@ module Repository @@ -41,7 +41,7 @@ module Repository
41 end 41 end
42 42
43 def satellite 43 def satellite
44 - @satellite ||= Gitlab::Satellite.new(self) 44 + @satellite ||= Gitlab::Satellite::Satellite.new(self)
45 end 45 end
46 46
47 def has_post_receive_file? 47 def has_post_receive_file?
lib/gitlab/file_editor.rb
@@ -1,58 +0,0 @@ @@ -1,58 +0,0 @@
1 -module Gitlab  
2 - # GitLab file editor  
3 - #  
4 - # It gives you ability to make changes to files  
5 - # & commit this changes from GitLab UI.  
6 - class FileEditor  
7 - attr_accessor :user, :project, :ref  
8 -  
9 - def initialize(user, project, ref)  
10 - self.user = user  
11 - self.project = project  
12 - self.ref = ref  
13 - end  
14 -  
15 - def update(path, content, commit_message, last_commit)  
16 - return false unless can_edit?(path, last_commit)  
17 -  
18 - Grit::Git.with_timeout(10.seconds) do  
19 - lock_file = Rails.root.join("tmp", "#{project.path}.lock")  
20 -  
21 - File.open(lock_file, "w+") do |f|  
22 - f.flock(File::LOCK_EX)  
23 -  
24 - unless project.satellite.exists?  
25 - raise "Satellite doesn't exist"  
26 - end  
27 -  
28 - project.satellite.clear  
29 -  
30 - Dir.chdir(project.satellite.path) do  
31 - r = Grit::Repo.new('.')  
32 - r.git.sh "git reset --hard"  
33 - r.git.sh "git fetch origin"  
34 - r.git.sh "git config user.name \"#{user.name}\""  
35 - r.git.sh "git config user.email \"#{user.email}\""  
36 - r.git.sh "git checkout -b #{ref} origin/#{ref}"  
37 - File.open(path, 'w'){|f| f.write(content)}  
38 - r.git.sh "git add ."  
39 - r.git.sh "git commit -am '#{commit_message}'"  
40 - output = r.git.sh "git push origin #{ref}"  
41 -  
42 - if output =~ /reject/  
43 - return false  
44 - end  
45 - end  
46 - end  
47 - end  
48 - true  
49 - end  
50 -  
51 - protected  
52 -  
53 - def can_edit?(path, last_commit)  
54 - current_last_commit = @project.last_commit_for(ref, path).sha  
55 - last_commit == current_last_commit  
56 - end  
57 - end  
58 -end  
lib/gitlab/merge.rb
@@ -1,106 +0,0 @@ @@ -1,106 +0,0 @@
1 -module Gitlab  
2 - class Merge  
3 - attr_accessor :merge_request, :project, :user  
4 -  
5 - def initialize(merge_request, user)  
6 - @merge_request = merge_request  
7 - @project = merge_request.project  
8 - @user = user  
9 - end  
10 -  
11 - def can_be_merged?  
12 - in_locked_and_timed_satellite do |merge_repo|  
13 - merge_in_satellite!(merge_repo)  
14 - end  
15 - end  
16 -  
17 - # Merges the source branch into the target branch in the satellite and  
18 - # pushes it back to Gitolite.  
19 - # It also removes the source branch if requested in the merge request.  
20 - #  
21 - # Returns false if the merge produced conflicts  
22 - # Returns false if pushing from the satellite to Gitolite failed or was rejected  
23 - # Returns true otherwise  
24 - def merge!  
25 - in_locked_and_timed_satellite do |merge_repo|  
26 - if merge_in_satellite!(merge_repo)  
27 - # push merge back to Gitolite  
28 - # will raise CommandFailed when push fails  
29 - merge_repo.git.push({raise: true}, :origin, merge_request.target_branch)  
30 -  
31 - # remove source branch  
32 - if merge_request.should_remove_source_branch && !project.root_ref?(merge_request.source_branch)  
33 - # will raise CommandFailed when push fails  
34 - merge_repo.git.push({raise: true}, :origin, ":#{merge_request.source_branch}")  
35 - end  
36 -  
37 - # merge, push and branch removal successful  
38 - true  
39 - end  
40 - end  
41 - rescue Grit::Git::CommandFailed  
42 - false  
43 - end  
44 -  
45 - private  
46 -  
47 - # * Sets a 30s timeout for Git  
48 - # * Locks the satellite repo  
49 - # * Yields the prepared satellite repo  
50 - def in_locked_and_timed_satellite  
51 - Grit::Git.with_timeout(30.seconds) do  
52 - lock_file = Rails.root.join("tmp", "#{project.path}.lock")  
53 -  
54 - File.open(lock_file, "w+") do |f|  
55 - f.flock(File::LOCK_EX)  
56 -  
57 - unless project.satellite.exists?  
58 - raise "Satellite doesn't exist"  
59 - end  
60 -  
61 - Dir.chdir(project.satellite.path) do  
62 - repo = Grit::Repo.new('.')  
63 -  
64 - return yield repo  
65 - end  
66 - end  
67 - end  
68 - rescue Errno::ENOMEM => ex  
69 - Gitlab::GitLogger.error(ex.message)  
70 - rescue Grit::Git::GitTimeout  
71 - return false  
72 - end  
73 -  
74 - # Merges the source_branch into the target_branch in the satellite.  
75 - #  
76 - # Note: it will clear out the satellite before doing anything  
77 - #  
78 - # Returns false if the merge produced conflicts  
79 - # Returns true otherwise  
80 - def merge_in_satellite!(repo)  
81 - prepare_satellite!(repo)  
82 -  
83 - # create target branch in satellite at the corresponding commit from Gitolite  
84 - repo.git.checkout({b: true}, merge_request.target_branch, "origin/#{merge_request.target_branch}")  
85 -  
86 - # merge the source branch from Gitolite into the satellite  
87 - # will raise CommandFailed when merge fails  
88 - repo.git.pull({no_ff: true, raise: true}, :origin, merge_request.source_branch)  
89 - rescue Grit::Git::CommandFailed  
90 - false  
91 - end  
92 -  
93 - # * Clears the satellite  
94 - # * Updates the satellite from Gitolite  
95 - # * Sets up Git variables for the user  
96 - def prepare_satellite!(repo)  
97 - project.satellite.clear  
98 -  
99 - repo.git.reset(hard: true)  
100 - repo.git.fetch({}, :origin)  
101 -  
102 - repo.git.config({}, "user.name", user.name)  
103 - repo.git.config({}, "user.email", user.email)  
104 - end  
105 - end  
106 -end  
lib/gitlab/satellite.rb
@@ -1,41 +0,0 @@ @@ -1,41 +0,0 @@
1 -module Gitlab  
2 - class Satellite  
3 -  
4 - PARKING_BRANCH = "__parking_branch"  
5 -  
6 - attr_accessor :project  
7 -  
8 - def initialize project  
9 - self.project = project  
10 - end  
11 -  
12 - def create  
13 - `git clone #{project.url_to_repo} #{path}`  
14 - end  
15 -  
16 - def path  
17 - Rails.root.join("tmp", "repo_satellites", project.path)  
18 - end  
19 -  
20 - def exists?  
21 - File.exists? path  
22 - end  
23 -  
24 - #will be deleted all branches except PARKING_BRANCH  
25 - def clear  
26 - Dir.chdir(path) do  
27 - heads = Grit::Repo.new(".").heads.map{|head| head.name}  
28 - if heads.include? PARKING_BRANCH  
29 - `git checkout #{PARKING_BRANCH}`  
30 - else  
31 - `git checkout -b #{PARKING_BRANCH}`  
32 - end  
33 - heads.delete(PARKING_BRANCH)  
34 - heads.each do |head|  
35 - `git branch -D #{head}`  
36 - end  
37 - end  
38 - end  
39 -  
40 - end  
41 -end  
lib/gitlab/satellite/action.rb 0 → 100644
@@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
  1 +module Gitlab
  2 + module Satellite
  3 + class Action
  4 + DEFAULT_OPTIONS = { git_timeout: 30.seconds }
  5 +
  6 + attr_accessor :options, :project, :user
  7 +
  8 + def initialize(user, project, options = {})
  9 + @options = DEFAULT_OPTIONS.merge(options)
  10 + @project = project
  11 + @user = user
  12 + end
  13 +
  14 + protected
  15 +
  16 + # * Sets a 30s timeout for Git
  17 + # * Locks the satellite repo
  18 + # * Yields the prepared satellite repo
  19 + def in_locked_and_timed_satellite
  20 + Grit::Git.with_timeout(options[:git_timeout]) do
  21 + project.satellite.lock do
  22 + return yield project.satellite.repo
  23 + end
  24 + end
  25 + rescue Errno::ENOMEM => ex
  26 + Gitlab::GitLogger.error(ex.message)
  27 + return false
  28 + rescue Grit::Git::GitTimeout => ex
  29 + Gitlab::GitLogger.error(ex.message)
  30 + return false
  31 + end
  32 +
  33 + # * Clears the satellite
  34 + # * Updates the satellite from Gitolite
  35 + # * Sets up Git variables for the user
  36 + #
  37 + # Note: use this within #in_locked_and_timed_satellite
  38 + def prepare_satellite!(repo)
  39 + project.satellite.clear_and_update!
  40 +
  41 + repo.git.config({}, "user.name", user.name)
  42 + repo.git.config({}, "user.email", user.email)
  43 + end
  44 + end
  45 + end
  46 +end
lib/gitlab/satellite/edit_file_action.rb 0 → 100644
@@ -0,0 +1,57 @@ @@ -0,0 +1,57 @@
  1 +module Gitlab
  2 + module Satellite
  3 + # GitLab server-side file update and commit
  4 + class EditFileAction < Action
  5 + attr_accessor :file_path, :ref
  6 +
  7 + def initialize(user, project, ref, file_path)
  8 + super user, project, git_timeout: 10.seconds
  9 + @file_path = file_path
  10 + @ref = ref
  11 + end
  12 +
  13 + # Updates the files content and creates a new commit for it
  14 + #
  15 + # Returns false if the ref has been updated while editing the file
  16 + # Returns false if commiting the change fails
  17 + # Returns false if pushing from the satellite to Gitolite failed or was rejected
  18 + # Returns true otherwise
  19 + def commit!(content, commit_message, last_commit)
  20 + return false unless can_edit?(last_commit)
  21 +
  22 + in_locked_and_timed_satellite do |repo|
  23 + prepare_satellite!(repo)
  24 +
  25 + # create target branch in satellite at the corresponding commit from Gitolite
  26 + repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}")
  27 +
  28 + # update the file in the satellite's working dir
  29 + file_path_in_satellite = File.join(repo.working_dir, file_path)
  30 + File.open(file_path_in_satellite, 'w') { |f| f.write(content) }
  31 +
  32 + # commit the changes
  33 + # will raise CommandFailed when commit fails
  34 + repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
  35 +
  36 +
  37 + # push commit back to Gitolite
  38 + # will raise CommandFailed when push fails
  39 + repo.git.push({raise: true, timeout: true}, :origin, ref)
  40 +
  41 + # everything worked
  42 + true
  43 + end
  44 + rescue Grit::Git::CommandFailed => ex
  45 + Gitlab::GitLogger.error(ex.message)
  46 + false
  47 + end
  48 +
  49 + protected
  50 +
  51 + def can_edit?(last_commit)
  52 + current_last_commit = @project.last_commit_for(ref, file_path).sha
  53 + last_commit == current_last_commit
  54 + end
  55 + end
  56 + end
  57 +end
lib/gitlab/satellite/merge_action.rb 0 → 100644
@@ -0,0 +1,71 @@ @@ -0,0 +1,71 @@
  1 +module Gitlab
  2 + module Satellite
  3 + # GitLab server-side merge
  4 + class MergeAction < Action
  5 + attr_accessor :merge_request
  6 +
  7 + def initialize(user, merge_request)
  8 + super user, merge_request.project
  9 + @merge_request = merge_request
  10 + end
  11 +
  12 + # Checks if a merge request can be executed without user interaction
  13 + def can_be_merged?
  14 + in_locked_and_timed_satellite do |merge_repo|
  15 + merge_in_satellite!(merge_repo)
  16 + end
  17 + end
  18 +
  19 + # Merges the source branch into the target branch in the satellite and
  20 + # pushes it back to Gitolite.
  21 + # It also removes the source branch if requested in the merge request.
  22 + #
  23 + # Returns false if the merge produced conflicts
  24 + # Returns false if pushing from the satellite to Gitolite failed or was rejected
  25 + # Returns true otherwise
  26 + def merge!
  27 + in_locked_and_timed_satellite do |merge_repo|
  28 + if merge_in_satellite!(merge_repo)
  29 + # push merge back to Gitolite
  30 + # will raise CommandFailed when push fails
  31 + merge_repo.git.push({raise: true, timeout: true}, :origin, merge_request.target_branch)
  32 +
  33 + # remove source branch
  34 + if merge_request.should_remove_source_branch && !project.root_ref?(merge_request.source_branch)
  35 + # will raise CommandFailed when push fails
  36 + merge_repo.git.push({raise: true, timeout: true}, :origin, ":#{merge_request.source_branch}")
  37 + end
  38 +
  39 + # merge, push and branch removal successful
  40 + true
  41 + end
  42 + end
  43 + rescue Grit::Git::CommandFailed => ex
  44 + Gitlab::GitLogger.error(ex.message)
  45 + false
  46 + end
  47 +
  48 + private
  49 +
  50 + # Merges the source_branch into the target_branch in the satellite.
  51 + #
  52 + # Note: it will clear out the satellite before doing anything
  53 + #
  54 + # Returns false if the merge produced conflicts
  55 + # Returns true otherwise
  56 + def merge_in_satellite!(repo)
  57 + prepare_satellite!(repo)
  58 +
  59 + # create target branch in satellite at the corresponding commit from Gitolite
  60 + repo.git.checkout({raise: true, timeout: true, b: true}, merge_request.target_branch, "origin/#{merge_request.target_branch}")
  61 +
  62 + # merge the source branch from Gitolite into the satellite
  63 + # will raise CommandFailed when merge fails
  64 + repo.git.pull({raise: true, timeout: true, no_ff: true}, :origin, merge_request.source_branch)
  65 + rescue Grit::Git::CommandFailed => ex
  66 + Gitlab::GitLogger.error(ex.message)
  67 + false
  68 + end
  69 + end
  70 + end
  71 +end
lib/gitlab/satellite/satellite.rb 0 → 100644
@@ -0,0 +1,92 @@ @@ -0,0 +1,92 @@
  1 +module Gitlab
  2 + module Satellite
  3 + class Satellite
  4 + PARKING_BRANCH = "__parking_branch"
  5 +
  6 + attr_accessor :project
  7 +
  8 + def initialize(project)
  9 + @project = project
  10 + end
  11 +
  12 + def clear_and_update!
  13 + raise "Satellite doesn't exist" unless exists?
  14 +
  15 + delete_heads!
  16 + clear_working_dir!
  17 + update_from_source!
  18 + end
  19 +
  20 + def create
  21 + `git clone #{project.url_to_repo} #{path}`
  22 + end
  23 +
  24 + def exists?
  25 + File.exists? path
  26 + end
  27 +
  28 + # * Locks the satellite
  29 + # * Changes the current directory to the satellite's working dir
  30 + # * Yields
  31 + def lock
  32 + raise "Satellite doesn't exist" unless exists?
  33 +
  34 + File.open(lock_file, "w+") do |f|
  35 + f.flock(File::LOCK_EX)
  36 +
  37 + Dir.chdir(path) do
  38 + return yield
  39 + end
  40 + end
  41 + end
  42 +
  43 + def lock_file
  44 + Rails.root.join("tmp", "#{project.path}.lock")
  45 + end
  46 +
  47 + def path
  48 + Rails.root.join("tmp", "repo_satellites", project.path)
  49 + end
  50 +
  51 + def repo
  52 + raise "Satellite doesn't exist" unless exists?
  53 +
  54 + @repo ||= Grit::Repo.new(path)
  55 + end
  56 +
  57 + private
  58 +
  59 + # Clear the working directory
  60 + def clear_working_dir!
  61 + repo.git.reset(hard: true)
  62 + end
  63 +
  64 + # Deletes all branches except the parking branch
  65 + #
  66 + # This ensures we have no name clashes or issues updating branches when
  67 + # working with the satellite.
  68 + def delete_heads!
  69 + heads = repo.heads.map(&:name)
  70 +
  71 + # update or create the parking branch
  72 + if heads.include? PARKING_BRANCH
  73 + repo.git.checkout({}, PARKING_BRANCH)
  74 + else
  75 + repo.git.checkout({b: true}, PARKING_BRANCH)
  76 + end
  77 +
  78 + # remove the parking branch from the list of heads ...
  79 + heads.delete(PARKING_BRANCH)
  80 + # ... and delete all others
  81 + heads.each { |head| repo.git.branch({D: true}, head) }
  82 + end
  83 +
  84 + # Updates the satellite from Gitolite
  85 + #
  86 + # Note: this will only update remote branches (i.e. origin/*)
  87 + def update_from_source!
  88 + repo.git.fetch({timeout: true}, :origin)
  89 + end
  90 + end
  91 + end
  92 +end