scvcs 0.2.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.
@@ -0,0 +1,38 @@
1
+ module Output
2
+ class Pager
3
+ attr_reader :pager
4
+
5
+ def initialize(raw: false)
6
+ if raw or not $stdout.isatty
7
+ @pager = nil
8
+ elsif Shell.command_exist? 'less'
9
+ @pager = 'less -R -F -X'
10
+ elsif Shell.command_exist? 'more'
11
+ @pager = 'more'
12
+ end
13
+ end
14
+
15
+ def print_block
16
+ return yield($stdout) if pager.nil?
17
+
18
+ IO.popen(pager, 'w') do |stream|
19
+ begin
20
+ $stdout = stream
21
+ yield stream
22
+ ensure
23
+ $stdout = STDOUT
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ def output(raw: false, &block)
30
+ Pager.new(raw: raw).print_block &block
31
+ end
32
+
33
+ extend self
34
+ end
35
+
36
+ class Object
37
+ include Output
38
+ end
@@ -0,0 +1,15 @@
1
+ module Shell
2
+ ##
3
+ # Detect if a command exists and can be executed using
4
+ # the OS shell.
5
+ #
6
+ # Based on http://stackoverflow.com/questions/2108727/which-in-ruby-checking-if-program-exists-in-path-from-ruby/5471032#5471032
7
+ #
8
+ def self.command_exist?(name)
9
+ extensions = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
10
+
11
+ ENV['PATH'].split(File::PATH_SEPARATOR).find do |path|
12
+ extensions.find { |ext| File.executable? File.join(path, "#{name}#{ext}") }
13
+ end
14
+ end
15
+ end
data/lib/scv.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'vcs_toolkit'
2
+
3
+ require 'scv/version'
4
+ require 'scv/objects'
5
+
6
+ require 'scv/file_store'
7
+ require 'scv/http_file_store'
8
+ require 'scv/config'
9
+ require 'scv/object_store'
10
+
11
+ require 'scv/repository'
12
+
13
+ require 'scv/server'
data/lib/scv/config.rb ADDED
@@ -0,0 +1,52 @@
1
+ require 'yaml'
2
+
3
+ module SCV
4
+ class Config
5
+ attr_accessor :data
6
+
7
+ def initialize(file_store, file_name)
8
+ @file_store = file_store
9
+ @file_name = file_name
10
+
11
+ @data = {}
12
+ load
13
+ end
14
+
15
+ def load
16
+ @data = YAML.load @file_store.fetch(@file_name) if @file_store.file? @file_name
17
+ end
18
+
19
+ def save
20
+ @file_store.store @file_name, @data.to_yaml
21
+ end
22
+
23
+ def [](key)
24
+ hash, key = resolve_key key
25
+ hash[key]
26
+ end
27
+
28
+ def []=(key, value)
29
+ hash, key = resolve_key key, create: true
30
+ hash[key] = value
31
+ end
32
+
33
+ def delete(key)
34
+ hash, key = resolve_key key, create: false
35
+ hash.delete key
36
+ end
37
+
38
+ private
39
+
40
+ def resolve_key(hash=@data, key, create: false)
41
+ key, rest = key.split '.', 2
42
+
43
+ if rest.nil?
44
+ [hash, key]
45
+ else
46
+ hash[key] = {} if create and not hash.key? key
47
+
48
+ resolve_key hash[key], rest, create: create
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,81 @@
1
+ require 'vcs_toolkit/file_store'
2
+
3
+ module SCV
4
+
5
+ class FileStore < VCSToolkit::FileStore
6
+ def initialize(path)
7
+ raise 'The path is not a directory' unless File.directory? path
8
+
9
+ @base_path = path
10
+ end
11
+
12
+ def store(path, content)
13
+ path = path_for path
14
+
15
+ FileUtils.mkdir_p File.dirname(path)
16
+
17
+ File.open(path, 'wb') do |file|
18
+ file.write(content)
19
+ end
20
+ end
21
+
22
+ def fetch(path)
23
+ raise KeyError unless file? path
24
+
25
+ File.open(path_for(path), 'rb') do |file|
26
+ file.read
27
+ end
28
+ end
29
+
30
+ def delete_file(path)
31
+ File.unlink path_for(path)
32
+ end
33
+
34
+ def delete_dir(path)
35
+ Dir.unlink path_for(path)
36
+ end
37
+
38
+ def file?(path)
39
+ File.file? path_for(path)
40
+ end
41
+
42
+ def directory?(path)
43
+ File.directory? path_for(path)
44
+ end
45
+
46
+ def changed?(path, blob)
47
+ fetch(path) != blob.content
48
+ end
49
+
50
+ def each_file(path='', &block)
51
+ path = '.' if path.empty?
52
+
53
+ Dir.entries(path_for(path)).select do |file_name|
54
+ file? path_for(file_name, path)
55
+ end.each(&block)
56
+ end
57
+
58
+ def each_directory(path='', &block)
59
+ path = '.' if path.empty?
60
+
61
+ Dir.entries(path_for(path)).select do |name|
62
+ not %w(. ..).include? name and directory? path_for(name, path)
63
+ end.each(&block)
64
+ end
65
+
66
+ private
67
+
68
+ def path_for(path, base=@base_path)
69
+ if base.empty? or base == '.'
70
+ if path.empty?
71
+ '.'
72
+ else
73
+ path
74
+ end
75
+ else
76
+ File.join(base, path)
77
+ end
78
+ end
79
+ end
80
+
81
+ end
@@ -0,0 +1,69 @@
1
+ require 'httparty'
2
+ require 'vcs_toolkit/file_store'
3
+
4
+ module SCV
5
+
6
+ class HTTPFileStore < VCSToolkit::FileStore
7
+ attr_reader :base_url
8
+
9
+ def initialize(url)
10
+ @base_url = url.sub(/\/$/, '')
11
+ end
12
+
13
+ def fetch(path)
14
+ http_response = HTTParty.get("#{base_url}/#{path}")
15
+
16
+ case http_response.code
17
+ when 200
18
+ http_response.body
19
+ when 404
20
+ raise KeyError, "The file #{base_url}/#{path} cannot be found"
21
+ else
22
+ raise "Invalid status code #{http_response.code} for #{base_url}/#{path}"
23
+ end
24
+ end
25
+
26
+ def store(path, content)
27
+ http_response = HTTParty.put("#{base_url}/#{path}", {body: content})
28
+
29
+ unless http_response.code == 200
30
+ raise "Invalid status code #{http_response.code} for #{base_url}/#{path} (put)"
31
+ end
32
+ end
33
+
34
+ def file?(path)
35
+ http_response = HTTParty.head("#{base_url}/#{path}")
36
+
37
+ case http_response.code
38
+ when 200
39
+ true
40
+ when 404
41
+ false
42
+ else
43
+ raise "Invalid status code #{http_response.code} for #{base_url}/#{path}"
44
+ end
45
+ end
46
+
47
+ # These methods should not be used currently as this class
48
+ # will only be used by the object_store.
49
+
50
+ # def delete_file(path)
51
+ # end
52
+
53
+ # def delete_dir(path)
54
+ # end
55
+
56
+ # def directory?(path)
57
+ # end
58
+
59
+ # def changed?(path, blob)
60
+ # end
61
+
62
+ # def each_file(path='', &block)
63
+ # end
64
+
65
+ # def each_directory(path='', &block)
66
+ # end
67
+ end
68
+
69
+ end
@@ -0,0 +1,125 @@
1
+ require 'vcs_toolkit/exceptions'
2
+ require 'scv/objects'
3
+
4
+ require 'json'
5
+
6
+ module SCV
7
+
8
+ ##
9
+ # Implements VCSToolkit::ObjectStore to store objects on the file system.
10
+ # The directory structure is as follows:
11
+ #
12
+ # .scv/
13
+ #
14
+ # objects/
15
+ # 59/
16
+ # 59873e99cef61a60b3826e1cbb9d4b089ae78c2b.json
17
+ # ...
18
+ # ...
19
+ #
20
+ # refs/
21
+ # HEAD.json
22
+ # master.json
23
+ # ...
24
+ #
25
+ # blobs/
26
+ # 59/
27
+ # 59873e99cef61a60b3826e1cbb9d4b089ae78c2b
28
+ # ...
29
+ # ...
30
+ #
31
+ # Each object in `.scv/objects/` is stored in a directory with a name
32
+ # of the first two symbols of the object id.
33
+ # The file extension determines the object format. Possible formats
34
+ # are `json` and `json.gz` (`json.gz` will be supported in the future).
35
+ #
36
+ # For each blob object in `.scv/blobs/` there may be a file in
37
+ # `.scv/objects/`. These blobs follow the same naming scheme as the
38
+ # objects, but they are just binary files (not in `json` format).
39
+ #
40
+ # The refs are named objects (object.named? == true) and can be
41
+ # enumerated.
42
+ #
43
+ class ObjectStore < VCSToolkit::ObjectStore
44
+ def initialize(file_store)
45
+ @store = file_store
46
+ end
47
+
48
+ def store(object_id, object)
49
+ if object.named?
50
+ object_path = get_object_path object_id, named: true
51
+ else
52
+ object_path = get_object_path object_id
53
+ end
54
+
55
+ if object.is_a? VCSToolkit::Objects::Blob
56
+ @store.store(get_blob_path(object_id), object.content)
57
+ else
58
+ @store.store(object_path, JSON.generate(object.to_hash))
59
+ end
60
+ end
61
+
62
+ def fetch(object_id)
63
+ object_location = find_object object_id
64
+
65
+ return fetch_blob(object_id) if object_location[:type] == :blob
66
+
67
+ hash = JSON.parse(@store.fetch(object_location[:path]))
68
+ object_type = hash['object_type'].capitalize
69
+
70
+ raise 'Unknown object type' unless Objects.const_defined? object_type
71
+
72
+ Objects.const_get(object_type).from_hash hash
73
+ end
74
+
75
+ ##
76
+ # A method not required by VCSToolkit used to remove
77
+ # labels or possibly garbage collection.
78
+ #
79
+ def remove(object_id)
80
+ raise KeyError, 'The object does not exit' unless key? object_id
81
+
82
+ location = find_object object_id
83
+
84
+ @store.delete_file location[:path]
85
+ end
86
+
87
+ def key?(object_id)
88
+ not find_object(object_id).nil?
89
+ end
90
+
91
+ def each(&block)
92
+ return [] unless @store.directory? 'refs'
93
+
94
+ @store.files('refs').map { |name| name.sub /\..*$/, '' }.each &block
95
+ end
96
+
97
+ private
98
+
99
+ def fetch_blob(object_id)
100
+ content = @store.fetch get_blob_path(object_id)
101
+
102
+ SCV::Objects::Blob.new id: object_id, content: content
103
+ end
104
+
105
+ def get_object_path(object_id, named: false)
106
+ if named
107
+ File.join 'refs', "#{object_id}.json"
108
+ else
109
+ File.join 'objects', object_id[0...2], "#{object_id}.json"
110
+ end
111
+ end
112
+
113
+ def get_blob_path(object_id)
114
+ File.join 'blobs', object_id.to_s[0...2], object_id.to_s
115
+ end
116
+
117
+ def find_object(object_id)
118
+ [
119
+ {path: get_object_path(object_id), type: :object},
120
+ {path: get_object_path(object_id, named: true), type: :label },
121
+ {path: get_blob_path(object_id), type: :blob },
122
+ ].find { |location| @store.file? location[:path] }
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,4 @@
1
+ require 'scv/objects/blob'
2
+ require 'scv/objects/commit'
3
+ require 'scv/objects/label'
4
+ require 'scv/objects/tree'
@@ -0,0 +1,10 @@
1
+ require 'vcs_toolkit/objects/blob'
2
+
3
+ module SCV
4
+ module Objects
5
+
6
+ class Blob < VCSToolkit::Objects::Blob
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,34 @@
1
+ require 'vcs_toolkit/objects/commit'
2
+
3
+ module SCV
4
+ module Objects
5
+
6
+ class Commit < VCSToolkit::Objects::Commit
7
+
8
+ def initialize(date:, **kwargs)
9
+ # Parse the date on deserialization
10
+ date = DateTime.parse date if date.is_a? String
11
+
12
+ super
13
+ end
14
+
15
+ def hash_objects
16
+ [@message, @tree, parents_for_hash, @author, @date.to_s]
17
+ end
18
+
19
+ private
20
+
21
+ def parents_for_hash
22
+ if parents.empty?
23
+ nil
24
+ elsif parents.size == 1
25
+ parents.first
26
+ else
27
+ parents
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+ end