scvcs 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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