dm-svn 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +23 -0
- data/Rakefile +56 -0
- data/VERSION +1 -0
- data/dm-svn.gemspec +85 -0
- data/lib/dm-svn.rb +13 -0
- data/lib/dm-svn/config.rb +38 -0
- data/lib/dm-svn/model.rb +12 -0
- data/lib/dm-svn/svn.rb +144 -0
- data/lib/dm-svn/svn/categorized.rb +113 -0
- data/lib/dm-svn/svn/changeset.rb +119 -0
- data/lib/dm-svn/svn/node.rb +128 -0
- data/lib/dm-svn/svn/sync.rb +85 -0
- data/spec/dm-svn/config_spec.rb +51 -0
- data/spec/dm-svn/database.yml +16 -0
- data/spec/dm-svn/fixtures/articles_comments.rb +95 -0
- data/spec/dm-svn/mock_models.rb +53 -0
- data/spec/dm-svn/model_spec.rb +5 -0
- data/spec/dm-svn/spec_helper.rb +50 -0
- data/spec/dm-svn/svn/categorized_spec.rb +138 -0
- data/spec/dm-svn/svn/changeset_spec.rb +42 -0
- data/spec/dm-svn/svn/node_spec.rb +125 -0
- data/spec/dm-svn/svn/sync_spec.rb +111 -0
- data/spec/dm-svn/svn_spec.rb +213 -0
- data/spec/spec.opts +0 -0
- data/spec/spec_helper.rb +23 -0
- metadata +132 -0
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
.DS_Store
|
2
|
+
log/*
|
3
|
+
tmp/*
|
4
|
+
TAGS
|
5
|
+
*~
|
6
|
+
.#*
|
7
|
+
schema/schema.rb
|
8
|
+
schema/*_structure.sql
|
9
|
+
schema/*.sqlite3
|
10
|
+
schema/*.sqlite
|
11
|
+
schema/*.db
|
12
|
+
*.sqlite
|
13
|
+
*.sqlite3
|
14
|
+
*.db
|
15
|
+
src/*
|
16
|
+
.hgignore
|
17
|
+
.hg/*
|
18
|
+
.svn/*
|
19
|
+
.project
|
20
|
+
.loadpath
|
21
|
+
lib/wistle/tmp/*
|
22
|
+
config/recaptcha.yml
|
23
|
+
*qt_temp*
|
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "dm-svn"
|
8
|
+
gem.summary = %Q{Sync content from a Subversion repository to a DataMapper model}
|
9
|
+
gem.description = %Q{dm-svn allows you to store data in a Subversion
|
10
|
+
repository, then sync that data to a DataMapper model (for example, to a
|
11
|
+
relational database. Essentially, it allows you app quicker access to the
|
12
|
+
Subversion data.}
|
13
|
+
gem.email = "jmorgan@morgancreative.net"
|
14
|
+
gem.homepage = "http://github.com/jm81/dm-svn"
|
15
|
+
gem.authors = ["Jared Morgan"]
|
16
|
+
gem.add_dependency('dm-core', '>= 0.10.0')
|
17
|
+
gem.add_dependency('dm-aggregates', '>= 0.10.0')
|
18
|
+
gem.add_dependency('dm-validations', '>= 0.10.0')
|
19
|
+
gem.add_dependency('jm81-svn-fixture', '>= 0.1.1')
|
20
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
21
|
+
end
|
22
|
+
Jeweler::GemcutterTasks.new
|
23
|
+
rescue LoadError
|
24
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'spec/rake/spectask'
|
28
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
29
|
+
spec.libs << 'lib' << 'spec'
|
30
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
31
|
+
end
|
32
|
+
|
33
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
34
|
+
spec.libs << 'lib' << 'spec'
|
35
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
36
|
+
spec.rcov = true
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
task :default => :spec
|
41
|
+
|
42
|
+
require 'rake/rdoctask'
|
43
|
+
Rake::RDocTask.new do |rdoc|
|
44
|
+
if File.exist?('VERSION.yml')
|
45
|
+
config = YAML.load(File.read('VERSION.yml'))
|
46
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
47
|
+
else
|
48
|
+
version = ""
|
49
|
+
end
|
50
|
+
|
51
|
+
rdoc.rdoc_dir = 'rdoc'
|
52
|
+
rdoc.title = "dm-svn #{version}"
|
53
|
+
rdoc.rdoc_files.include('README*')
|
54
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
55
|
+
end
|
56
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.0
|
data/dm-svn.gemspec
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{dm-svn}
|
8
|
+
s.version = "0.2.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Jared Morgan"]
|
12
|
+
s.date = %q{2009-10-11}
|
13
|
+
s.description = %q{dm-svn allows you to store data in a Subversion
|
14
|
+
repository, then sync that data to a DataMapper model (for example, to a
|
15
|
+
relational database. Essentially, it allows you app quicker access to the
|
16
|
+
Subversion data.}
|
17
|
+
s.email = %q{jmorgan@morgancreative.net}
|
18
|
+
s.files = [
|
19
|
+
".gitignore",
|
20
|
+
"Rakefile",
|
21
|
+
"VERSION",
|
22
|
+
"dm-svn.gemspec",
|
23
|
+
"lib/dm-svn.rb",
|
24
|
+
"lib/dm-svn/config.rb",
|
25
|
+
"lib/dm-svn/model.rb",
|
26
|
+
"lib/dm-svn/svn.rb",
|
27
|
+
"lib/dm-svn/svn/categorized.rb",
|
28
|
+
"lib/dm-svn/svn/changeset.rb",
|
29
|
+
"lib/dm-svn/svn/node.rb",
|
30
|
+
"lib/dm-svn/svn/sync.rb",
|
31
|
+
"spec/dm-svn/config_spec.rb",
|
32
|
+
"spec/dm-svn/database.yml",
|
33
|
+
"spec/dm-svn/fixtures/articles_comments.rb",
|
34
|
+
"spec/dm-svn/mock_models.rb",
|
35
|
+
"spec/dm-svn/model_spec.rb",
|
36
|
+
"spec/dm-svn/spec_helper.rb",
|
37
|
+
"spec/dm-svn/svn/categorized_spec.rb",
|
38
|
+
"spec/dm-svn/svn/changeset_spec.rb",
|
39
|
+
"spec/dm-svn/svn/node_spec.rb",
|
40
|
+
"spec/dm-svn/svn/sync_spec.rb",
|
41
|
+
"spec/dm-svn/svn_spec.rb",
|
42
|
+
"spec/spec.opts",
|
43
|
+
"spec/spec_helper.rb"
|
44
|
+
]
|
45
|
+
s.homepage = %q{http://github.com/jm81/dm-svn}
|
46
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
47
|
+
s.require_paths = ["lib"]
|
48
|
+
s.rubygems_version = %q{1.3.5}
|
49
|
+
s.summary = %q{Sync content from a Subversion repository to a DataMapper model}
|
50
|
+
s.test_files = [
|
51
|
+
"spec/dm-svn/config_spec.rb",
|
52
|
+
"spec/dm-svn/fixtures/articles_comments.rb",
|
53
|
+
"spec/dm-svn/mock_models.rb",
|
54
|
+
"spec/dm-svn/model_spec.rb",
|
55
|
+
"spec/dm-svn/spec_helper.rb",
|
56
|
+
"spec/dm-svn/svn/categorized_spec.rb",
|
57
|
+
"spec/dm-svn/svn/changeset_spec.rb",
|
58
|
+
"spec/dm-svn/svn/node_spec.rb",
|
59
|
+
"spec/dm-svn/svn/sync_spec.rb",
|
60
|
+
"spec/dm-svn/svn_spec.rb",
|
61
|
+
"spec/spec_helper.rb"
|
62
|
+
]
|
63
|
+
|
64
|
+
if s.respond_to? :specification_version then
|
65
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
66
|
+
s.specification_version = 3
|
67
|
+
|
68
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
69
|
+
s.add_runtime_dependency(%q<dm-core>, [">= 0.10.0"])
|
70
|
+
s.add_runtime_dependency(%q<dm-aggregates>, [">= 0.10.0"])
|
71
|
+
s.add_runtime_dependency(%q<dm-validations>, [">= 0.10.0"])
|
72
|
+
s.add_runtime_dependency(%q<jm81-svn-fixture>, [">= 0.1.1"])
|
73
|
+
else
|
74
|
+
s.add_dependency(%q<dm-core>, [">= 0.10.0"])
|
75
|
+
s.add_dependency(%q<dm-aggregates>, [">= 0.10.0"])
|
76
|
+
s.add_dependency(%q<dm-validations>, [">= 0.10.0"])
|
77
|
+
s.add_dependency(%q<jm81-svn-fixture>, [">= 0.1.1"])
|
78
|
+
end
|
79
|
+
else
|
80
|
+
s.add_dependency(%q<dm-core>, [">= 0.10.0"])
|
81
|
+
s.add_dependency(%q<dm-aggregates>, [">= 0.10.0"])
|
82
|
+
s.add_dependency(%q<dm-validations>, [">= 0.10.0"])
|
83
|
+
s.add_dependency(%q<jm81-svn-fixture>, [">= 0.1.1"])
|
84
|
+
end
|
85
|
+
end
|
data/lib/dm-svn.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'dm-core'
|
3
|
+
require 'dm-aggregates' # Only needed by specs, but this seems the easiest place to require.
|
4
|
+
require 'dm-validations'
|
5
|
+
require 'svn/client'
|
6
|
+
|
7
|
+
module DmSvn
|
8
|
+
VERSION = '0.2.0'
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'dm-svn/config'
|
12
|
+
require 'dm-svn/svn'
|
13
|
+
require 'dm-svn/model'
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module DmSvn
|
2
|
+
class Config
|
3
|
+
OPTS = [:uri, :username, :password,
|
4
|
+
:body_property, :property_prefix, :extension]
|
5
|
+
|
6
|
+
attr_accessor *OPTS
|
7
|
+
attr_accessor :path_from_root # Used by Sync.
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
# Set defaults
|
11
|
+
@body_property = 'body'
|
12
|
+
@property_prefix = 'ws:'
|
13
|
+
@extension = 'txt'
|
14
|
+
|
15
|
+
# Try to set variables from database.yml.
|
16
|
+
# The location of database.yml should, I suppose, be configurable.
|
17
|
+
# Oh, well.
|
18
|
+
if Object.const_defined?("RAILS_ROOT")
|
19
|
+
f = "#{RAILS_ROOT}/config/database.yml"
|
20
|
+
env = Kernel.const_defined?("RAILS_ENV") ? RAILS_ENV : "development"
|
21
|
+
elsif Object.const_defined?("Merb")
|
22
|
+
f = "#{Merb.root}/config/database.yml"
|
23
|
+
env = Merb.env.to_sym || :development
|
24
|
+
end
|
25
|
+
|
26
|
+
if f
|
27
|
+
config = YAML.load(IO.read(f))[env]
|
28
|
+
OPTS.each do |field|
|
29
|
+
config_field = config["svn_#{field}"] || config["svn_#{field}".to_sym]
|
30
|
+
if config_field
|
31
|
+
instance_variable_set("@#{field}", config_field)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/dm-svn/model.rb
ADDED
data/lib/dm-svn/svn.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
module DmSvn
|
2
|
+
module Svn
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def included(klass) # Set a few 'magic' properties
|
6
|
+
klass.extend(ClassMethods)
|
7
|
+
|
8
|
+
# svn_name could be just a name, or a full path, always excluding
|
9
|
+
# extension. If directories are stored in a model (not yet supported),
|
10
|
+
# it contains the full path (from the config.uri).
|
11
|
+
klass.property :svn_name, DataMapper::Types::Text, :lazy => false
|
12
|
+
klass.property :svn_created_at, DateTime
|
13
|
+
klass.property :svn_updated_at, DateTime
|
14
|
+
klass.property :svn_created_rev, String
|
15
|
+
klass.property :svn_updated_rev, String
|
16
|
+
klass.property :svn_created_by, String
|
17
|
+
klass.property :svn_updated_by, String
|
18
|
+
|
19
|
+
# On create, set svn_created_* attrs based on svn_updated_* attrs
|
20
|
+
klass.before :create do
|
21
|
+
attribute_set(:svn_created_at, svn_updated_at)
|
22
|
+
attribute_set(:svn_created_rev, svn_updated_rev)
|
23
|
+
attribute_set(:svn_created_by, svn_updated_by)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# +name+ could be reasonably used for another property, but may normally
|
29
|
+
# be assumed to be the 'svn_name'.
|
30
|
+
def name
|
31
|
+
@svn_name
|
32
|
+
end
|
33
|
+
|
34
|
+
# The path from the svn root for the model. For the moment, just an alias
|
35
|
+
# of +svn_name+.
|
36
|
+
def path
|
37
|
+
@svn_name
|
38
|
+
end
|
39
|
+
|
40
|
+
# Set the path. This may be responsible for moving the record to a different
|
41
|
+
# parent, etc.
|
42
|
+
def path=(value)
|
43
|
+
attribute_set(:svn_name, value)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Move to a different path and save
|
47
|
+
def move_to(new_path)
|
48
|
+
self.path = new_path
|
49
|
+
self.save
|
50
|
+
end
|
51
|
+
|
52
|
+
# Update properties (body and other properties) from a DmSvn::Svn::Node
|
53
|
+
# or similar (expects #body as a String and #properties as a Hash).
|
54
|
+
# This method calls #save.
|
55
|
+
def update_from_svn(node)
|
56
|
+
attribute_set(self.class.config.body_property, node.body) if node.body
|
57
|
+
self.path = node.short_path
|
58
|
+
|
59
|
+
node.properties.each do | attr, value |
|
60
|
+
if self.respond_to?("#{attr}=")
|
61
|
+
self.__send__("#{attr}=", value)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
if !valid?
|
66
|
+
puts "Invalid #{node.short_path} at revision #{node.revision}"
|
67
|
+
puts " - " + errors.full_messages.join(".\n - ")
|
68
|
+
end
|
69
|
+
|
70
|
+
save
|
71
|
+
end
|
72
|
+
|
73
|
+
module ClassMethods
|
74
|
+
def config
|
75
|
+
@config ||= Config.new
|
76
|
+
end
|
77
|
+
|
78
|
+
# Override belongs_to to add +:svn+ option. If :svn => true is
|
79
|
+
# included in the options, SvnSync will also sync the +belongs_to+ model.
|
80
|
+
# For example, <code>belongs_to :category, :svn => true</code>, means
|
81
|
+
# that the Category model will also be updated by SvnSync, and be based on
|
82
|
+
# folders. Folders can have svn properties set, and/or a meta.yml file
|
83
|
+
# with properties.
|
84
|
+
# def belongs_to(name, options={})
|
85
|
+
#
|
86
|
+
# end
|
87
|
+
|
88
|
+
# Override DataMapper's +property+ class method to accept as an option
|
89
|
+
# +body_property+. Setting this option tells DmSvn::Svn that this field
|
90
|
+
# will store the contents of the repository file.
|
91
|
+
def property(name, type, options = {})
|
92
|
+
if options.delete(:body_property)
|
93
|
+
config.body_property = name.to_s
|
94
|
+
end
|
95
|
+
|
96
|
+
super(name, type, options)
|
97
|
+
end
|
98
|
+
|
99
|
+
# DataMapper uses +repository+, so prepend "svn_"
|
100
|
+
def svn_repository
|
101
|
+
return @svn_repository if @svn_repository
|
102
|
+
|
103
|
+
@svn_repository = DmSvn::Model.first(:name => self.name)
|
104
|
+
@svn_repository ||= DmSvn::Model.create(:name => self.name, :revision => 0)
|
105
|
+
@svn_repository.config = config
|
106
|
+
@svn_repository
|
107
|
+
end
|
108
|
+
|
109
|
+
def sync
|
110
|
+
DmSvn::Svn::Sync.new(svn_repository).run
|
111
|
+
end
|
112
|
+
|
113
|
+
# Override normal get behavior to try to get based on path if the argument
|
114
|
+
# is a String. Extra args are ignored by default.
|
115
|
+
def get(path_or_id, *args)
|
116
|
+
if path_or_id.is_a?(String)
|
117
|
+
get_by_path(path_or_id)
|
118
|
+
else
|
119
|
+
super
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Try to get by path. If not, create a new record and so set its path.
|
124
|
+
def get_or_create(path)
|
125
|
+
i = get_by_path(path)
|
126
|
+
return i if i
|
127
|
+
|
128
|
+
i = create
|
129
|
+
i.path = path
|
130
|
+
i.save
|
131
|
+
return i
|
132
|
+
end
|
133
|
+
|
134
|
+
def get_by_path(path)
|
135
|
+
first(:svn_name => path)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
%w{sync changeset node categorized}.each do |f|
|
143
|
+
require "dm-svn/svn/#{f}"
|
144
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module DmSvn
|
2
|
+
module Svn
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
|
6
|
+
# Override belongs_to to add a :dm-svn option if :dm-svn => true, include
|
7
|
+
# Categorized and set up @svn_category and @svn_category_model instance
|
8
|
+
# methods.
|
9
|
+
def belongs_to(what, options = {})
|
10
|
+
svn = options.delete(:svn)
|
11
|
+
if svn
|
12
|
+
@svn_category = what
|
13
|
+
@svn_category_model = options[:class_name] || what.to_s.camel_case
|
14
|
+
include(DmSvn::Svn::Categorized)
|
15
|
+
end
|
16
|
+
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
# Method name for accessing the parent instance.
|
21
|
+
def svn_category
|
22
|
+
@svn_category
|
23
|
+
end
|
24
|
+
|
25
|
+
# Name of the parent model class (as a String)
|
26
|
+
def svn_category_model
|
27
|
+
@svn_category_model
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
# This module is including when belongs_to is called with :dm-svn => true.
|
33
|
+
# It overrides #path and #path= to take into account categories (folders in
|
34
|
+
# the Subversion repository). It also overrides .get to accept get_parent
|
35
|
+
# argument.
|
36
|
+
module Categorized
|
37
|
+
class << self
|
38
|
+
def included(klass)
|
39
|
+
klass.extend(ClassMethods)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# The path from the svn root for the model. Includes any folders.
|
44
|
+
def path
|
45
|
+
cat = self.send(self.class.svn_category)
|
46
|
+
|
47
|
+
if cat && !cat.path.blank?
|
48
|
+
return cat.path + "/" + @svn_name
|
49
|
+
end
|
50
|
+
|
51
|
+
return @svn_name
|
52
|
+
end
|
53
|
+
|
54
|
+
# Set the path. This is responsible for moving the record to a different
|
55
|
+
# parent, etc.
|
56
|
+
def path=(value)
|
57
|
+
value = value[1..-1] while value[0..0] == "/"
|
58
|
+
ary = value.split("/")
|
59
|
+
immediate = ary.pop
|
60
|
+
parent = ary.join("/")
|
61
|
+
|
62
|
+
if parent.blank?
|
63
|
+
self.send("#{self.class.svn_category}=", nil)
|
64
|
+
else
|
65
|
+
category_model = Object.const_get(self.class.svn_category_model)
|
66
|
+
category = category_model.get_or_create(parent)
|
67
|
+
self.send("#{self.class.svn_category}=", category)
|
68
|
+
end
|
69
|
+
|
70
|
+
attribute_set(:svn_name, immediate)
|
71
|
+
end
|
72
|
+
|
73
|
+
module ClassMethods
|
74
|
+
|
75
|
+
# Get by path, which gets parent (possibly recursively) first.
|
76
|
+
def get_by_path(value)
|
77
|
+
value = value[1..-1] while value[0..0] == "/"
|
78
|
+
ary = value.split("/")
|
79
|
+
immediate = ary.pop
|
80
|
+
parent = ary.join("/")
|
81
|
+
|
82
|
+
if parent.blank?
|
83
|
+
first(:svn_name => immediate, "#{self.svn_category}_id".to_sym => nil)
|
84
|
+
else
|
85
|
+
category_model = Object.const_get(self.svn_category_model)
|
86
|
+
category = category_model.get_by_path(parent)
|
87
|
+
return nil if category.nil?
|
88
|
+
first(:svn_name => immediate, "#{self.svn_category}_id".to_sym => category.id)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Add get_parent argument. If true and a path is not found for the
|
93
|
+
# model, try to find path in parent model (if any).
|
94
|
+
def get(path_or_id, get_parent = false)
|
95
|
+
if path_or_id.is_a?(String)
|
96
|
+
i = get_by_path(path_or_id)
|
97
|
+
if i || !get_parent
|
98
|
+
return i
|
99
|
+
else # if get_parent
|
100
|
+
category_model = Object.const_get(@svn_category_model)
|
101
|
+
return nil if category_model == self.class
|
102
|
+
category_model.get_by_path(path_or_id)
|
103
|
+
end
|
104
|
+
else
|
105
|
+
super(path_or_id)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|