tfs_graph 0.1.1 → 0.1.2
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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/Gemfile +12 -4
- data/Rakefile +1 -0
- data/lib/tfs_graph.rb +1 -2
- data/lib/tfs_graph/abstract_store.rb +23 -0
- data/lib/tfs_graph/associators/branch_associator.rb +1 -1
- data/lib/tfs_graph/associators/changeset_tree_builder.rb +29 -0
- data/lib/tfs_graph/behaviors.rb +9 -0
- data/lib/tfs_graph/behaviors/neo4j_repository/branch.rb +39 -0
- data/lib/tfs_graph/behaviors/neo4j_repository/changeset.rb +12 -0
- data/lib/tfs_graph/behaviors/neo4j_repository/project.rb +89 -0
- data/lib/tfs_graph/behaviors/related_repository/branch.rb +26 -0
- data/lib/tfs_graph/behaviors/related_repository/changeset.rb +8 -0
- data/lib/tfs_graph/behaviors/related_repository/project.rb +48 -0
- data/lib/tfs_graph/branch.rb +84 -37
- data/lib/tfs_graph/branch/branch_archive_handler.rb +10 -6
- data/lib/tfs_graph/branch/branch_store.rb +13 -26
- data/lib/tfs_graph/changeset.rb +46 -25
- data/lib/tfs_graph/changeset/changeset_normalizer.rb +1 -0
- data/lib/tfs_graph/changeset/changeset_store.rb +13 -36
- data/lib/tfs_graph/changeset_merge.rb +20 -18
- data/lib/tfs_graph/changeset_merge/changeset_merge_store.rb +13 -10
- data/lib/tfs_graph/config.rb +12 -4
- data/lib/tfs_graph/entity.rb +34 -6
- data/lib/tfs_graph/extensions.rb +27 -0
- data/lib/tfs_graph/graph_populator.rb +9 -1
- data/lib/tfs_graph/persistable_entity.rb +60 -0
- data/lib/tfs_graph/populators/everything.rb +16 -4
- data/lib/tfs_graph/populators/for_archived_branch.rb +28 -0
- data/lib/tfs_graph/populators/for_branch.rb +35 -0
- data/lib/tfs_graph/populators/for_project.rb +22 -5
- data/lib/tfs_graph/populators/since_date.rb +21 -5
- data/lib/tfs_graph/populators/since_last.rb +22 -10
- data/lib/tfs_graph/populators/utilities.rb +4 -19
- data/lib/tfs_graph/project.rb +49 -13
- data/lib/tfs_graph/project/project_store.rb +13 -22
- data/lib/tfs_graph/repository.rb +78 -0
- data/lib/tfs_graph/repository/neo4j_repository.rb +97 -0
- data/lib/tfs_graph/repository/related_repository.rb +89 -0
- data/lib/tfs_graph/repository_registry.rb +60 -0
- data/lib/tfs_graph/server_registry.rb +45 -0
- data/lib/tfs_graph/store_helpers.rb +4 -5
- data/lib/tfs_graph/version.rb +1 -1
- data/schema.cypher +7 -0
- data/spec/branch_spec.rb +120 -0
- data/spec/neo4j_repository_integration_spec.rb +346 -0
- data/spec/persistable_entity_spec.rb +91 -0
- data/spec/project_spec.rb +29 -0
- data/spec/related_repository_integration_spec.rb +328 -0
- data/spec/repository_registry_spec.rb +48 -0
- data/spec/repository_spec.rb +73 -0
- data/spec/server_registery_spec.rb +36 -0
- data/spec/spec_helper.rb +12 -24
- data/tfs_graph.gemspec +3 -2
- metadata +67 -21
- data/lib/tfs_graph/associators/changeset_tree_creator.rb +0 -19
- data/spec/factories.rb +0 -20
@@ -1,30 +1,21 @@
|
|
1
|
-
require 'tfs_graph/
|
1
|
+
require 'tfs_graph/abstract_store'
|
2
|
+
|
2
3
|
require 'tfs_graph/project/project_normalizer'
|
4
|
+
require 'tfs_graph/abstract_store'
|
3
5
|
|
4
6
|
module TFSGraph
|
5
|
-
class ProjectStore
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
def cache
|
10
|
-
projects = tfs.projects.run
|
11
|
-
normalized = ProjectNormalizer.normalize_many projects
|
12
|
-
|
13
|
-
normalized.map do |project_attrs|
|
14
|
-
project = Project.create project_attrs
|
15
|
-
Related::Relationship.create(:projects, Related.root, project)
|
16
|
-
|
17
|
-
project
|
18
|
-
end
|
19
|
-
end
|
7
|
+
class ProjectStore < AbstractStore
|
8
|
+
def cache(project)
|
9
|
+
RepositoryRegistry.project_repository.create project
|
10
|
+
end
|
20
11
|
|
21
|
-
|
22
|
-
|
23
|
-
|
12
|
+
private
|
13
|
+
def root_query
|
14
|
+
tfs.projects
|
15
|
+
end
|
24
16
|
|
25
|
-
|
26
|
-
|
27
|
-
end
|
17
|
+
def normalize(projects)
|
18
|
+
ProjectNormalizer.normalize_many projects
|
28
19
|
end
|
29
20
|
end
|
30
21
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'active_support/hash_with_indifferent_access'
|
2
|
+
require 'tfs_graph/extensions'
|
3
|
+
|
4
|
+
require 'tfs_graph/project'
|
5
|
+
require 'tfs_graph/branch'
|
6
|
+
require 'tfs_graph/changeset'
|
7
|
+
|
8
|
+
require 'tfs_graph/behaviors'
|
9
|
+
|
10
|
+
module TFSGraph
|
11
|
+
class Repository
|
12
|
+
include Extensions
|
13
|
+
attr_reader :type
|
14
|
+
|
15
|
+
NotFound = Class.new(RuntimeError)
|
16
|
+
|
17
|
+
def initialize(type)
|
18
|
+
@type = type
|
19
|
+
|
20
|
+
add_behavior self, constantize("TFSGraph::Behaviors::#{self.base_class_name}::#{type.base_class_name}")
|
21
|
+
|
22
|
+
# register self as the server type
|
23
|
+
ServerRegistry.server(self)
|
24
|
+
end
|
25
|
+
|
26
|
+
def find(id)
|
27
|
+
rebuild find_native(id)
|
28
|
+
end
|
29
|
+
|
30
|
+
def exists?(id)
|
31
|
+
begin
|
32
|
+
find_native(id)
|
33
|
+
true
|
34
|
+
rescue NotFound
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def related?(node1, node2, type)
|
40
|
+
node1.rels(dir: :outgoing, between: node2, type: type).any?
|
41
|
+
end
|
42
|
+
|
43
|
+
def save(object)
|
44
|
+
db_object = object.persisted? ? update(object) : persist(object)
|
45
|
+
object.persist get_id(db_object), db_object
|
46
|
+
end
|
47
|
+
|
48
|
+
def delete(obj)
|
49
|
+
obj.db_object = nil
|
50
|
+
obj.id = nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def build(args={})
|
54
|
+
@type.new self, args
|
55
|
+
end
|
56
|
+
|
57
|
+
def rebuild(db_object)
|
58
|
+
attributes = HashWithIndifferentAccess.new db_object.attributes
|
59
|
+
|
60
|
+
obj = build attributes
|
61
|
+
obj.persist get_id(db_object), db_object
|
62
|
+
end
|
63
|
+
|
64
|
+
def create(args)
|
65
|
+
object = build(args)
|
66
|
+
save(object)
|
67
|
+
end
|
68
|
+
|
69
|
+
def inspect
|
70
|
+
type
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
def normalize(attrs)
|
75
|
+
HashWithIndifferentAccess.new attrs
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'neo4j-core'
|
2
|
+
require 'tfs_graph/repository'
|
3
|
+
|
4
|
+
module TFSGraph
|
5
|
+
class Repository
|
6
|
+
class Neo4jRepository < Repository
|
7
|
+
def find_native(id)
|
8
|
+
node = Neo4j::Label.query(type.base_class_name.downcase.to_sym, conditions: {id: id}).to_a.first
|
9
|
+
node ||= find_by_neo_id(id)
|
10
|
+
|
11
|
+
raise NotFound, id unless node
|
12
|
+
node
|
13
|
+
end
|
14
|
+
|
15
|
+
def find_by_neo_id(id)
|
16
|
+
Neo4j::Node.load(id)
|
17
|
+
end
|
18
|
+
|
19
|
+
def session
|
20
|
+
Neo4j::Session.current
|
21
|
+
end
|
22
|
+
|
23
|
+
def flush
|
24
|
+
@root = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def delete(obj)
|
28
|
+
obj.db_object.del
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
def drop_all
|
33
|
+
flush
|
34
|
+
session.query("MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r")
|
35
|
+
end
|
36
|
+
|
37
|
+
def root
|
38
|
+
@root ||= begin
|
39
|
+
node = Neo4j::Label.find_all_nodes(:root).first
|
40
|
+
node = Neo4j::Node.create({name: "Root node"}, :root) if node.nil?
|
41
|
+
node
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def relate(relationship, parent, child)
|
46
|
+
Neo4j::Relationship.create relationship, parent, child unless related?(parent, child, relationship)
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_nodes(entity, direction, relation, type)
|
50
|
+
begin
|
51
|
+
entity.nodes(dir: direction.to_sym, type: relation.to_sym).map do |node|
|
52
|
+
type.repository.rebuild node
|
53
|
+
end
|
54
|
+
rescue Neo4j::Server::CypherResponse::ResponseError => e
|
55
|
+
[]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def rebuild(db_object)
|
60
|
+
attributes = normalize db_object.props
|
61
|
+
|
62
|
+
obj = build attributes
|
63
|
+
obj.persist get_id(db_object), db_object
|
64
|
+
end
|
65
|
+
|
66
|
+
def rebuild_from_query(attrs, id)
|
67
|
+
obj = build normalize(attrs)
|
68
|
+
obj.persist id, nil
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
# persist and update both expose the DB object from Neo4j
|
73
|
+
# make methods private so we have to use save to persist
|
74
|
+
|
75
|
+
# create the DB object
|
76
|
+
def persist(object)
|
77
|
+
begin
|
78
|
+
Neo4j::Node.create(object.to_hash, object.base_class_name.downcase)
|
79
|
+
rescue Neo4j::Server::CypherResponse::ResponseError => e
|
80
|
+
# assume all errors come from constraint errors... probably a bad idea
|
81
|
+
fetch_existing_record(object)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# update the DB object
|
86
|
+
def update(object)
|
87
|
+
object.db_object.update_props object.attributes
|
88
|
+
object.db_object
|
89
|
+
end
|
90
|
+
|
91
|
+
def get_id(object)
|
92
|
+
return 0 if object.nil?
|
93
|
+
object.neo_id
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'related'
|
2
|
+
require 'tfs_graph/repository'
|
3
|
+
|
4
|
+
module TFSGraph
|
5
|
+
class Repository
|
6
|
+
class RelatedRepository < Repository
|
7
|
+
def initialize(type)
|
8
|
+
super
|
9
|
+
Related.redis = ServerRegistry.redis
|
10
|
+
end
|
11
|
+
|
12
|
+
def find_native(id)
|
13
|
+
begin
|
14
|
+
Related::Node.find(id)
|
15
|
+
rescue Related::NotFound => e
|
16
|
+
raise TFSGraph::Repository::NotFound, e.message
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def root
|
21
|
+
Related.root
|
22
|
+
end
|
23
|
+
|
24
|
+
def session
|
25
|
+
ServerRegistry.redis
|
26
|
+
end
|
27
|
+
|
28
|
+
def flush
|
29
|
+
# noop
|
30
|
+
end
|
31
|
+
|
32
|
+
def drop_all
|
33
|
+
flush
|
34
|
+
session.keys("*").each do |k|
|
35
|
+
session.del k
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def delete(obj)
|
40
|
+
obj.db_object.destroy
|
41
|
+
super
|
42
|
+
end
|
43
|
+
|
44
|
+
def relate(relationship, parent, child)
|
45
|
+
Related::Relationship.create relationship, parent, child
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_nodes(entity, direction, relation, type)
|
49
|
+
get_nodes_for(get_relation(entity, direction, relation), type)
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_nodes_for(relation, type)
|
53
|
+
relation.nodes.map do |node|
|
54
|
+
type.repository.rebuild node
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def get_relation(entity, direction, relation)
|
59
|
+
entity.send(direction.to_sym, relation.to_sym)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
# persist and update both expose the DB object from Redis/Related
|
64
|
+
# make methods private so we have to use save to persist
|
65
|
+
|
66
|
+
# create the DB object
|
67
|
+
def persist(object)
|
68
|
+
Related::Node.create(object.to_hash)
|
69
|
+
end
|
70
|
+
|
71
|
+
# update the DB object
|
72
|
+
def update(object)
|
73
|
+
db_object = object.db_object
|
74
|
+
|
75
|
+
object.attributes.each do |key, value|
|
76
|
+
db_object.write_attribute key, value
|
77
|
+
end
|
78
|
+
|
79
|
+
db_object.save
|
80
|
+
db_object
|
81
|
+
end
|
82
|
+
|
83
|
+
def get_id(object)
|
84
|
+
return 0 if object.nil?
|
85
|
+
object.id
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'tfs_graph/extensions'
|
3
|
+
|
4
|
+
module TFSGraph
|
5
|
+
class RepositoryRegistry
|
6
|
+
include Extensions
|
7
|
+
include Singleton
|
8
|
+
|
9
|
+
TYPES = %w(branch changeset project)
|
10
|
+
|
11
|
+
def self.register
|
12
|
+
# assume re-registering means we want to clear existing repos
|
13
|
+
instance.reset!
|
14
|
+
yield instance if block_given?
|
15
|
+
|
16
|
+
instance
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
reset!
|
21
|
+
end
|
22
|
+
|
23
|
+
def reset!
|
24
|
+
@base_repo = nil
|
25
|
+
|
26
|
+
TYPES.each do |type|
|
27
|
+
instance_variable_set repo_memo(type), nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def identifier
|
32
|
+
(@base_repo.name =~ /Neo4j/i) ? "neo4j" : "redis"
|
33
|
+
end
|
34
|
+
|
35
|
+
def type(type)
|
36
|
+
@base_repo = type
|
37
|
+
end
|
38
|
+
|
39
|
+
TYPES.each do |type|
|
40
|
+
define_method "#{type}_repository" do
|
41
|
+
existing = instance_variable_get repo_memo(type)
|
42
|
+
return existing unless existing.nil?
|
43
|
+
|
44
|
+
repo = @base_repo.new constantize("TFSGraph::#{type.capitalize}")
|
45
|
+
|
46
|
+
instance_variable_set(repo_memo(type), repo)
|
47
|
+
repo
|
48
|
+
end
|
49
|
+
|
50
|
+
define_singleton_method "#{type}_repository" do
|
51
|
+
instance.send "#{type}_repository"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def repo_memo(type)
|
57
|
+
"@#{type}_repo"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'redis-namespace'
|
2
|
+
|
3
|
+
module TFSGraph
|
4
|
+
class ServerRegistry
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
DEFAULT_REDIS = {url: "redis://localhost:6379", namespace: "tfs_graph" }
|
8
|
+
|
9
|
+
def reset!
|
10
|
+
@redis = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.register
|
14
|
+
instance.reset!
|
15
|
+
|
16
|
+
yield instance if block_given?
|
17
|
+
instance
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
reset!
|
22
|
+
end
|
23
|
+
|
24
|
+
def server(server_obj=nil)
|
25
|
+
return @server if @server && server_obj.nil?
|
26
|
+
raise ArgumentError, "Need to register a server first" unless server_obj
|
27
|
+
|
28
|
+
@server = server_obj
|
29
|
+
end
|
30
|
+
|
31
|
+
def redis(url: DEFAULT_REDIS[:url], namespace: DEFAULT_REDIS[:namespace])
|
32
|
+
return @redis unless @redis.nil?
|
33
|
+
|
34
|
+
@redis = Redis::Namespace.new(namespace, redis: Redis.connect(url: url))
|
35
|
+
end
|
36
|
+
|
37
|
+
define_singleton_method :redis do |url: DEFAULT_REDIS[:url], namespace: DEFAULT_REDIS[:namespace]|
|
38
|
+
instance.redis url: url, namespace: namespace
|
39
|
+
end
|
40
|
+
|
41
|
+
define_singleton_method :server do |server_obj=nil|
|
42
|
+
instance.server server_obj
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'time'
|
2
|
+
require 'tfs_graph/server_registry'
|
2
3
|
|
3
4
|
module TFSGraph
|
4
5
|
module StoreHelpers
|
@@ -6,9 +7,7 @@ module TFSGraph
|
|
6
7
|
|
7
8
|
# flush by key so that we only disturbe our namespace
|
8
9
|
def flush_all
|
9
|
-
|
10
|
-
redis.del k
|
11
|
-
end
|
10
|
+
RepositoryRegistry.project_repository.drop_all
|
12
11
|
end
|
13
12
|
|
14
13
|
def mark_as_updated(time=nil)
|
@@ -18,14 +17,14 @@ module TFSGraph
|
|
18
17
|
|
19
18
|
def last_updated_on
|
20
19
|
date = redis.get(UPDATED_KEY)
|
21
|
-
return Time.
|
20
|
+
return Time.at(0).localtime unless date
|
22
21
|
|
23
22
|
Time.parse(date).localtime
|
24
23
|
end
|
25
24
|
|
26
25
|
private
|
27
26
|
def redis
|
28
|
-
|
27
|
+
ServerRegistry.redis
|
29
28
|
end
|
30
29
|
end
|
31
30
|
end
|