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 26 end
27 27  
28 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 31 params[:content],
33 32 params[:commit_message],
34 33 params[:last_commit]
35 34 )
36 35  
37   - if update_status
  36 + if updated_successfully
38 37 redirect_to project_tree_path(@project, @id), notice: "Your changes have been successfully commited"
39 38 else
40 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 60 end
61 61  
62 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 64 CAN_BE_MERGED
65 65 else
66 66 CANNOT_BE_MERGED
... ... @@ -167,7 +167,7 @@ class MergeRequest < ActiveRecord::Base
167 167 end
168 168  
169 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 171 self.merge!(current_user.id)
172 172 true
173 173 end
... ...
app/roles/repository.rb
... ... @@ -41,7 +41,7 @@ module Repository
41 41 end
42 42  
43 43 def satellite
44   - @satellite ||= Gitlab::Satellite.new(self)
  44 + @satellite ||= Gitlab::Satellite::Satellite.new(self)
45 45 end
46 46  
47 47 def has_post_receive_file?
... ...
lib/gitlab/file_editor.rb
... ... @@ -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   -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   -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 @@
  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 @@
  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 @@
  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 @@
  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
... ...