vcs_toolkit 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 +7 -0
- data/LICENSE +20 -0
- data/README.md +8 -0
- data/lib/vcs_toolkit.rb +16 -0
- data/lib/vcs_toolkit/conflict.rb +55 -0
- data/lib/vcs_toolkit/diff.rb +61 -0
- data/lib/vcs_toolkit/exceptions.rb +10 -0
- data/lib/vcs_toolkit/file_store.rb +139 -0
- data/lib/vcs_toolkit/merge.rb +96 -0
- data/lib/vcs_toolkit/object_store.rb +51 -0
- data/lib/vcs_toolkit/objects.rb +5 -0
- data/lib/vcs_toolkit/objects/blob.rb +37 -0
- data/lib/vcs_toolkit/objects/commit.rb +76 -0
- data/lib/vcs_toolkit/objects/label.rb +29 -0
- data/lib/vcs_toolkit/objects/object.rb +47 -0
- data/lib/vcs_toolkit/objects/tree.rb +81 -0
- data/lib/vcs_toolkit/repository.rb +302 -0
- data/lib/vcs_toolkit/serializable.rb +23 -0
- data/lib/vcs_toolkit/utils/hashable_object.rb +45 -0
- data/lib/vcs_toolkit/utils/memory_file_store.rb +101 -0
- data/lib/vcs_toolkit/utils/status.rb +60 -0
- data/lib/vcs_toolkit/utils/sync.rb +73 -0
- data/lib/vcs_toolkit/version.rb +3 -0
- metadata +124 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2f7c92235e805cd0715922732c68622c509ea05e
|
4
|
+
data.tar.gz: f72a425fcf33a19ff060fb20a16e4818730c82e1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2b8c306c6e8ce861ae375f2090abbc7f45cbcba7c3b09d4663a86e03a185a34a8a9c7ae2ebc5bd775719cd515b6005b070e01aa4b0cac51fc595116f9bfdfdc9
|
7
|
+
data.tar.gz: 7cee17342b67f593b5abedbdc4ccd20684745a5f224c4269f2169a9c025bfbb935f850416d7478508e28d4de0ec206fb375716d549107916413e6bda28b8c266
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 Georgy Angelov
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
data/lib/vcs_toolkit.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'vcs_toolkit/version'
|
2
|
+
require 'vcs_toolkit/exceptions'
|
3
|
+
|
4
|
+
require 'vcs_toolkit/diff'
|
5
|
+
require 'vcs_toolkit/conflict'
|
6
|
+
require 'vcs_toolkit/merge'
|
7
|
+
require 'vcs_toolkit/objects'
|
8
|
+
|
9
|
+
require 'vcs_toolkit/object_store'
|
10
|
+
require 'vcs_toolkit/file_store'
|
11
|
+
|
12
|
+
require 'vcs_toolkit/repository'
|
13
|
+
|
14
|
+
require 'vcs_toolkit/utils/memory_file_store'
|
15
|
+
require 'vcs_toolkit/utils/status'
|
16
|
+
require 'vcs_toolkit/utils/sync'
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'diff-lcs'
|
2
|
+
require 'vcs_toolkit/diff'
|
3
|
+
|
4
|
+
# I know monkey patching is evil but
|
5
|
+
# this is used only for consistency in arrays
|
6
|
+
# that contain both Diff::LCS::Change and Conflict.
|
7
|
+
# So we can do this: `changes.any? { |change| change.conflict? }`
|
8
|
+
# instead of `changes.any? { |change| change.is_a? VCSToolkit::Conflict }`
|
9
|
+
class Diff::LCS::Change
|
10
|
+
def conflict?
|
11
|
+
false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module VCSToolkit
|
16
|
+
class Conflict
|
17
|
+
attr_reader :diff_one, :diff_two
|
18
|
+
|
19
|
+
def initialize(diff_one, diff_two)
|
20
|
+
@diff_one = diff_one
|
21
|
+
@diff_two = diff_two
|
22
|
+
end
|
23
|
+
|
24
|
+
def conflict?
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
# These methods are used for compatibility with ::Diff::LCS::Change
|
29
|
+
# so we can do this: `changes.any? { |change| change.adding? }`
|
30
|
+
# instead of `changes.any? { |change| (not change.is_a? VCSToolkit::Conflict) and change.adding? }`
|
31
|
+
def adding?
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
def deleting?
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
def unchanged?
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
def changed?
|
44
|
+
false
|
45
|
+
end
|
46
|
+
|
47
|
+
def finished_a?
|
48
|
+
false
|
49
|
+
end
|
50
|
+
|
51
|
+
def finished_b?
|
52
|
+
false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'diff-lcs'
|
2
|
+
|
3
|
+
module VCSToolkit
|
4
|
+
class Diff
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize(changes)
|
8
|
+
@changes = changes
|
9
|
+
end
|
10
|
+
|
11
|
+
def has_changes?
|
12
|
+
not @changes.all?(&:unchanged?)
|
13
|
+
end
|
14
|
+
|
15
|
+
def has_conflicts?
|
16
|
+
@changes.any?(&:conflict?)
|
17
|
+
end
|
18
|
+
|
19
|
+
def each(&block)
|
20
|
+
@changes.each &block
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
flat_map do |change|
|
25
|
+
if change.unchanged?
|
26
|
+
[change.new_element]
|
27
|
+
elsif change.deleting?
|
28
|
+
["-#{change.old_element}"]
|
29
|
+
elsif change.adding?
|
30
|
+
["+#{change.new_element}"]
|
31
|
+
elsif change.changed?
|
32
|
+
["-#{change.old_element}", "+#{change.new_element}"]
|
33
|
+
else
|
34
|
+
raise "Unknown change in the diff #{change.action}"
|
35
|
+
end
|
36
|
+
end.join ''
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Reconstruct the new sequence from the diff
|
41
|
+
#
|
42
|
+
def new_content(conflict_start='<<<', conflict_switch='>>>', conflict_end='===')
|
43
|
+
flat_map do |change|
|
44
|
+
if change.conflict?
|
45
|
+
version_one = change.diff_one.new_content(conflict_start, conflict_switch, conflict_end)
|
46
|
+
version_two = change.diff_two.new_content(conflict_start, conflict_switch, conflict_end)
|
47
|
+
|
48
|
+
[conflict_start] + version_one + [conflict_switch] + version_two + [conflict_end]
|
49
|
+
elsif change.deleting?
|
50
|
+
[]
|
51
|
+
else
|
52
|
+
[change.new_element]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.from_sequences(sequence_one, sequence_two)
|
58
|
+
new ::Diff::LCS.sdiff(sequence_one, sequence_two)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'vcs_toolkit/exceptions'
|
2
|
+
|
3
|
+
module VCSToolkit
|
4
|
+
##
|
5
|
+
# This class is used to implement a custom storage provider for
|
6
|
+
# files.
|
7
|
+
#
|
8
|
+
class FileStore
|
9
|
+
##
|
10
|
+
# Implement this to store a specific file in persistent storage.
|
11
|
+
#
|
12
|
+
def store(path, blob)
|
13
|
+
raise NotImplementedError, 'You must implement FileStore#store'
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Implement this to retrieve a file with the specified name.
|
18
|
+
#
|
19
|
+
def fetch(path)
|
20
|
+
raise NotImplementedError, 'You must implement FileStore#fetch'
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Implement this to delete the specified file
|
25
|
+
#
|
26
|
+
def delete_file(path)
|
27
|
+
raise NotImplementedError, 'You must implement FileStore#delete_file'
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Implement this to delete the specified directory.
|
32
|
+
# This method will be called only on empty directories.
|
33
|
+
# Use #delete if you want to recursively delete a directory.
|
34
|
+
#
|
35
|
+
def delete_dir(path)
|
36
|
+
raise NotImplementedError, 'You must implement FileStore#delete_dir'
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Implement this to detect wether a file with that name exists.
|
41
|
+
#
|
42
|
+
def file?(path)
|
43
|
+
raise NotImplementedError, 'You must implement FileStore#file?'
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Implement this to detect wether a directory with that name exists.
|
48
|
+
#
|
49
|
+
def directory?(path)
|
50
|
+
raise NotImplementedError, 'You must implement FileStore#directory?'
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Implement this to compare a file and a blob object.
|
55
|
+
# It should return boolean value to indicate wether the file is different.
|
56
|
+
#
|
57
|
+
# The hash algorithm should be the same one used in `Objects::*`.
|
58
|
+
#
|
59
|
+
def changed?(path, blob)
|
60
|
+
raise NotImplementedError, 'You must implement FileStore#changed?'
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Implement this to enumerate over all files in the given directory.
|
65
|
+
#
|
66
|
+
# The order of enumeration doesn't matter.
|
67
|
+
#
|
68
|
+
def each_file(path='')
|
69
|
+
raise NotImplementedError, 'You must implement FileStore#each_file'
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Implement this to enumerate over all files.
|
74
|
+
#
|
75
|
+
# The order of enumeration doesn't matter.
|
76
|
+
#
|
77
|
+
def each_directory(path='')
|
78
|
+
raise NotImplementedError, 'You must implement FileStore#each_directory'
|
79
|
+
end
|
80
|
+
|
81
|
+
def exist?(path)
|
82
|
+
file?(path) or directory?(path)
|
83
|
+
end
|
84
|
+
|
85
|
+
def files(path='', ignore: [])
|
86
|
+
enum_for(:each_file, path).reject { |file| ignored? file, ignore }
|
87
|
+
end
|
88
|
+
|
89
|
+
def directories(path='', ignore: [])
|
90
|
+
enum_for(:each_directory, path).reject { |file| ignored? file, ignore }
|
91
|
+
end
|
92
|
+
|
93
|
+
def all_files(path='', ignore: [])
|
94
|
+
enum_for :yield_all_files, path, ignore: ignore
|
95
|
+
end
|
96
|
+
|
97
|
+
def delete(path)
|
98
|
+
if directory? path
|
99
|
+
files(path).each do |file|
|
100
|
+
delete_file File.join(path, file)
|
101
|
+
end
|
102
|
+
|
103
|
+
directories(path).each do |directory|
|
104
|
+
delete File.join(path, directory)
|
105
|
+
end
|
106
|
+
|
107
|
+
delete_dir path
|
108
|
+
else
|
109
|
+
delete_file path
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def yield_all_files(path='', ignore: [], &block)
|
116
|
+
files(path).reject { |path| ignored? path, ignore }.each &block
|
117
|
+
|
118
|
+
directories(path).each do |dir_name|
|
119
|
+
dir_path = File.join(path, dir_name).sub(/^\/+/, '')
|
120
|
+
|
121
|
+
all_files(dir_path).each do |file|
|
122
|
+
file_path = File.join(dir_name, file)
|
123
|
+
|
124
|
+
yield file_path unless ignored?(file_path, ignore) or ignored?(file.split('/').last, ignore)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def ignored?(path, ignores)
|
130
|
+
ignores.any? do |ignore|
|
131
|
+
if ignore.is_a? Regexp
|
132
|
+
ignore =~ path
|
133
|
+
else
|
134
|
+
ignore == path
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'vcs_toolkit/diff'
|
2
|
+
require 'vcs_toolkit/conflict'
|
3
|
+
|
4
|
+
module VCSToolkit
|
5
|
+
module Merge
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def three_way(sequence_one, sequence_two, sequence_three)
|
9
|
+
diff_one = Diff.from_sequences(sequence_one, sequence_two)
|
10
|
+
diff_two = Diff.from_sequences(sequence_one, sequence_three)
|
11
|
+
|
12
|
+
combined_changes = combine_diffs diff_one, diff_two
|
13
|
+
merge_changes = combined_changes.flat_map do |line_number, (changeset_one, changeset_two)|
|
14
|
+
if changeset_one.all?(&:unchanged?)
|
15
|
+
changeset_two
|
16
|
+
elsif changeset_two.all?(&:unchanged?)
|
17
|
+
changeset_one
|
18
|
+
elsif same_changes(changeset_one, changeset_two)
|
19
|
+
# TODO: Check if they can be the same but one is split in two parts
|
20
|
+
changeset_one
|
21
|
+
else
|
22
|
+
extract_conflict(changeset_one, changeset_two)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
Diff.new merge_changes
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
##
|
32
|
+
# Return common prefix and suffix of the two changesets
|
33
|
+
# in the following format:
|
34
|
+
#
|
35
|
+
# [<common_prefix_list>, Conflict(Diff, Diff), <common_suffix_list>]
|
36
|
+
#
|
37
|
+
def extract_conflict(changeset_one, changeset_two)
|
38
|
+
common_start = changeset_one.zip(changeset_two).take_while do |change_one, change_two|
|
39
|
+
same_change(change_one, change_two)
|
40
|
+
end
|
41
|
+
|
42
|
+
common_end = changeset_one.reverse.zip(changeset_two.reverse).take_while do |change_one, change_two|
|
43
|
+
same_change(change_one, change_two)
|
44
|
+
end
|
45
|
+
|
46
|
+
common_size = common_end.size + common_start.size
|
47
|
+
|
48
|
+
diff_one = Diff.new changeset_one.slice(common_start.size, changeset_one.size - common_size)
|
49
|
+
diff_two = Diff.new changeset_two.slice(common_start.size, changeset_two.size - common_size)
|
50
|
+
|
51
|
+
common_start.map(&:first) + [Conflict.new(diff_one, diff_two)] + common_end.map(&:first)
|
52
|
+
end
|
53
|
+
|
54
|
+
def same_changes(changeset_one, changeset_two)
|
55
|
+
changeset_one.size == changeset_two.size and
|
56
|
+
changeset_one.zip(changeset_two).all? do |change_one, change_two|
|
57
|
+
same_change(change_one, change_two)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def same_change(change_one, change_two)
|
62
|
+
# new_position is not compared deliberately
|
63
|
+
# because any additions on a file will increase new_position
|
64
|
+
# and because of that it will cause conflicts even
|
65
|
+
# if the changes are the same
|
66
|
+
change_one.action == change_two.action and
|
67
|
+
change_one.old_position == change_two.old_position and
|
68
|
+
change_one.old_element == change_two.old_element and
|
69
|
+
change_one.new_element == change_two.new_element
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Group changes by their old index.
|
74
|
+
#
|
75
|
+
# The structure is as follows:
|
76
|
+
#
|
77
|
+
# {
|
78
|
+
# <line_number_on_ancestor> => [
|
79
|
+
# [ <change>, ... ], # The changes in the first file
|
80
|
+
# [ <change>, ... ] # The changes in the second file
|
81
|
+
# ]
|
82
|
+
# }
|
83
|
+
def combine_diffs(diff_one, diff_two)
|
84
|
+
Hash.new { |hash, key| hash[key] = [[], []] }.tap do |combined_diff|
|
85
|
+
diff_one.each do |change|
|
86
|
+
combined_diff[change.old_position].first << change
|
87
|
+
end
|
88
|
+
|
89
|
+
diff_two.each do |change|
|
90
|
+
combined_diff[change.old_position].last << change
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'vcs_toolkit/exceptions'
|
2
|
+
|
3
|
+
module VCSToolkit
|
4
|
+
##
|
5
|
+
# This class is used to implement a custom storage provider for
|
6
|
+
# objects.
|
7
|
+
#
|
8
|
+
# The methods are compatible with the interface of Hash so that
|
9
|
+
# a simple Hash can be used instead of a full-featured object manager.
|
10
|
+
#
|
11
|
+
class ObjectStore
|
12
|
+
include Enumerable
|
13
|
+
|
14
|
+
##
|
15
|
+
# Implement this to store a specific object in persistent storage.
|
16
|
+
#
|
17
|
+
# object_id is here for compatibility with Hash
|
18
|
+
#
|
19
|
+
def store(object_id, object)
|
20
|
+
raise NotImplementedError, 'You must implement ObjectStore#store'
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Implement this to retrieve an object with the specified object_id.
|
25
|
+
# It should detect the object type and instantiate the specific
|
26
|
+
# object class (or a subclass of it).
|
27
|
+
#
|
28
|
+
def fetch(object_id)
|
29
|
+
raise NotImplementedError, 'You must implement ObjectStore#fetch'
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Implement this to detect wether a object with that object_id exists.
|
34
|
+
#
|
35
|
+
def key?(object_id)
|
36
|
+
raise NotImplementedError, 'You must implement ObjectStore#key?'
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Implement this to enumerate over all objects that
|
41
|
+
# have Object#named? set to true.
|
42
|
+
# Even enumeration of all objects is possible, but is not
|
43
|
+
# neccessary.
|
44
|
+
#
|
45
|
+
# The order of enumeration doesn't matter.
|
46
|
+
#
|
47
|
+
def each
|
48
|
+
raise NotImplementedError, 'You must implement ObjectStore#each'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|