change_agent 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -0
- data/README.md +25 -0
- data/change_agent.gemspec +0 -1
- data/lib/change_agent.rb +4 -3
- data/lib/change_agent/client.rb +8 -3
- data/lib/change_agent/document.rb +43 -35
- data/lib/change_agent/version.rb +1 -1
- data/script/bootstrap +2 -0
- data/script/cibuild +14 -0
- data/script/console +2 -0
- data/test/test_change_agent_client.rb +20 -23
- data/test/test_change_agent_document.rb +27 -37
- metadata +3 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 637bed85fb1871ec16e91eccd2e654cacc3718b1
|
4
|
+
data.tar.gz: ac8434e78322e91231c2b8d446df980eb8472a94
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6b12aa9d46caeefcd917f72bf9a2ca4f514dcb55044a5a559d24031c83e8747680879a1873578e1d5875faa31b4a19831726af3dc5f4f6de0dd6de287355252a
|
7
|
+
data.tar.gz: 652be6634b377ccba3b04bcadeafae8dc314e5fa3ccced55377e3e53dc6dd46a4de6eb5c8c97a9ca448ab98912020d8404ec8bd89f2a3a807f9f71b70e403355
|
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
*A Git-backed key-value store, for tracking changes to documents and other files over time.*
|
4
4
|
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/change_agent.svg)](http://badge.fury.io/rb/change_agent) [![Build Status](https://travis-ci.org/benbalter/change_agent.svg)](https://travis-ci.org/benbalter/change_agent)
|
6
|
+
|
5
7
|
### A git-backed key value store sounds like a terrible idea. Why would you do that?
|
6
8
|
|
7
9
|
Let's say you're building a scraper to see when Members of Congress post press releases to their websites and to track how those press releases change over time. You could build a purpose-built application, store each revision in a database, and then build an interface to view all the known press releases and compare their history. Stop the insanity!
|
@@ -41,6 +43,29 @@ change_agent.get "foo/bar"
|
|
41
43
|
|
42
44
|
It's really up to you, but you'll get performance and usability bumps the more you namespace. I'd recommend thinking about what you want the Git repo to look like when browse, and work backwards from there.
|
43
45
|
|
46
|
+
### Cloning an existing repo / datastore
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
repo = "https://github.com/benbalter/some_repo"
|
50
|
+
directory = "data"
|
51
|
+
change_agent = ChangeAgent::Client.new(directory, repo)
|
52
|
+
|
53
|
+
change_agent.get("foo")
|
54
|
+
=> "bar"
|
55
|
+
```
|
56
|
+
|
57
|
+
### Pushing and pulling
|
58
|
+
|
59
|
+
Ready to push your Git repo to a server? Assuming you've already got a remote set up, it's as simple as
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
# pull in the latest data
|
63
|
+
change_agent.git.pull
|
64
|
+
|
65
|
+
# push the data
|
66
|
+
change_agent.git.push
|
67
|
+
```
|
68
|
+
|
44
69
|
## Project status
|
45
70
|
|
46
71
|
Initial proof of concept
|
data/change_agent.gemspec
CHANGED
@@ -17,7 +17,6 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
18
|
spec.require_paths = ["lib"]
|
19
19
|
|
20
|
-
spec.add_dependency "git"
|
21
20
|
spec.add_dependency "rugged"
|
22
21
|
spec.add_development_dependency "bundler", "~> 1.6"
|
23
22
|
spec.add_development_dependency "rake"
|
data/lib/change_agent.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
require_relative "change_agent/version"
|
2
2
|
require_relative "change_agent/document"
|
3
3
|
require_relative "change_agent/client"
|
4
|
-
require "
|
4
|
+
require "rugged"
|
5
|
+
require 'pathname'
|
5
6
|
|
6
7
|
module ChangeAgent
|
7
8
|
|
8
|
-
def self.init(directory=nil)
|
9
|
-
Client.new(directory)
|
9
|
+
def self.init(directory=nil, remote=nil)
|
10
|
+
Client.new(directory, remote)
|
10
11
|
end
|
11
12
|
|
12
13
|
end
|
data/lib/change_agent/client.rb
CHANGED
@@ -3,12 +3,17 @@ module ChangeAgent
|
|
3
3
|
|
4
4
|
attr_accessor :directory
|
5
5
|
|
6
|
-
def initialize(directory=nil)
|
6
|
+
def initialize(directory=nil, remote=nil)
|
7
7
|
@directory = File.expand_path(directory || Dir.pwd)
|
8
|
+
@remote = remote
|
8
9
|
end
|
9
10
|
|
10
|
-
def
|
11
|
-
@
|
11
|
+
def repo
|
12
|
+
if @remote.nil?
|
13
|
+
@repo ||= Rugged::Repository.init_at directory
|
14
|
+
else
|
15
|
+
@repo ||= Rugged::Repository.clone_at @remote, directory
|
16
|
+
end
|
12
17
|
end
|
13
18
|
|
14
19
|
def set(key, value)
|
@@ -1,11 +1,12 @@
|
|
1
1
|
module ChangeAgent
|
2
2
|
class Document
|
3
3
|
|
4
|
-
attr_accessor :key
|
5
4
|
attr_writer :contents
|
5
|
+
attr_accessor :path
|
6
|
+
alias_method :key, :path
|
6
7
|
|
7
|
-
def initialize(
|
8
|
-
@
|
8
|
+
def initialize(path, client_or_directory=nil)
|
9
|
+
@path = path
|
9
10
|
if client_or_directory.class == ChangeAgent::Client
|
10
11
|
@client = client_or_directory
|
11
12
|
else
|
@@ -13,51 +14,58 @@ module ChangeAgent
|
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
16
|
-
def
|
17
|
-
@client.
|
18
|
-
end
|
19
|
-
|
20
|
-
# base dir for repo
|
21
|
-
def base_dir
|
22
|
-
@client.directory
|
23
|
-
end
|
24
|
-
|
25
|
-
# directory containing file
|
26
|
-
def directory
|
27
|
-
@directory ||= File.dirname(path)
|
28
|
-
end
|
29
|
-
|
30
|
-
def path
|
31
|
-
File.expand_path key, base_dir
|
32
|
-
end
|
33
|
-
|
34
|
-
def exists?
|
35
|
-
File.exists? path
|
17
|
+
def repo
|
18
|
+
@client.repo
|
36
19
|
end
|
37
20
|
|
38
21
|
def contents
|
39
|
-
@contents ||=
|
40
|
-
|
22
|
+
@contents ||= begin
|
23
|
+
tree = repo.head.target.tree
|
24
|
+
blob = repo.lookup tree.path(path)[:oid]
|
25
|
+
blob.content
|
26
|
+
end
|
27
|
+
rescue Rugged::ReferenceError, Rugged::TreeError
|
41
28
|
nil
|
42
29
|
end
|
43
30
|
|
44
31
|
def write
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
end
|
32
|
+
clean_path
|
33
|
+
oid = repo.write contents, :blob
|
34
|
+
repo.index.add(path: path, oid: oid, mode: 0100644)
|
49
35
|
|
50
|
-
|
51
|
-
|
52
|
-
|
36
|
+
Rugged::Commit.create repo,
|
37
|
+
message: "Updating #{path}",
|
38
|
+
parents: repo.empty? ? [] : [ repo.head.target ],
|
39
|
+
tree: repo.index.write_tree(repo),
|
40
|
+
update_ref: 'HEAD'
|
53
41
|
end
|
54
42
|
|
55
|
-
def delete
|
56
|
-
|
43
|
+
def delete(file=path)
|
44
|
+
repo.index.remove(file)
|
45
|
+
|
46
|
+
Rugged::Commit.create repo,
|
47
|
+
message: "Removing #{path}",
|
48
|
+
parents: [repo.head.target],
|
49
|
+
tree: repo.index.write_tree(repo),
|
50
|
+
update_ref: 'HEAD'
|
57
51
|
end
|
58
52
|
|
59
53
|
def inspect
|
60
|
-
"#<ChangeAgent::Document
|
54
|
+
"#<ChangeAgent::Document path=\"#{path}\">"
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def clean_path
|
60
|
+
return if repo.empty?
|
61
|
+
dirs = []
|
62
|
+
tree = repo.head.target.tree
|
63
|
+
path.split("/").each do |part|
|
64
|
+
file = dirs.push(part).join("/")
|
65
|
+
delete(file) if tree.path(file)
|
66
|
+
end
|
67
|
+
rescue Rugged::TreeError
|
68
|
+
nil
|
61
69
|
end
|
62
70
|
end
|
63
71
|
end
|
data/lib/change_agent/version.rb
CHANGED
data/script/bootstrap
CHANGED
data/script/cibuild
CHANGED
@@ -1 +1,15 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
git config --get user.name > /dev/null
|
4
|
+
if [ $? -eq 1 ]; then
|
5
|
+
git config --global user.name "Your Name"
|
6
|
+
fi
|
7
|
+
|
8
|
+
git config --get user.email > /dev/null
|
9
|
+
if [ $? -eq 1 ]; then
|
10
|
+
git config --global user.email "you@example.com"
|
11
|
+
fi
|
12
|
+
|
1
13
|
bundle exec rake test
|
14
|
+
|
15
|
+
rm -Rf tmp
|
data/script/console
CHANGED
@@ -7,53 +7,50 @@ class TestChangeAgentClient < Minitest::Test
|
|
7
7
|
@client = ChangeAgent::Client.new(tempdir)
|
8
8
|
end
|
9
9
|
|
10
|
+
def teardown
|
11
|
+
FileUtils.rm_rf tempdir
|
12
|
+
end
|
13
|
+
|
10
14
|
should "set the directory on init" do
|
11
15
|
assert_equal tempdir, @client.directory
|
12
16
|
end
|
13
17
|
|
18
|
+
should "clone into existing repos" do
|
19
|
+
repo = "https://github.com/benbalter/change_agent"
|
20
|
+
agent = ChangeAgent::Client.new(tempdir, repo)
|
21
|
+
assert_equal repo, agent.repo.remotes.first.url
|
22
|
+
assert Dir.entries(tempdir).count > 5
|
23
|
+
end
|
24
|
+
|
14
25
|
should "default to the pwd" do
|
15
26
|
assert_equal Dir.pwd, ChangeAgent::Client.new.directory
|
16
27
|
end
|
17
28
|
|
18
29
|
should "init the git object" do
|
19
|
-
assert_equal
|
20
|
-
|
21
|
-
assert_equal expected, @client.git.repo.path
|
30
|
+
assert_equal Rugged::Repository, @client.repo.class
|
31
|
+
assert_equal tempdir + "/.git/", @client.repo.path
|
22
32
|
end
|
23
33
|
|
24
34
|
should "store a value" do
|
25
35
|
@client.set "foo", "bar"
|
26
|
-
|
27
|
-
|
28
|
-
assert_equal "bar",
|
36
|
+
tree = @client.repo.head.target.tree
|
37
|
+
blob = @client.repo.lookup tree["foo"][:oid]
|
38
|
+
assert_equal "bar", blob.content
|
29
39
|
end
|
30
40
|
|
31
41
|
should "store a namespaced value" do
|
32
42
|
@client.set "foo/bar", "baz"
|
33
|
-
|
34
|
-
|
35
|
-
assert_equal "baz",
|
43
|
+
tree = @client.repo.head.target.tree
|
44
|
+
blob = @client.repo.lookup tree.path("foo/bar")[:oid]
|
45
|
+
assert_equal "baz", blob.content
|
36
46
|
end
|
37
47
|
|
38
48
|
should "retrieve a file's value" do
|
39
|
-
file = File.expand_path "foo", tempdir
|
40
|
-
File.write file, "bar"
|
41
|
-
assert_equal "bar", @client.get("foo")
|
42
|
-
end
|
43
|
-
|
44
|
-
should "retrive a namespaced file's value" do
|
45
|
-
file = File.expand_path "foo/bar", tempdir
|
46
|
-
FileUtils.mkdir_p File.dirname(file)
|
47
|
-
File.write file, "baz"
|
48
|
-
assert_equal "baz", @client.get("foo/bar")
|
49
|
-
end
|
50
|
-
|
51
|
-
should "round trip a value" do
|
52
49
|
@client.set "foo", "bar"
|
53
50
|
assert_equal "bar", @client.get("foo")
|
54
51
|
end
|
55
52
|
|
56
|
-
should "
|
53
|
+
should "retrive a namespaced file's value" do
|
57
54
|
@client.set "foo/bar", "baz"
|
58
55
|
assert_equal "baz", @client.get("foo/bar")
|
59
56
|
end
|
@@ -4,12 +4,17 @@ class TestChangeAgentDocument < Minitest::Test
|
|
4
4
|
|
5
5
|
def setup
|
6
6
|
init_tempdir
|
7
|
-
@
|
8
|
-
@
|
7
|
+
@client = ChangeAgent::Client.new tempdir
|
8
|
+
@document = ChangeAgent::Document.new("foo", @client)
|
9
|
+
@namespaced_document = ChangeAgent::Document.new("bar/foo", @client)
|
9
10
|
end
|
10
11
|
|
11
|
-
|
12
|
-
|
12
|
+
def teardown
|
13
|
+
FileUtils.rm_rf tempdir
|
14
|
+
end
|
15
|
+
|
16
|
+
should "store the document path on init" do
|
17
|
+
assert_equal "foo", @document.path
|
13
18
|
end
|
14
19
|
|
15
20
|
should "accept a client if passed" do
|
@@ -22,58 +27,43 @@ class TestChangeAgentDocument < Minitest::Test
|
|
22
27
|
end
|
23
28
|
|
24
29
|
should "expose the git client" do
|
25
|
-
assert_equal
|
26
|
-
end
|
27
|
-
|
28
|
-
should "calcuate the base_dir" do
|
29
|
-
assert_equal tempdir, @document.base_dir
|
30
|
-
end
|
31
|
-
|
32
|
-
should "calcuate the directory" do
|
33
|
-
assert_equal tempdir, @document.directory
|
34
|
-
|
35
|
-
expected = File.expand_path "bar", tempdir
|
36
|
-
assert_equal expected, @namespaced_document.directory
|
37
|
-
end
|
38
|
-
|
39
|
-
should "know the file path" do
|
40
|
-
expected = File.expand_path "foo", tempdir
|
41
|
-
assert_equal expected, @document.path
|
42
|
-
|
43
|
-
expected = File.expand_path "bar/foo", tempdir
|
44
|
-
assert_equal expected, @namespaced_document.path
|
45
|
-
end
|
46
|
-
|
47
|
-
should "know if a file exists" do
|
48
|
-
refute @document.exists?
|
49
|
-
FileUtils.touch @document.path
|
50
|
-
assert @document.exists?
|
30
|
+
assert_equal Rugged::Repository, @document.repo.class
|
51
31
|
end
|
52
32
|
|
53
33
|
should "read a file's contents" do
|
54
|
-
|
34
|
+
@document.contents = "bar"
|
35
|
+
@document.write
|
55
36
|
assert_equal "bar", @document.contents
|
56
37
|
end
|
57
38
|
|
58
39
|
should "write a file's contents" do
|
59
40
|
@document.contents = "bar"
|
60
|
-
assert_equal "bar", @document.contents
|
61
41
|
@document.write
|
62
|
-
assert_equal "bar",
|
42
|
+
assert_equal "bar", @client.get("foo")
|
63
43
|
end
|
64
44
|
|
65
45
|
should "commit the document to the repo" do
|
66
46
|
@document.contents = "bar"
|
67
47
|
@document.write
|
68
|
-
assert_equal
|
69
|
-
assert_equal "Updating #{@document.path}", @document.git.log.first.message
|
48
|
+
assert_equal "Updating #{@document.key}", @document.repo.last_commit.message
|
70
49
|
end
|
71
50
|
|
72
51
|
should "delete the document" do
|
73
52
|
@document.contents = "bar"
|
74
53
|
@document.write
|
75
|
-
assert
|
54
|
+
assert @client.get "foo"
|
76
55
|
@document.delete
|
77
|
-
refute
|
56
|
+
refute @client.get "foo"
|
57
|
+
assert_equal "Removing #{@document.key}", @document.repo.last_commit.message
|
58
|
+
end
|
59
|
+
|
60
|
+
should "clobber conflicting namespace" do
|
61
|
+
@document.contents = "bar"
|
62
|
+
@document.write
|
63
|
+
|
64
|
+
doc = ChangeAgent::Document.new("foo/bar", @client)
|
65
|
+
doc.contents = "baz"
|
66
|
+
doc.write
|
67
|
+
assert_equal "baz", @client.get("foo/bar")
|
78
68
|
end
|
79
69
|
end
|
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: change_agent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Balter
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-11-
|
11
|
+
date: 2014-11-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: git
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '0'
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: rugged
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -102,6 +88,7 @@ extensions: []
|
|
102
88
|
extra_rdoc_files: []
|
103
89
|
files:
|
104
90
|
- ".gitignore"
|
91
|
+
- ".travis.yml"
|
105
92
|
- Gemfile
|
106
93
|
- LICENSE.txt
|
107
94
|
- README.md
|