git_dump 0.1.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.
- checksums.yaml +15 -0
- data/.gitignore +14 -0
- data/.rubocop.yml +62 -0
- data/.travis.yml +37 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +75 -0
- data/git_dump.gemspec +22 -0
- data/lib/git_dump.rb +41 -0
- data/lib/git_dump/cmd.rb +118 -0
- data/lib/git_dump/entry.rb +31 -0
- data/lib/git_dump/path_object.rb +11 -0
- data/lib/git_dump/repo.rb +42 -0
- data/lib/git_dump/repo/git.rb +331 -0
- data/lib/git_dump/repo/rugged.rb +97 -0
- data/lib/git_dump/tree.rb +31 -0
- data/lib/git_dump/tree/base.rb +54 -0
- data/lib/git_dump/tree/builder.rb +62 -0
- data/lib/git_dump/version.rb +55 -0
- data/lib/git_dump/version/base.rb +21 -0
- data/lib/git_dump/version/builder.rb +77 -0
- data/script/benchmark +81 -0
- data/spec/git_dump_spec.rb +392 -0
- data/spec/spec_helper.rb +0 -0
- metadata +97 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'git_dump/path_object'
|
2
|
+
require 'git_dump/tree/base'
|
3
|
+
require 'git_dump/entry'
|
4
|
+
|
5
|
+
class GitDump
|
6
|
+
# Interface to git tree
|
7
|
+
class Tree < PathObject
|
8
|
+
include Base
|
9
|
+
|
10
|
+
attr_reader :sha
|
11
|
+
def initialize(repo, dir, name, sha)
|
12
|
+
super(repo, dir, name)
|
13
|
+
@sha = sha
|
14
|
+
@entries = read_entries
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def read_entries
|
20
|
+
entries = {}
|
21
|
+
repo.tree_entries(sha).each do |entry|
|
22
|
+
entries[entry[:name]] = if entry[:type] == :tree
|
23
|
+
self.class.new(repo, path, entry[:name], entry[:sha])
|
24
|
+
else
|
25
|
+
Entry.new(repo, path, entry[:name], entry[:sha], entry[:mode])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
entries
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'git_dump/path_object'
|
2
|
+
require 'git_dump/entry'
|
3
|
+
|
4
|
+
class GitDump
|
5
|
+
class Tree < PathObject
|
6
|
+
# Common methods in Tree and Builder
|
7
|
+
module Base
|
8
|
+
# Retrive tree or entry at path, return nil if there is nothing at path
|
9
|
+
def [](path)
|
10
|
+
get_at(parse_path(path))
|
11
|
+
end
|
12
|
+
|
13
|
+
# Iterate over every tree/entry of this tree, return enumerator if no
|
14
|
+
# block given
|
15
|
+
def each(&block)
|
16
|
+
return to_enum(:each) unless block
|
17
|
+
@entries.each do |_, entry|
|
18
|
+
block[entry]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Iterate over all entries recursively, return enumerator if no block
|
23
|
+
# given
|
24
|
+
def each_recursive(&block)
|
25
|
+
return to_enum(:each_recursive) unless block
|
26
|
+
@entries.each do |_, entry|
|
27
|
+
if entry.is_a?(Entry)
|
28
|
+
block[entry]
|
29
|
+
else
|
30
|
+
entry.each_recursive(&block)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def get_at(parts)
|
38
|
+
return unless (entry = @entries[parts.first])
|
39
|
+
if parts.length == 1
|
40
|
+
entry
|
41
|
+
elsif entry.is_a?(self.class)
|
42
|
+
entry.get_at(parts.drop(1))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def parse_path(path)
|
49
|
+
path = Array(path).join('/') unless path.is_a?(String)
|
50
|
+
path.scan(/[^\/]+/)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'git_dump/path_object'
|
2
|
+
require 'git_dump/entry'
|
3
|
+
|
4
|
+
class GitDump
|
5
|
+
class Tree < PathObject
|
6
|
+
# Creating tree
|
7
|
+
class Builder < PathObject
|
8
|
+
include Base
|
9
|
+
|
10
|
+
def initialize(repo, dir, name)
|
11
|
+
super(repo, dir, name)
|
12
|
+
@entries = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
# Store data `content` with mode `mode` at `path`
|
16
|
+
# Pass `nil` as content to remove
|
17
|
+
def store(path, content, mode = 0644)
|
18
|
+
put_at(parse_path(path), content && repo.data_sha(content), mode)
|
19
|
+
end
|
20
|
+
alias_method :[]=, :store
|
21
|
+
|
22
|
+
# Store data from `from` with mode `mode` (by default file mode) at `path`
|
23
|
+
def store_from(path, from, mode = nil)
|
24
|
+
mode ||= File.stat(from).mode
|
25
|
+
put_at(parse_path(path), repo.path_sha(from), mode)
|
26
|
+
end
|
27
|
+
|
28
|
+
def sha
|
29
|
+
repo.treeify(@entries.map do |name, entry|
|
30
|
+
attributes = {:name => name, :sha => entry.sha}
|
31
|
+
if entry.is_a?(self.class)
|
32
|
+
attributes.merge(:type => :tree)
|
33
|
+
else
|
34
|
+
attributes.merge(:type => :blob, :mode => entry.mode)
|
35
|
+
end
|
36
|
+
end)
|
37
|
+
end
|
38
|
+
|
39
|
+
def inspect
|
40
|
+
"#<#{self.class} #{@entries.inspect}>"
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
def put_at(parts, sha, mode)
|
46
|
+
name = parts.shift
|
47
|
+
if parts.empty?
|
48
|
+
if sha.nil?
|
49
|
+
@entries.delete(name)
|
50
|
+
else
|
51
|
+
@entries[name] = Entry.new(repo, path, name, sha, mode)
|
52
|
+
end
|
53
|
+
else
|
54
|
+
unless @entries[name].is_a?(self.class)
|
55
|
+
@entries[name] = self.class.new(repo, path, name)
|
56
|
+
end
|
57
|
+
@entries[name].put_at(parts, sha, mode)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'git_dump/version/base'
|
2
|
+
require 'git_dump/tree'
|
3
|
+
|
4
|
+
class GitDump
|
5
|
+
# Reading version
|
6
|
+
class Version
|
7
|
+
include Base
|
8
|
+
|
9
|
+
def self.list(repo)
|
10
|
+
repo.tag_entries.map do |entry|
|
11
|
+
Version.new(repo, entry[:name], entry[:sha], {
|
12
|
+
:time => entry[:author_time],
|
13
|
+
:commit_time => entry[:commit_time],
|
14
|
+
:annotation => entry[:tag_message],
|
15
|
+
:description => entry[:commit_message],
|
16
|
+
})
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.by_id(repo, id)
|
21
|
+
list(repo).find{ |version| version.id == id }
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :repo, :id, :sha, :time
|
25
|
+
attr_reader :commit_time, :annotation, :description
|
26
|
+
def initialize(repo, id, sha, attributes = {})
|
27
|
+
@repo, @id, @sha = repo, id, sha
|
28
|
+
@time = attributes[:time]
|
29
|
+
@commit_time = attributes[:commit_time]
|
30
|
+
@annotation = attributes[:annotation]
|
31
|
+
@description = attributes[:description]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Send this version to repo at url
|
35
|
+
# Use :progress => true to show progress
|
36
|
+
def push(url, options = {})
|
37
|
+
repo.push(url, id, options)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Remove this version
|
41
|
+
def remove
|
42
|
+
repo.remove_tag(id)
|
43
|
+
end
|
44
|
+
|
45
|
+
def inspect
|
46
|
+
"#<#{self.class} id=#{@id} sha=#{@sha} tree=#{@tree.inspect}>"
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def tree
|
52
|
+
@tree ||= Tree.new(repo, nil, nil, sha)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class GitDump
|
2
|
+
class Version
|
3
|
+
# Common methods in Version and Builder
|
4
|
+
module Base
|
5
|
+
# Retrive tree or entry at path, return nil if there is nothing at path
|
6
|
+
def [](path)
|
7
|
+
tree[path]
|
8
|
+
end
|
9
|
+
|
10
|
+
# Iterate over every tree/entry at root level
|
11
|
+
def each(&block)
|
12
|
+
tree.each(&block)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Iterate over all entries recursively
|
16
|
+
def each_recursive(&block)
|
17
|
+
tree.each_recursive(&block)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'git_dump/version'
|
2
|
+
require 'git_dump/version/base'
|
3
|
+
require 'git_dump/tree/builder'
|
4
|
+
|
5
|
+
class GitDump
|
6
|
+
class Version
|
7
|
+
# Creating version
|
8
|
+
class Builder
|
9
|
+
include Base
|
10
|
+
|
11
|
+
attr_reader :repo
|
12
|
+
def initialize(repo)
|
13
|
+
@repo = repo
|
14
|
+
@tree = Tree::Builder.new(repo, nil, nil)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Store data `content` with mode `mode` at `path`
|
18
|
+
# Pass `nil` as content to remove
|
19
|
+
def store(path, content, mode = 0644)
|
20
|
+
tree.store(path, content, mode)
|
21
|
+
end
|
22
|
+
alias_method :[]=, :store
|
23
|
+
|
24
|
+
# Store data from `from` with mode `mode` (by default file mode) at `path`
|
25
|
+
def store_from(path, from, mode = nil)
|
26
|
+
tree.store_from(path, from, mode)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Create commit and tag it, returns Version instance
|
30
|
+
# Options:
|
31
|
+
# :time - set version time (tag and commit)
|
32
|
+
# :tags - list of strings to associate with this version
|
33
|
+
# :annotation - tag message
|
34
|
+
# :description - commit message
|
35
|
+
# :keep_identity - don't override identity with "git_dump
|
36
|
+
# gitdump@hostname"
|
37
|
+
def commit(options = {})
|
38
|
+
options = {:time => Time.now}.merge(options)
|
39
|
+
|
40
|
+
base = {:time => options[:time]}
|
41
|
+
unless options[:keep_identity]
|
42
|
+
base[:name] = 'git_dump'
|
43
|
+
base[:email] = "git_dump@#{GitDump.hostname}"
|
44
|
+
end
|
45
|
+
|
46
|
+
commit_sha = repo.commit(tree.sha, base.merge({
|
47
|
+
:message => options[:description],
|
48
|
+
}))
|
49
|
+
|
50
|
+
tag_name = repo.tag(commit_sha, name_parts(options), base.merge({
|
51
|
+
:message => options[:annotation],
|
52
|
+
}))
|
53
|
+
|
54
|
+
repo.gc(:auto => true)
|
55
|
+
|
56
|
+
Version.by_id(repo, tag_name)
|
57
|
+
end
|
58
|
+
|
59
|
+
def inspect
|
60
|
+
"#<#{self.class} tree=#{tree.inspect}>"
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
attr_reader :tree
|
66
|
+
|
67
|
+
def name_parts(options)
|
68
|
+
[
|
69
|
+
options[:time].dup.utc.strftime('%Y-%m-%d_%H-%M-%S'),
|
70
|
+
GitDump.hostname,
|
71
|
+
Array(options[:tags]).join(','),
|
72
|
+
GitDump.uuid,
|
73
|
+
]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/script/benchmark
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'bundler/setup'
|
6
|
+
|
7
|
+
require 'git_dump'
|
8
|
+
require 'benchmark'
|
9
|
+
require 'tmpdir'
|
10
|
+
|
11
|
+
def measure(label, number = nil, &block)
|
12
|
+
label = "#{label} x#{number}" if number
|
13
|
+
label = "#{label}:"
|
14
|
+
|
15
|
+
result = nil
|
16
|
+
print label.ljust(30)
|
17
|
+
times = Benchmark.measure do
|
18
|
+
(number || 1).times do |n|
|
19
|
+
result = block.call(n)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
puts " #{times}"
|
23
|
+
result
|
24
|
+
end
|
25
|
+
|
26
|
+
puts "Using #{defined?(Rugged) ? 'libgit2' : 'git commands'}:"
|
27
|
+
|
28
|
+
urandom = File.open('/dev/urandom')
|
29
|
+
|
30
|
+
Dir.mktmpdir do |dir|
|
31
|
+
dump = measure 'init' do
|
32
|
+
GitDump.new(File.join(dir, 'bm.git'), :create => true)
|
33
|
+
end
|
34
|
+
|
35
|
+
builder = dump.new_version
|
36
|
+
|
37
|
+
[
|
38
|
+
['1K', 1_000, 1024],
|
39
|
+
['10M', 10, 10 * 1024 * 1024],
|
40
|
+
].each do |label, times, size|
|
41
|
+
datas = Array.new(times) do
|
42
|
+
urandom.read(size)
|
43
|
+
end.each
|
44
|
+
measure "store #{label} strings", times do |n|
|
45
|
+
path = format('data%s%08d', label, n).scan(/.{1,2}/).join('/')
|
46
|
+
data = datas.next
|
47
|
+
builder.store(path, data)
|
48
|
+
end
|
49
|
+
|
50
|
+
files = Array.new(times) do |i|
|
51
|
+
path = File.join(dir, "file#{i}")
|
52
|
+
File.open(path, 'w') do |f|
|
53
|
+
f.write(urandom.read(size))
|
54
|
+
end
|
55
|
+
path
|
56
|
+
end.each
|
57
|
+
measure "store #{label} files", times do |n|
|
58
|
+
path = format('file%s%08d', label, n).scan(/.{1,2}/).join('/')
|
59
|
+
file = files.next
|
60
|
+
builder.store_from(path, file)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
measure 'commit', 10 do
|
65
|
+
builder.commit
|
66
|
+
end
|
67
|
+
|
68
|
+
measure 'read versions', 100 do
|
69
|
+
dump.versions
|
70
|
+
end
|
71
|
+
|
72
|
+
entries = dump.versions.first.each_recursive.to_a
|
73
|
+
|
74
|
+
measure 'read entries', entries.length do |n|
|
75
|
+
entries[n].read
|
76
|
+
end
|
77
|
+
|
78
|
+
measure 'read entries to disk', entries.length do |n|
|
79
|
+
entries[n].write_to(File.join(dir, 'out'))
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,392 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'git_dump'
|
3
|
+
require 'tmpdir'
|
4
|
+
|
5
|
+
describe GitDump do
|
6
|
+
around do |example|
|
7
|
+
Dir.mktmpdir do |dir|
|
8
|
+
@tmp_dir = dir
|
9
|
+
example.run
|
10
|
+
end
|
11
|
+
end
|
12
|
+
let(:tmp_dir){ @tmp_dir }
|
13
|
+
|
14
|
+
DATAS = {
|
15
|
+
:text => "\r\n\r\nline\nline\rline\r\nline\n\rline\r\n\r\n",
|
16
|
+
:binary => 256.times.sort_by{ rand }.pack('C*'),
|
17
|
+
}
|
18
|
+
|
19
|
+
describe :new do
|
20
|
+
let(:path){ File.join(tmp_dir, 'dump') }
|
21
|
+
|
22
|
+
it 'initializes with bare git repo' do
|
23
|
+
system('git', 'init', '-q', '--bare', path)
|
24
|
+
expect(GitDump.new(path).path).to eq(path)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'initializes with full git repo' do
|
28
|
+
system('git', 'init', '-q', path)
|
29
|
+
expect(GitDump.new(path).path).to eq(path)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'creates bare git repo' do
|
33
|
+
dump = GitDump.new path, :create => true
|
34
|
+
Dir.chdir path do
|
35
|
+
expect(`git rev-parse --git-dir`.strip).to eq('.')
|
36
|
+
expect(`git rev-parse --is-bare-repository`.strip).to eq('true')
|
37
|
+
end
|
38
|
+
expect(dump.path).to eq(path)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'creates full git repo' do
|
42
|
+
dump = GitDump.new path, :create => :non_bare
|
43
|
+
Dir.chdir path do
|
44
|
+
expect(`git rev-parse --git-dir`.strip).to eq('.git')
|
45
|
+
expect(`git rev-parse --is-bare-repository`.strip).to eq('false')
|
46
|
+
end
|
47
|
+
expect(dump.path).to eq(path)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'raises if dump does not exist and not asked to create' do
|
51
|
+
expect{ GitDump.new path }.to raise_error GitDump::Repo::InitException
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'raises if path is a file' do
|
55
|
+
File.open(path, 'w'){}
|
56
|
+
expect{ GitDump.new path }.to raise_error GitDump::Repo::InitException
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'raises if dump is a directory but not a git repository' do
|
60
|
+
Dir.mkdir(path)
|
61
|
+
expect{ GitDump.new path }.to raise_error GitDump::Repo::InitException
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context do
|
66
|
+
let(:dump){ GitDump.new File.join(tmp_dir, 'dump'), :create => true }
|
67
|
+
|
68
|
+
it 'returns empty list for empty repo versions' do
|
69
|
+
expect(dump.versions).to be_empty
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'creates version for every commit' do
|
73
|
+
builder = dump.new_version
|
74
|
+
3.times{ builder.commit }
|
75
|
+
|
76
|
+
expect(dump.versions.length).to eq(3)
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'builds id from time, hostname and uuid' do
|
80
|
+
allow(GitDump).to receive(:hostname).and_return('ivans')
|
81
|
+
time = Time.utc(2000, 10, 20, 12, 34, 56)
|
82
|
+
|
83
|
+
builder = dump.new_version
|
84
|
+
built = builder.commit(:time => time)
|
85
|
+
|
86
|
+
expect(built.id).to match(%r{
|
87
|
+
\A
|
88
|
+
2000-10-20_12-34-56
|
89
|
+
/
|
90
|
+
ivans
|
91
|
+
/
|
92
|
+
(?i:
|
93
|
+
[0-9a-f]{8}-
|
94
|
+
[0-9a-f]{4}-
|
95
|
+
4[0-9a-f]{3}-
|
96
|
+
[89ab][0-9a-f]{3}-
|
97
|
+
[0-9a-f]{12}
|
98
|
+
)
|
99
|
+
\z
|
100
|
+
}x)
|
101
|
+
end
|
102
|
+
|
103
|
+
[
|
104
|
+
'hello,world,foo,bar_',
|
105
|
+
%w[hello world foo bar_],
|
106
|
+
'hello,world,foo,bar!@#$%^&*()',
|
107
|
+
].each do |tags|
|
108
|
+
it 'puts tags in name' do
|
109
|
+
builder = dump.new_version
|
110
|
+
built = builder.commit(:tags => tags)
|
111
|
+
|
112
|
+
expect(built.id.split('/')).to include('hello,world,foo,bar_')
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'sets and reads version time' do
|
117
|
+
time = Time.parse('2000-10-20 12:34:56')
|
118
|
+
|
119
|
+
builder = dump.new_version
|
120
|
+
built = builder.commit(:time => time)
|
121
|
+
|
122
|
+
expect(built.time).to eq(time)
|
123
|
+
expect(dump.versions.first.time).to eq(time)
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'reads commit time' do
|
127
|
+
builder = dump.new_version
|
128
|
+
from = Time.at(Time.now.to_i) # round down to second
|
129
|
+
built = builder.commit(:time => Time.parse('2000-10-20 12:34:56'))
|
130
|
+
to = Time.now
|
131
|
+
|
132
|
+
expect(built.commit_time).to be_between(from, to)
|
133
|
+
expect(dump.versions.first.commit_time).to be_between(from, to)
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'sets and reads version annotation' do
|
137
|
+
message = DATAS[:text] + File.read(__FILE__)
|
138
|
+
|
139
|
+
builder = dump.new_version
|
140
|
+
built = builder.commit(:annotation => message)
|
141
|
+
|
142
|
+
expect(built.annotation).to eq(message)
|
143
|
+
expect(dump.versions.first.annotation).to eq(message)
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'sets and reads version description' do
|
147
|
+
message = DATAS[:text] + File.read(__FILE__)
|
148
|
+
|
149
|
+
builder = dump.new_version
|
150
|
+
built = builder.commit(:description => message)
|
151
|
+
|
152
|
+
expect(built.description).to eq(message)
|
153
|
+
expect(dump.versions.first.description).to eq(message)
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'creates and reads version' do
|
157
|
+
builder = dump.new_version
|
158
|
+
builder['string/x'] = 'test a'
|
159
|
+
builder.store('stringio/x', StringIO.new('test b'), 0644)
|
160
|
+
builder.store('io/x', File.open(__FILE__), 0755)
|
161
|
+
builder.store_from('path/x', __FILE__)
|
162
|
+
built = builder.commit
|
163
|
+
|
164
|
+
reinit_dump = GitDump.new(dump.path)
|
165
|
+
|
166
|
+
expect(reinit_dump.versions.length).to eq(1)
|
167
|
+
|
168
|
+
version = reinit_dump.versions.first
|
169
|
+
|
170
|
+
expect(version.id).to eq(built.id)
|
171
|
+
|
172
|
+
expect(version['string/x'].read).to eq('test a')
|
173
|
+
expect(version['stringio/x'].read).to eq('test b')
|
174
|
+
expect(version['io/x'].read).to eq(File.read(__FILE__))
|
175
|
+
expect(version['path/x'].read).to eq(File.read(__FILE__))
|
176
|
+
expect(version['should/not/be/there']).to be_nil
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'removes version' do
|
180
|
+
builder = dump.new_version
|
181
|
+
builder['a'] = 'b'
|
182
|
+
built = builder.commit
|
183
|
+
|
184
|
+
expect(dump.versions.length).to eq(1)
|
185
|
+
|
186
|
+
built.remove
|
187
|
+
|
188
|
+
expect(dump.versions).to be_empty
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'returns path and name for trees and entries' do
|
192
|
+
builder = dump.new_version
|
193
|
+
builder['a/b/c'] = 'test'
|
194
|
+
|
195
|
+
%w[
|
196
|
+
a
|
197
|
+
a/b
|
198
|
+
a/b/c
|
199
|
+
].each do |path|
|
200
|
+
expect(builder[path].path).to eq(path)
|
201
|
+
expect(builder[path].name).to eq(path.split('/').last)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'cleans paths' do
|
206
|
+
builder = dump.new_version
|
207
|
+
builder['//aa//fa//'] = 'test a'
|
208
|
+
|
209
|
+
expect(builder['//aa//fa//'].read).to eq('test a')
|
210
|
+
expect(builder['aa/fa//'].read).to eq('test a')
|
211
|
+
expect(builder['aa/fa'].read).to eq('test a')
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'replaces tree branches' do
|
215
|
+
builder = dump.new_version
|
216
|
+
builder['a/a/a/a'] = 'test a'
|
217
|
+
builder['a/a/a/b'] = 'test b'
|
218
|
+
builder['a/a'] = 'hello'
|
219
|
+
|
220
|
+
expect(builder['a/a/a/a']).to be_nil
|
221
|
+
expect(builder['a/a/a/b']).to be_nil
|
222
|
+
expect(builder['a/a'].read).to eq('hello')
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'removes entries and trees' do
|
226
|
+
builder = dump.new_version
|
227
|
+
builder['a/a/a'] = 'test a'
|
228
|
+
builder['a/a/b'] = 'test b'
|
229
|
+
builder['b/a'] = 'test c'
|
230
|
+
builder['b/b'] = 'test d'
|
231
|
+
|
232
|
+
builder['a/a'] = nil
|
233
|
+
builder['b/a'] = nil
|
234
|
+
|
235
|
+
version = builder.commit
|
236
|
+
|
237
|
+
expect(version.each.map(&:path)).to match_array(%w[a b])
|
238
|
+
|
239
|
+
expect(version['a'].each.map(&:path)).to be_empty
|
240
|
+
|
241
|
+
expect(version['b'].each.map(&:path)).to match_array(%w[b/b])
|
242
|
+
end
|
243
|
+
|
244
|
+
DATAS.each do |type, data|
|
245
|
+
it "does not change #{type} data" do
|
246
|
+
builder = dump.new_version
|
247
|
+
builder['a'] = data
|
248
|
+
|
249
|
+
expect(builder['a'].read).to eq(data)
|
250
|
+
end
|
251
|
+
|
252
|
+
it "does not change #{type} data read from file" do
|
253
|
+
path = File.join(tmp_dir, 'file.txt')
|
254
|
+
File.open(path, 'wb') do |f|
|
255
|
+
f.write(data)
|
256
|
+
end
|
257
|
+
|
258
|
+
builder = dump.new_version
|
259
|
+
builder.store_from('a', path)
|
260
|
+
|
261
|
+
expect(builder['a'].read).to eq(data)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
describe :traversing do
|
266
|
+
let(:version) do
|
267
|
+
builder = dump.new_version
|
268
|
+
builder['c/c/c'] = 'c\c\c'
|
269
|
+
builder['a'] = 'a'
|
270
|
+
builder['b/a'] = 'b\a'
|
271
|
+
builder['b/b'] = 'b\b'
|
272
|
+
builder
|
273
|
+
end
|
274
|
+
|
275
|
+
def recursive_path_n_read(o)
|
276
|
+
o.each_recursive.map do |entry|
|
277
|
+
[entry.path, entry.read]
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
it 'traverses entries recursively' do
|
282
|
+
expect(recursive_path_n_read(version)).to match_array([
|
283
|
+
%w[a a],
|
284
|
+
%w[b/a b\a],
|
285
|
+
%w[b/b b\b],
|
286
|
+
%w[c/c/c c\c\c],
|
287
|
+
])
|
288
|
+
|
289
|
+
expect(recursive_path_n_read(version['b'])).to match_array([
|
290
|
+
%w[b/a b\a],
|
291
|
+
%w[b/b b\b],
|
292
|
+
])
|
293
|
+
|
294
|
+
expect(recursive_path_n_read(version['c'])).to match_array([
|
295
|
+
%w[c/c/c c\c\c],
|
296
|
+
])
|
297
|
+
|
298
|
+
expect(recursive_path_n_read(version['c/c'])).to match_array([
|
299
|
+
%w[c/c/c c\c\c],
|
300
|
+
])
|
301
|
+
end
|
302
|
+
|
303
|
+
it 'traverses level' do
|
304
|
+
expect(version.each.map(&:path)).to match_array(%w[a b c])
|
305
|
+
|
306
|
+
expect(version['b'].each.map(&:path)).to match_array(%w[b/a b/b])
|
307
|
+
|
308
|
+
expect(version['c'].each.map(&:path)).to eq(%w[c/c])
|
309
|
+
|
310
|
+
expect(version['c/c'].each.map(&:path)).to eq(%w[c/c/c])
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
describe :write_to do
|
315
|
+
DATAS.each do |type, data|
|
316
|
+
it "writes back #{type} data" do
|
317
|
+
builder = dump.new_version
|
318
|
+
builder['a'] = data
|
319
|
+
|
320
|
+
path = File.join(tmp_dir, 'file')
|
321
|
+
builder['a'].write_to(path)
|
322
|
+
|
323
|
+
expect(File.open(path, 'rb', &:read)).to eq(data)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
[0644, 0755].each do |mode|
|
328
|
+
it "sets mode to #{mode.to_s(8)}" do
|
329
|
+
builder = dump.new_version
|
330
|
+
builder.store('a', 'test', mode)
|
331
|
+
|
332
|
+
path = File.join(tmp_dir, 'file')
|
333
|
+
builder['a'].write_to(path)
|
334
|
+
|
335
|
+
expect(File.stat(path).mode & 0777).to eq(mode)
|
336
|
+
end
|
337
|
+
|
338
|
+
it "fixes mode to #{mode.to_s(8)}" do
|
339
|
+
builder = dump.new_version
|
340
|
+
builder.store('a', 'test', mode & 0100)
|
341
|
+
|
342
|
+
path = File.join(tmp_dir, 'file')
|
343
|
+
builder['a'].write_to(path)
|
344
|
+
|
345
|
+
expect(File.stat(path).mode & 0777).to eq(mode)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
it 'gets remote version ids' do
|
351
|
+
builder = dump.new_version
|
352
|
+
3.times{ builder.commit }
|
353
|
+
|
354
|
+
expect(GitDump.remote_version_ids(dump.path)).
|
355
|
+
to eq(dump.versions.map(&:id))
|
356
|
+
end
|
357
|
+
|
358
|
+
describe :exchange do
|
359
|
+
let(:other_dump) do
|
360
|
+
GitDump.new File.join(tmp_dir, 'other'), :create => true
|
361
|
+
end
|
362
|
+
|
363
|
+
let(:built) do
|
364
|
+
builder = dump.new_version
|
365
|
+
builder['a'] = 'b'
|
366
|
+
builder.commit
|
367
|
+
end
|
368
|
+
|
369
|
+
def check_received_version
|
370
|
+
expect(other_dump.versions.length).to eq(1)
|
371
|
+
|
372
|
+
version = other_dump.versions.first
|
373
|
+
expect(version.id).to eq(built.id)
|
374
|
+
expect(version.each_recursive.map do |entry|
|
375
|
+
[entry.path, entry.read]
|
376
|
+
end).to eq([%w[a b]])
|
377
|
+
end
|
378
|
+
|
379
|
+
it 'pushes version' do
|
380
|
+
built.push(other_dump.path)
|
381
|
+
|
382
|
+
check_received_version
|
383
|
+
end
|
384
|
+
|
385
|
+
it 'fetches version' do
|
386
|
+
other_dump.fetch(dump.path, built.id)
|
387
|
+
|
388
|
+
check_received_version
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|