Commit 59b36f203274bedf6d455968d49af8fdba5e40ef
1 parent
b1e42551
Exists in
master
and in
4 other branches
Use gitlab-shell to move repos. Requires gitlab-shell v1.1.0
Showing
7 changed files
with
182 additions
and
236 deletions
Show diff stats
app/services/project_transfer_service.rb
@@ -3,29 +3,27 @@ | @@ -3,29 +3,27 @@ | ||
3 | # Used for transfer project to another namespace | 3 | # Used for transfer project to another namespace |
4 | # | 4 | # |
5 | class ProjectTransferService | 5 | class ProjectTransferService |
6 | + include Gitolited | ||
7 | + | ||
6 | attr_accessor :project | 8 | attr_accessor :project |
7 | 9 | ||
8 | def transfer(project, new_namespace) | 10 | def transfer(project, new_namespace) |
9 | Project.transaction do | 11 | Project.transaction do |
10 | - old_namespace = project.namespace | ||
11 | - project.namespace = new_namespace | ||
12 | - | ||
13 | - old_dir = old_namespace.try(:path) || '' | ||
14 | - new_dir = new_namespace.try(:path) || '' | ||
15 | - | ||
16 | - old_repo = if old_dir.present? | ||
17 | - File.join(old_dir, project.path) | ||
18 | - else | ||
19 | - project.path | ||
20 | - end | 12 | + old_path = project.path_with_namespace |
13 | + new_path = File.join(new_namespace.try(:path) || '', project.path) | ||
21 | 14 | ||
22 | if Project.where(path: project.path, namespace_id: new_namespace.try(:id)).present? | 15 | if Project.where(path: project.path, namespace_id: new_namespace.try(:id)).present? |
23 | raise TransferError.new("Project with same path in target namespace already exists") | 16 | raise TransferError.new("Project with same path in target namespace already exists") |
24 | end | 17 | end |
25 | 18 | ||
26 | - Gitlab::ProjectMover.new(project, old_dir, new_dir).execute | ||
27 | - | 19 | + project.namespace = new_namespace |
28 | project.save! | 20 | project.save! |
21 | + | ||
22 | + unless gitlab_shell.mv_repository(old_path, new_path) | ||
23 | + raise TransferError.new('Cannot move project') | ||
24 | + end | ||
25 | + | ||
26 | + true | ||
29 | end | 27 | end |
30 | rescue Gitlab::ProjectMover::ProjectMoveError => ex | 28 | rescue Gitlab::ProjectMover::ProjectMoveError => ex |
31 | raise Project::TransferError.new(ex.message) | 29 | raise Project::TransferError.new(ex.message) |
lib/gitlab/backend/shell.rb
@@ -24,6 +24,18 @@ module Gitlab | @@ -24,6 +24,18 @@ module Gitlab | ||
24 | system("#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects import-project #{name}.git #{url}") | 24 | system("#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects import-project #{name}.git #{url}") |
25 | end | 25 | end |
26 | 26 | ||
27 | + # Move repository | ||
28 | + # | ||
29 | + # path - project path with namespace | ||
30 | + # new_path - new project path with namespace | ||
31 | + # | ||
32 | + # Ex. | ||
33 | + # mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git") | ||
34 | + # | ||
35 | + def mv_repository(path, new_path) | ||
36 | + system("#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects mv-project #{path}.git #{new_path}.git") | ||
37 | + end | ||
38 | + | ||
27 | # Remove repository from file system | 39 | # Remove repository from file system |
28 | # | 40 | # |
29 | # name - project path with namespace | 41 | # name - project path with namespace |
@@ -56,7 +68,7 @@ module Gitlab | @@ -56,7 +68,7 @@ module Gitlab | ||
56 | def url_to_repo path | 68 | def url_to_repo path |
57 | Gitlab.config.gitlab_shell.ssh_path_prefix + "#{path}.git" | 69 | Gitlab.config.gitlab_shell.ssh_path_prefix + "#{path}.git" |
58 | end | 70 | end |
59 | - | 71 | + |
60 | def gitlab_shell_user_home | 72 | def gitlab_shell_user_home |
61 | File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}") | 73 | File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}") |
62 | end | 74 | end |
lib/gitlab/project_mover.rb
@@ -1,45 +0,0 @@ | @@ -1,45 +0,0 @@ | ||
1 | -# ProjectMover class | ||
2 | -# | ||
3 | -# Used for moving project repositories from one subdir to another | ||
4 | -module Gitlab | ||
5 | - class ProjectMover | ||
6 | - class ProjectMoveError < StandardError; end | ||
7 | - | ||
8 | - attr_reader :project, :old_dir, :new_dir | ||
9 | - | ||
10 | - def initialize(project, old_dir, new_dir) | ||
11 | - @project = project | ||
12 | - @old_dir = old_dir | ||
13 | - @new_dir = new_dir | ||
14 | - end | ||
15 | - | ||
16 | - def execute | ||
17 | - # Create new dir if missing | ||
18 | - new_dir_path = File.join(Gitlab.config.gitlab_shell.repos_path, new_dir) | ||
19 | - FileUtils.mkdir( new_dir_path, mode: 0770 ) unless File.exists?(new_dir_path) | ||
20 | - | ||
21 | - old_path = File.join(Gitlab.config.gitlab_shell.repos_path, old_dir, "#{project.path}.git") | ||
22 | - new_path = File.join(new_dir_path, "#{project.path}.git") | ||
23 | - | ||
24 | - if File.exists? new_path | ||
25 | - raise ProjectMoveError.new("Destination #{new_path} already exists") | ||
26 | - end | ||
27 | - | ||
28 | - begin | ||
29 | - FileUtils.mv( old_path, new_path ) | ||
30 | - log_info "Project #{project.name} was moved from #{old_path} to #{new_path}" | ||
31 | - true | ||
32 | - rescue Exception => e | ||
33 | - message = "Project #{project.name} cannot be moved from #{old_path} to #{new_path}" | ||
34 | - log_info "Error! #{message} (#{e.message})" | ||
35 | - raise ProjectMoveError.new(message) | ||
36 | - end | ||
37 | - end | ||
38 | - | ||
39 | - protected | ||
40 | - | ||
41 | - def log_info message | ||
42 | - Gitlab::AppLogger.info message | ||
43 | - end | ||
44 | - end | ||
45 | -end |
spec/lib/project_mover_spec.rb
@@ -1,66 +0,0 @@ | @@ -1,66 +0,0 @@ | ||
1 | -require 'spec_helper' | ||
2 | - | ||
3 | -describe Gitlab::ProjectMover do | ||
4 | - let(:base_path) { Rails.root.join('tmp', 'rspec-sandbox') } | ||
5 | - | ||
6 | - before do | ||
7 | - FileUtils.rm_rf base_path if File.exists? base_path | ||
8 | - FileUtils.mkdir_p base_path | ||
9 | - | ||
10 | - Gitlab.config.gitlab_shell.stub(repos_path: base_path) | ||
11 | - | ||
12 | - @project = create(:project) | ||
13 | - end | ||
14 | - | ||
15 | - after do | ||
16 | - FileUtils.rm_rf base_path | ||
17 | - end | ||
18 | - | ||
19 | - it "should move project to subdir" do | ||
20 | - mk_dir base_path, '', @project.path | ||
21 | - mover = Gitlab::ProjectMover.new(@project, '', 'opensource') | ||
22 | - | ||
23 | - mover.execute.should be_true | ||
24 | - moved?('opensource', @project.path).should be_true | ||
25 | - end | ||
26 | - | ||
27 | - it "should move project from one subdir to another" do | ||
28 | - mk_dir base_path, 'vsizov', @project.path | ||
29 | - mover = Gitlab::ProjectMover.new(@project, 'vsizov', 'randx') | ||
30 | - | ||
31 | - mover.execute.should be_true | ||
32 | - moved?('randx', @project.path).should be_true | ||
33 | - end | ||
34 | - | ||
35 | - it "should move project from subdir to base" do | ||
36 | - mk_dir base_path, 'vsizov', @project.path | ||
37 | - mover = Gitlab::ProjectMover.new(@project, 'vsizov', '') | ||
38 | - | ||
39 | - mover.execute.should be_true | ||
40 | - moved?('', @project.path).should be_true | ||
41 | - end | ||
42 | - | ||
43 | - it "should raise if destination exists" do | ||
44 | - mk_dir base_path, '', @project.path | ||
45 | - mk_dir base_path, 'vsizov', @project.path | ||
46 | - mover = Gitlab::ProjectMover.new(@project, 'vsizov', '') | ||
47 | - | ||
48 | - expect { mover.execute }.to raise_error(Gitlab::ProjectMover::ProjectMoveError) | ||
49 | - end | ||
50 | - | ||
51 | - it "should raise if move failed" do | ||
52 | - mk_dir base_path | ||
53 | - mover = Gitlab::ProjectMover.new(@project, 'vsizov', '') | ||
54 | - | ||
55 | - expect { mover.execute }.to raise_error(Gitlab::ProjectMover::ProjectMoveError) | ||
56 | - end | ||
57 | - | ||
58 | - | ||
59 | - def mk_dir base_path, namespace = '', project_path = '' | ||
60 | - FileUtils.mkdir_p File.join(base_path, namespace, project_path + ".git") | ||
61 | - end | ||
62 | - | ||
63 | - def moved? namespace, path | ||
64 | - File.exists?(File.join(base_path, namespace, path + '.git')) | ||
65 | - end | ||
66 | -end |
spec/services/git_push_service.rb
@@ -1,111 +0,0 @@ | @@ -1,111 +0,0 @@ | ||
1 | -require 'spec_helper' | ||
2 | - | ||
3 | -describe GitPushService do | ||
4 | - let (:user) { create :user } | ||
5 | - let (:project) { create :project } | ||
6 | - let (:service) { GitPushService.new } | ||
7 | - | ||
8 | - before do | ||
9 | - @oldrev = 'b98a310def241a6fd9c9a9a3e7934c48e498fe81' | ||
10 | - @newrev = 'b19a04f53caeebf4fe5ec2327cb83e9253dc91bb' | ||
11 | - @ref = 'refs/heads/master' | ||
12 | - end | ||
13 | - | ||
14 | - describe "Git Push Data" do | ||
15 | - before do | ||
16 | - service.execute(project, user, @oldrev, @newrev, @ref) | ||
17 | - @push_data = service.push_data | ||
18 | - @commit = project.repository.commit(@newrev) | ||
19 | - end | ||
20 | - | ||
21 | - subject { @push_data } | ||
22 | - | ||
23 | - it { should include(before: @oldrev) } | ||
24 | - it { should include(after: @newrev) } | ||
25 | - it { should include(ref: @ref) } | ||
26 | - it { should include(user_id: user.id) } | ||
27 | - it { should include(user_name: user.name) } | ||
28 | - | ||
29 | - context "with repository data" do | ||
30 | - subject { @push_data[:repository] } | ||
31 | - | ||
32 | - it { should include(name: project.name) } | ||
33 | - it { should include(url: project.url_to_repo) } | ||
34 | - it { should include(description: project.description) } | ||
35 | - it { should include(homepage: project.web_url) } | ||
36 | - end | ||
37 | - | ||
38 | - context "with commits" do | ||
39 | - subject { @push_data[:commits] } | ||
40 | - | ||
41 | - it { should be_an(Array) } | ||
42 | - it { should have(1).element } | ||
43 | - | ||
44 | - context "the commit" do | ||
45 | - subject { @push_data[:commits].first } | ||
46 | - | ||
47 | - it { should include(id: @commit.id) } | ||
48 | - it { should include(message: @commit.safe_message) } | ||
49 | - it { should include(timestamp: @commit.date.xmlschema) } | ||
50 | - it { should include(url: "#{Gitlab.config.gitlab.url}/#{project.code}/commit/#{@commit.id}") } | ||
51 | - | ||
52 | - context "with a author" do | ||
53 | - subject { @push_data[:commits].first[:author] } | ||
54 | - | ||
55 | - it { should include(name: @commit.author_name) } | ||
56 | - it { should include(email: @commit.author_email) } | ||
57 | - end | ||
58 | - end | ||
59 | - end | ||
60 | - end | ||
61 | - | ||
62 | - describe "Push Event" do | ||
63 | - before do | ||
64 | - service.execute(project, user, @oldrev, @newrev, @ref) | ||
65 | - @event = Event.last | ||
66 | - end | ||
67 | - | ||
68 | - it { @event.should_not be_nil } | ||
69 | - it { @event.project.should == project } | ||
70 | - it { @event.action.should == Event::PUSHED } | ||
71 | - it { @event.data.should == service.push_data } | ||
72 | - end | ||
73 | - | ||
74 | - describe "Web Hooks" do | ||
75 | - context "with web hooks" do | ||
76 | - before do | ||
77 | - @project_hook = create(:project_hook) | ||
78 | - @project_hook_2 = create(:project_hook) | ||
79 | - project.hooks << [@project_hook, @project_hook_2] | ||
80 | - | ||
81 | - stub_request(:post, @project_hook.url) | ||
82 | - stub_request(:post, @project_hook_2.url) | ||
83 | - end | ||
84 | - | ||
85 | - it "executes multiple web hook" do | ||
86 | - @project_hook.should_receive(:async_execute).once | ||
87 | - @project_hook_2.should_receive(:async_execute).once | ||
88 | - | ||
89 | - service.execute(project, user, @oldrev, @newrev, @ref) | ||
90 | - end | ||
91 | - end | ||
92 | - | ||
93 | - context "does not execute web hooks" do | ||
94 | - before do | ||
95 | - @project_hook = create(:project_hook) | ||
96 | - project.hooks << [@project_hook] | ||
97 | - end | ||
98 | - | ||
99 | - it "when pushing a branch for the first time" do | ||
100 | - @project_hook.should_not_receive(:execute) | ||
101 | - service.execute(project, user, '00000000000000000000000000000000', 'newrev', 'refs/heads/master') | ||
102 | - end | ||
103 | - | ||
104 | - it "when pushing tags" do | ||
105 | - @project_hook.should_not_receive(:execute) | ||
106 | - service.execute(project, user, 'newrev', 'newrev', 'refs/tags/v1.0.0') | ||
107 | - end | ||
108 | - end | ||
109 | - end | ||
110 | -end | ||
111 | - |
@@ -0,0 +1,111 @@ | @@ -0,0 +1,111 @@ | ||
1 | +require 'spec_helper' | ||
2 | + | ||
3 | +describe GitPushService do | ||
4 | + let (:user) { create :user } | ||
5 | + let (:project) { create :project } | ||
6 | + let (:service) { GitPushService.new } | ||
7 | + | ||
8 | + before do | ||
9 | + @oldrev = 'b98a310def241a6fd9c9a9a3e7934c48e498fe81' | ||
10 | + @newrev = 'b19a04f53caeebf4fe5ec2327cb83e9253dc91bb' | ||
11 | + @ref = 'refs/heads/master' | ||
12 | + end | ||
13 | + | ||
14 | + describe "Git Push Data" do | ||
15 | + before do | ||
16 | + service.execute(project, user, @oldrev, @newrev, @ref) | ||
17 | + @push_data = service.push_data | ||
18 | + @commit = project.repository.commit(@newrev) | ||
19 | + end | ||
20 | + | ||
21 | + subject { @push_data } | ||
22 | + | ||
23 | + it { should include(before: @oldrev) } | ||
24 | + it { should include(after: @newrev) } | ||
25 | + it { should include(ref: @ref) } | ||
26 | + it { should include(user_id: user.id) } | ||
27 | + it { should include(user_name: user.name) } | ||
28 | + | ||
29 | + context "with repository data" do | ||
30 | + subject { @push_data[:repository] } | ||
31 | + | ||
32 | + it { should include(name: project.name) } | ||
33 | + it { should include(url: project.url_to_repo) } | ||
34 | + it { should include(description: project.description) } | ||
35 | + it { should include(homepage: project.web_url) } | ||
36 | + end | ||
37 | + | ||
38 | + context "with commits" do | ||
39 | + subject { @push_data[:commits] } | ||
40 | + | ||
41 | + it { should be_an(Array) } | ||
42 | + it { should have(1).element } | ||
43 | + | ||
44 | + context "the commit" do | ||
45 | + subject { @push_data[:commits].first } | ||
46 | + | ||
47 | + it { should include(id: @commit.id) } | ||
48 | + it { should include(message: @commit.safe_message) } | ||
49 | + it { should include(timestamp: @commit.date.xmlschema) } | ||
50 | + it { should include(url: "#{Gitlab.config.gitlab.url}/#{project.code}/commit/#{@commit.id}") } | ||
51 | + | ||
52 | + context "with a author" do | ||
53 | + subject { @push_data[:commits].first[:author] } | ||
54 | + | ||
55 | + it { should include(name: @commit.author_name) } | ||
56 | + it { should include(email: @commit.author_email) } | ||
57 | + end | ||
58 | + end | ||
59 | + end | ||
60 | + end | ||
61 | + | ||
62 | + describe "Push Event" do | ||
63 | + before do | ||
64 | + service.execute(project, user, @oldrev, @newrev, @ref) | ||
65 | + @event = Event.last | ||
66 | + end | ||
67 | + | ||
68 | + it { @event.should_not be_nil } | ||
69 | + it { @event.project.should == project } | ||
70 | + it { @event.action.should == Event::PUSHED } | ||
71 | + it { @event.data.should == service.push_data } | ||
72 | + end | ||
73 | + | ||
74 | + describe "Web Hooks" do | ||
75 | + context "with web hooks" do | ||
76 | + before do | ||
77 | + @project_hook = create(:project_hook) | ||
78 | + @project_hook_2 = create(:project_hook) | ||
79 | + project.hooks << [@project_hook, @project_hook_2] | ||
80 | + | ||
81 | + stub_request(:post, @project_hook.url) | ||
82 | + stub_request(:post, @project_hook_2.url) | ||
83 | + end | ||
84 | + | ||
85 | + it "executes multiple web hook" do | ||
86 | + @project_hook.should_receive(:async_execute).once | ||
87 | + @project_hook_2.should_receive(:async_execute).once | ||
88 | + | ||
89 | + service.execute(project, user, @oldrev, @newrev, @ref) | ||
90 | + end | ||
91 | + end | ||
92 | + | ||
93 | + context "does not execute web hooks" do | ||
94 | + before do | ||
95 | + @project_hook = create(:project_hook) | ||
96 | + project.hooks << [@project_hook] | ||
97 | + end | ||
98 | + | ||
99 | + it "when pushing a branch for the first time" do | ||
100 | + @project_hook.should_not_receive(:execute) | ||
101 | + service.execute(project, user, '00000000000000000000000000000000', 'newrev', 'refs/heads/master') | ||
102 | + end | ||
103 | + | ||
104 | + it "when pushing tags" do | ||
105 | + @project_hook.should_not_receive(:execute) | ||
106 | + service.execute(project, user, 'newrev', 'newrev', 'refs/tags/v1.0.0') | ||
107 | + end | ||
108 | + end | ||
109 | + end | ||
110 | +end | ||
111 | + |
@@ -0,0 +1,47 @@ | @@ -0,0 +1,47 @@ | ||
1 | +require 'spec_helper' | ||
2 | + | ||
3 | +describe ProjectTransferService do | ||
4 | + context 'namespace -> namespace' do | ||
5 | + let(:user) { create(:user) } | ||
6 | + let(:group) { create(:group) } | ||
7 | + let(:project) { create(:project, namespace: user.namespace) } | ||
8 | + | ||
9 | + before do | ||
10 | + @result = service.transfer(project, group) | ||
11 | + end | ||
12 | + | ||
13 | + it { @result.should be_true } | ||
14 | + it { project.namespace.should == group } | ||
15 | + end | ||
16 | + | ||
17 | + context 'namespace -> no namespace' do | ||
18 | + let(:user) { create(:user) } | ||
19 | + let(:project) { create(:project, namespace: user.namespace) } | ||
20 | + | ||
21 | + before do | ||
22 | + @result = service.transfer(project, nil) | ||
23 | + end | ||
24 | + | ||
25 | + it { @result.should be_true } | ||
26 | + it { project.namespace.should == nil } | ||
27 | + end | ||
28 | + | ||
29 | + context 'no namespace -> namespace' do | ||
30 | + let(:project) { create(:project) } | ||
31 | + let(:user) { create(:user) } | ||
32 | + | ||
33 | + before do | ||
34 | + @result = service.transfer(project, user.namespace) | ||
35 | + end | ||
36 | + | ||
37 | + it { @result.should be_true } | ||
38 | + it { project.namespace.should == user.namespace } | ||
39 | + end | ||
40 | + | ||
41 | + def service | ||
42 | + service = ProjectTransferService.new | ||
43 | + service.gitlab_shell.stub(mv_repository: true) | ||
44 | + service | ||
45 | + end | ||
46 | +end | ||
47 | + |