perforce2svn 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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