perforce2svn 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/Gemfile +2 -0
  2. data/Gemfile.lock +38 -0
  3. data/LICENSE +23 -0
  4. data/README.markdown +66 -0
  5. data/Rakefile +24 -0
  6. data/bin/perforce2svn +11 -0
  7. data/lib/VERSION.yml +6 -0
  8. data/lib/perforce2svn/cli.rb +117 -0
  9. data/lib/perforce2svn/environment.rb +66 -0
  10. data/lib/perforce2svn/errors.rb +16 -0
  11. data/lib/perforce2svn/logging.rb +35 -0
  12. data/lib/perforce2svn/mapping/analyzer.rb +30 -0
  13. data/lib/perforce2svn/mapping/branch_mapping.rb +32 -0
  14. data/lib/perforce2svn/mapping/commands.rb +75 -0
  15. data/lib/perforce2svn/mapping/help.txt +139 -0
  16. data/lib/perforce2svn/mapping/lexer.rb +101 -0
  17. data/lib/perforce2svn/mapping/mapping_file.rb +65 -0
  18. data/lib/perforce2svn/mapping/operation.rb +8 -0
  19. data/lib/perforce2svn/mapping/parser.rb +145 -0
  20. data/lib/perforce2svn/migrator.rb +71 -0
  21. data/lib/perforce2svn/perforce/commit_builder.rb +159 -0
  22. data/lib/perforce2svn/perforce/p4_depot.rb +69 -0
  23. data/lib/perforce2svn/perforce/perforce_file.rb +81 -0
  24. data/lib/perforce2svn/subversion/svn_repo.rb +156 -0
  25. data/lib/perforce2svn/subversion/svn_transaction.rb +136 -0
  26. data/lib/perforce2svn/version_range.rb +43 -0
  27. data/mjt.map +7 -0
  28. data/perforce2svn.gemspec +49 -0
  29. data/spec/integration/hamlet.txt +7067 -0
  30. data/spec/integration/madmen_icon_bigger.jpg +0 -0
  31. data/spec/integration/perforce/p4_depot_spec.rb +16 -0
  32. data/spec/integration/perforce/perforce_file.yml +4 -0
  33. data/spec/integration/perforce/perforce_file_spec.rb +19 -0
  34. data/spec/integration/subversion/svn_repo_spec.rb +93 -0
  35. data/spec/integration/subversion/svn_transaction_spec.rb +112 -0
  36. data/spec/perforce2svn/cli_spec.rb +61 -0
  37. data/spec/perforce2svn/mapping/analyzer_spec.rb +41 -0
  38. data/spec/perforce2svn/mapping/branch_mapping_spec.rb +40 -0
  39. data/spec/perforce2svn/mapping/lexer_spec.rb +43 -0
  40. data/spec/perforce2svn/mapping/parser_spec.rb +140 -0
  41. data/spec/perforce2svn/perforce/commit_builder_spec.rb +74 -0
  42. data/spec/perforce2svn/version_range_spec.rb +42 -0
  43. data/spec/spec_helpers.rb +44 -0
  44. metadata +230 -0
@@ -0,0 +1,16 @@
1
+ require 'spec_helpers'
2
+ require 'perforce2svn/perforce/p4_depot'
3
+
4
+ module Perforce2Svn::Perforce
5
+ describe P4Depot do
6
+ it "should be able to connect to the Perforce server" do
7
+ attempting {
8
+ P4Depot.instance.connect!
9
+ }.should_not raise_error
10
+ end
11
+
12
+ it "should be able to retrieve the latest revision" do
13
+ P4Depot.instance.latest_revision.should >= 8000
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,4 @@
1
+ revision: 2
2
+ author: 'james.kirk'
3
+ match: 'package com.bigfishgames.faunasphere'
4
+ path: '//depot/gamecommunity/thinglefin/trunk/services/src/main/java/com/bigfishgames/faunasphere/simulation/world/FaunaWorld.java'
@@ -0,0 +1,19 @@
1
+ require 'spec_helpers'
2
+ require 'perforce2svn/perforce/perforce_file'
3
+ require 'yaml'
4
+
5
+ module Perforce2Svn::Perforce
6
+ describe PerforceFile do
7
+ before :each do
8
+ config_file = File.join(File.dirname(__FILE__), 'perforce_file.yml')
9
+ @config = YAML::load_file(config_file)
10
+ @file = PerforceFile.new(@config['revision'], @config['path'], nil, 'text', 'add')
11
+ end
12
+
13
+ it "should be able to retrieve file contents." do
14
+ @file.contents.should match(Regexp.compile(@config['match']))
15
+ end
16
+
17
+ it "should locate the symlink"
18
+ end
19
+ end
@@ -0,0 +1,93 @@
1
+ require 'perforce2svn/errors'
2
+ require 'perforce2svn/subversion/svn_repo'
3
+ require 'spec_helpers'
4
+ require 'fileutils'
5
+
6
+ module Perforce2Svn::Subversion
7
+ describe SvnRepo do
8
+ include CommitHelper
9
+
10
+ before :each do
11
+ @repo = SvnRepo.new('REPO')
12
+ end
13
+
14
+ after :each do
15
+ @repo.delete!
16
+ end
17
+
18
+ it 'should create a new repository upon initialization' do
19
+ File.exists?(@repo.repository_path).should be(true)
20
+ File.exists?(File.join(@repo.repository_path, 'hooks')).should be(true)
21
+ end
22
+
23
+ it 'should fail to create a transaction when a block is not given' do
24
+ attempting {
25
+ @repo.transaction('me', Time.now, 'log')
26
+ }.should raise_error(Perforce2Svn::SvnTransactionError, /block-scoped/)
27
+ end
28
+
29
+ it "should be able to retrieve the file contents from a path" do
30
+ write('/a', "this is some text")
31
+ @repo.pull_contents('/a').should eql("this is some text")
32
+ end
33
+
34
+ it "should fail when the file contents don't exist" do
35
+ attempting {
36
+ @repo.pull_contents('/a')
37
+ }.should raise_error(Svn::Error::FsNotFound, /revision 0/)
38
+ end
39
+
40
+ it "should fail when a file exists but not at a given revision" do
41
+ write('/a', 'content')
42
+ attempting {
43
+ @repo.pull_contents('/a', 10)
44
+ }.should raise_error(Svn::Error::FsNoSuchRevision, /10/)
45
+ end
46
+
47
+ it "should be able to determine when a path doesn't exist" do
48
+ @repo.exists?('/a').should be_false
49
+ end
50
+
51
+ it "should be able to determine when a path exists" do
52
+ write('/a', 'contents')
53
+ @repo.exists?('/a').should be_true
54
+ end
55
+
56
+ it "should be able to retrieve the commit log" do
57
+ write('/a', 'contents')
58
+ commit = @repo.commit_log(1)
59
+ commit.author.should eql('gabe')
60
+ end
61
+
62
+ it "should be fail to retrieve the commit log on a bad revision" do
63
+ attempting {
64
+ @repo.commit_log(10)
65
+ }.should raise_error(Svn::Error::FsNoSuchRevision, /10/)
66
+ end
67
+
68
+ it "should be able to retrieve a property" do
69
+ write('/a', 'contents', true)
70
+ @repo.prop_get('/a', 'svn:mime-type', 1).should eql('application/octet-stream')
71
+ end
72
+
73
+ it "should be able to list child directories" do
74
+ write('/a', 'contents')
75
+ write('/b', 'contents')
76
+ @repo.children('/').should eql(['a', 'b'])
77
+ end
78
+
79
+ it "should not retrieve properties on non-existent paths" do
80
+ write('/a', 'contents')
81
+ attempting {
82
+ @repo.prop_get('/b', Svn::Core::PROP_REVISION_LOG)
83
+ }.should raise_error(Svn::Error::FsNotFound, /\/b/)
84
+ end
85
+
86
+ it "should fail to retrieve properties on a bad revision" do
87
+ write('/a', 'contents')
88
+ attempting {
89
+ @repo.prop_get('/a', Svn::Core::PROP_REVISION_AUTHOR, 5)
90
+ }.should raise_error(Svn::Error::FsNoSuchRevision, /5/)
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,112 @@
1
+ require 'perforce2svn/subversion/svn_repo'
2
+ require 'spec_helpers'
3
+ require 'stringio'
4
+
5
+ module Perforce2Svn::Subversion
6
+ describe SvnTransaction do
7
+ include CommitHelper
8
+
9
+ before :each do
10
+ @repo = SvnRepo.new('REPO')
11
+ end
12
+
13
+ after :each do
14
+ @repo.delete!
15
+ end
16
+
17
+ it "should be able to commit a single change" do
18
+ write('/a.txt', "this is some text\nthat\goes here")
19
+ @repo.pull_contents('/a.txt').should eql("this is some text\nthat\goes here")
20
+ end
21
+
22
+ it "should be able to retrieve the updated file contents" do
23
+ write("/a.txt", "first commit")
24
+ write("/a.txt", 'second commit')
25
+ @repo.pull_contents('/a.txt').should eql('second commit')
26
+ end
27
+
28
+ it "should be able to delete a file" do
29
+ write('/a', 'conent')
30
+ delete('/a')
31
+ attempting {
32
+ @repo.pull_contents('/a')
33
+ }.should raise_error
34
+ end
35
+
36
+ it "can create multiple directories" do
37
+ @repo.exists?('/a/b/c').should be(false)
38
+
39
+ @repo.transaction('g', Time.new, 'l') do |txn|
40
+ txn.exists?('/a/b/c').should be(false)
41
+ txn.mkdir('/a/b/c')
42
+ end
43
+
44
+ @repo.exists?('/a/b/c').should be(true)
45
+ end
46
+
47
+ it "can delete all parent directories" do
48
+ write('/a/b/c/d.txt', 'some text')
49
+ write('/a/b.txt', 'other text')
50
+ delete('/a/b/c/d.txt')
51
+
52
+ @repo.exists?('/a/b/c').should be(false)
53
+ end
54
+
55
+ it "will write binary mime types correctly" do
56
+ jpg = read_in('spec/integration/madmen_icon_bigger.jpg', 'rb')
57
+ write('/a.jpg', jpg, true)
58
+
59
+ @repo.pull_contents('/a.jpg').should eql(jpg)
60
+ @repo.prop_get('/a.jpg', 'svn:mime-type').should eql('application/octet-stream')
61
+ end
62
+
63
+ it "will make sure that big files are retained correctly" do
64
+ hamlet = read_in('spec/integration/hamlet.txt')
65
+ write('/hamlet.txt', hamlet)
66
+
67
+ @repo.pull_contents('/hamlet.txt').should eql(hamlet)
68
+ end
69
+
70
+ it "will handle symlinks correctly" do
71
+ write("/src.txt", "some content")
72
+ symlink('./src.txt', '/b.txt')
73
+
74
+ @repo.pull_contents('/b.txt').should eql('link ./src.txt')
75
+ @repo.prop_get('/b.txt', 'svn:special').should eql('*')
76
+ end
77
+
78
+ it "will return the most recent revision number on commit" do
79
+ write('/a.txt', 'content').should eql(1)
80
+ end
81
+
82
+ it "will be able to find if a directory has children" do
83
+ write('/a/b.txt', 'content')
84
+ write('/a/c.txt', 'content')
85
+ @repo.transaction('gabe', Time.now, 'r') do |txn|
86
+ txn.has_children?('/a').should be(true)
87
+ end
88
+ end
89
+
90
+ it "will be able to copy paths" do
91
+ write('/a.txt', 'content')
92
+ @repo.transaction('gabe', Time.now, 'r') do |txn|
93
+ txn.copy('/a.txt', '/b.txt')
94
+ end
95
+
96
+ @repo.exists?('/a.txt').should be_true
97
+ @repo.exists?('/b.txt').should be_true
98
+ @repo.pull_contents('/b.txt').should eql('content')
99
+ end
100
+
101
+ it "will be able to move paths" do
102
+ write('/a.txt', 'content')
103
+ @repo.transaction('gabe', Time.now, 'r') do |txn|
104
+ txn.move('/a.txt', '/b.txt')
105
+ end
106
+
107
+ @repo.exists?('/a.txt').should be_false
108
+ @repo.exists?('/b.txt').should be_true
109
+ @repo.pull_contents('/b.txt').should eql('content')
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,61 @@
1
+ require 'perforce2svn/cli'
2
+ require 'perforce2svn/errors'
3
+ require 'spec_helpers'
4
+
5
+ module Perforce2Svn
6
+ module CLIHelper
7
+ def parse(*args)
8
+ cli = CLI.new
9
+ args << '--repository'
10
+ args << 'here'
11
+ args << __FILE__
12
+ cli.parse!(args, true)
13
+ end
14
+ end
15
+
16
+ describe CLI do
17
+ include CLIHelper
18
+
19
+ it "should be able to parse the debug flag correctly" do
20
+ parse("--debug")[:debug].should be(true)
21
+ end
22
+
23
+ it "should be able to retrieve the repository path" do
24
+ parse('-r', 'some/path')[:repository].should eql('here')
25
+ end
26
+
27
+ it "should be able to parse the live path" do
28
+ parse('-l', '/')[:live_path].should eql('/')
29
+ end
30
+
31
+ it "should be able to skip updates" do
32
+ parse('--skip-commands')[:skip_commands].should be(true)
33
+ end
34
+
35
+ it "should be able to skip perforce" do
36
+ parse('--skip-perforce')[:skip_perforce].should be(true)
37
+ end
38
+
39
+ it "should be able to run the analysis only" do
40
+ parse('-a')[:analyze_only].should be(true)
41
+ end
42
+
43
+ describe "when validating the count format" do
44
+ it "should fail when the start revision is less than 1" do
45
+ attempting_to {
46
+ parse('-c', '0:4')
47
+ }.should raise_error(Choosy::ValidationError, /Minimum/)
48
+ end
49
+
50
+ it "should set -1 when given HEAD" do
51
+ parse('-c', '1:HEAD')[:changes].max.should eql(-1)
52
+ end
53
+
54
+ it "should fail when the end revision < 1" do
55
+ attempting_to {
56
+ parse('-c', '1:0')
57
+ }.should raise_error(Choosy::ValidationError, /Maximum/)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,41 @@
1
+ require 'perforce2svn/logging'
2
+ require 'perforce2svn/mapping/analyzer'
3
+ require 'perforce2svn/mapping/commands'
4
+ require 'ostruct'
5
+
6
+ module Perforce2Svn::Mapping
7
+
8
+ module AnalyzerHelper
9
+ def analyzing(*commands)
10
+ a = Analyzer.new(File.dirname(__FILE__))
11
+ a.check(commands)
12
+ end
13
+
14
+ def updater(file = nil)
15
+ file ||= __FILE__
16
+ tok = OpenStruct.new
17
+ tok.line_number = 1
18
+ Update.new(tok, nil, file)
19
+ end
20
+ end
21
+
22
+ describe "Mapping analyzer" do
23
+ include AnalyzerHelper
24
+
25
+ it "should be able to test whether updated files exist" do
26
+ analyzing(updater).should be(true)
27
+ end
28
+
29
+ it "should fail when updated files don't exist" do
30
+ analyzing(updater("nowhere")).should be(false)
31
+ end
32
+
33
+ it "should be able to check multiple files" do
34
+ analyzing(updater("nowhere")).should be(false)
35
+ end
36
+
37
+ it "should be able to check when a file has a relative path" do
38
+ analyzing(updater(File.basename(__FILE__))).should be(true)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helpers'
2
+ require 'perforce2svn/mapping/branch_mapping'
3
+ require 'ostruct'
4
+
5
+ module Perforce2Svn::Mapping
6
+ module BranchMappingHelper
7
+ def map(p4, svn)
8
+ tok = OpenStruct.new
9
+ tok.line_number = 1
10
+ BranchMapping.new(tok, p4, svn)
11
+ end
12
+ end
13
+
14
+ describe "Branch Mappings" do
15
+ include BranchMappingHelper
16
+
17
+ it "should be able to determine when a path doesn't match" do
18
+ bm = map('//depot/path/', '/svn/path/here')
19
+ bm.matches_perforce_path?('//depot/not/here').should be(false)
20
+ end
21
+
22
+ it "should be able to determine when a path doesn't match" do
23
+ bm = map('//depot/path/', '/svn/path/here')
24
+ bm.matches_perforce_path?('//depot/path/goes/here').should be(true)
25
+ end
26
+
27
+ it "should be able to format the dotted P4 path" do
28
+ bm = map('//depot/path/', '/svn/path/here')
29
+ bm.p4_dotted.should eql('//depot/path/...')
30
+ end
31
+
32
+ it "should be able to translate funky path characters correctly for subversion" do
33
+ bf = map("//p/", "/o/")
34
+ bf.to_svn_path("//p/%40/a").should eql("/o/@/a")
35
+ bf.to_svn_path("//p/%23/a").should eql("/o/#/a")
36
+ bf.to_svn_path("//p/%2a/a").should eql("/o/*/a")
37
+ bf.to_svn_path("//p/%25/a").should eql("/o/%/a")
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,43 @@
1
+ require 'perforce2svn/mapping/lexer'
2
+
3
+ module Perforce2Svn
4
+ module Mapping
5
+
6
+ describe "Mapping lexer" do
7
+ it "should be able to parse a set of simple tokens in a line" do
8
+ lexer = Lexer.new(nil)
9
+ tok = lexer.tokenize('migrate this/that other/file')
10
+ tok.length.should eql(3)
11
+ tok[0].should eql('migrate')
12
+ tok[1].should eql('this/that')
13
+ tok[2].should eql('other/file')
14
+ end
15
+
16
+ it "should handle spaces in the path names" do
17
+ lexer = Lexer.new(nil)
18
+ tok = lexer.tokenize("migrate this/that\\ other/path will\\ be/something")
19
+ tok.length.should eql(3)
20
+ tok[0].should eql('migrate')
21
+ tok[1].should eql("this/that other/path")
22
+ tok[2].should eql("will be/something")
23
+ end
24
+
25
+ it "will delete multiple spaces" do
26
+ lexer = Lexer.new(nil)
27
+ tok = lexer.tokenize(" migrate this/that and/a\\ nother ");
28
+ tok.length.should eql(3)
29
+ tok[0].should eql('migrate')
30
+ tok[1].should eql('this/that')
31
+ tok[2].should eql('and/a nother')
32
+ end
33
+
34
+ it "should leave off the comments at the end of lines" do
35
+ lexer = Lexer.new(nil)
36
+ tok = lexer.tokenize("this is a # comment string")
37
+ tok.length.should eql(3)
38
+ tok[0].should eql('this')
39
+ tok[2].should eql('a')
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,140 @@
1
+ require 'perforce2svn/mapping/parser'
2
+ require 'stringio'
3
+
4
+ module Perforce2Svn::Mapping
5
+ module ParserHelper
6
+ def parse(txt)
7
+ p = Parser.new
8
+ p.parse!(StringIO.new(txt), 'live')
9
+ end
10
+
11
+ def mappings(txt)
12
+ parse(txt)[:mappings]
13
+ end
14
+
15
+ def commands(txt)
16
+ parse(txt)[:commands]
17
+ end
18
+ end
19
+
20
+ describe Parser do
21
+ include ParserHelper
22
+
23
+ it "should fail when an unknown directive is found" do
24
+ parse(<<EOF
25
+ # A comment
26
+ not-directive arg
27
+ EOF
28
+ )[:failed].should be(true)
29
+ end
30
+
31
+ it "should be able to parse 'update's" do
32
+ parse("update /some/path")[:failed].should be(false)
33
+ end
34
+
35
+ it "should be able to parse 'migrate'" do
36
+ c = mappings("migrate //depot/path /svn/path")
37
+ c[0].class.should eql(BranchMapping)
38
+ c[0].line_number.should eql(1)
39
+ c[0].svn_path.should eql('/svn/path/')
40
+ end
41
+
42
+ it "should fail to parse the 'update' command without a svn prefix" do
43
+ parse("update src/main/pom.xml")[:failed].should be(true)
44
+ end
45
+
46
+ it "should parse the 'update' command when an svn prefix is available" do
47
+ parse(<<EOF
48
+ svn-prefix /some/path
49
+ update src/main/pom.xml
50
+ EOF
51
+ )[:failed].should be(false)
52
+ end
53
+
54
+ it "should parse the mapping file and return a list of migrations" do
55
+ m = mappings(<<EOF
56
+ migrate //depot/path /trunk/project
57
+ svn-prefix /trunk
58
+ migrate //depot/partition another-project
59
+ EOF
60
+ )
61
+
62
+ first = m[0]
63
+ first.line_number.should eql(1)
64
+ first.p4_path.should eql('//depot/path/')
65
+ first.svn_path.should eql('/trunk/project/')
66
+
67
+ second = m[1]
68
+ second.line_number.should eql(3)
69
+ second.p4_path.should eql('//depot/partition/')
70
+ second.svn_path.should eql('/trunk/another-project/')
71
+ end
72
+
73
+ it "should be able to correctly parse updates" do
74
+ update = commands("update /src/dest.xml")[0]
75
+ update.class.should eql(Update)
76
+ update.line_number.should eql(1)
77
+ update.live_path.should eql('live/src/dest.xml')
78
+ update.svn_path.should eql('/src/dest.xml')
79
+ end
80
+
81
+ it "should be able to correctly parse delete" do
82
+ delete = commands("delete /src/another.java")[0]
83
+ delete.class.should eql(Delete)
84
+ delete.line_number.should eql(1)
85
+ delete.svn_path.should eql('/src/another.java')
86
+ end
87
+
88
+ it "should be able to correctly parse moves" do
89
+ move = commands("move /this/location.txt /to/here/location.txt")[0]
90
+ move.class.should eql(Move)
91
+ move.line_number.should eql(1)
92
+ move.svn_from.should eql('/this/location.txt')
93
+ move.svn_to.should eql('/to/here/location.txt')
94
+ end
95
+
96
+ it "should be able to correctly parse copies" do
97
+ copy = commands("copy /src.txt /dest.txt")[0]
98
+ copy.class.should eql(Copy)
99
+ copy.line_number.should eql(1)
100
+ copy.svn_from.should eql('/src.txt')
101
+ copy.svn_to.should eql('/dest.txt')
102
+ end
103
+
104
+ it "should be able to insert the svn prefix when a live path is calculated" do
105
+ update = commands("svn-prefix /trunk/project\nupdate src/dest.xml")[0]
106
+ update.live_path.should eql('live/trunk/project/src/dest.xml')
107
+ update.svn_path.should eql('/trunk/project/src/dest.xml')
108
+ end
109
+
110
+ it "should fail when too many arguments are given to a command" do
111
+ parse('update /this/path /should/not/be')[:failed].should be(true)
112
+ end
113
+
114
+ it "should add the author to the context" do
115
+ parse('author An author')[:author].should eql('An author')
116
+ end
117
+
118
+ it "should add the message to the context" do
119
+ parse(<<EOF
120
+ message this is a message that \
121
+ should span multiple lines \
122
+ and continue
123
+ EOF
124
+ )[:message].should eql('this is a message that should span multiple lines and continue')
125
+ end
126
+
127
+ it "should be able to order the commands" do
128
+ cmds = commands(<<EOF
129
+ update /src/dest.xml
130
+ delete /src/another.java
131
+ EOF
132
+ )
133
+
134
+ cmds[0].class.should eql(Update)
135
+ cmds[0].line_number.should eql(1)
136
+ cmds[1].class.should eql(Delete)
137
+ cmds[1].line_number.should eql(2)
138
+ end
139
+ end
140
+ end