-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
version-control-system #45
Changes from 18 commits
8545230
31bc7b1
30df542
4c05fbf
d7d87d0
5332694
d1f1658
338179b
4bfc151
d95ac6b
0bfea34
d4bb34e
4c00cc7
c42013e
f062559
4a923c2
64d6b09
1fe0d6f
4fd8216
b9b2cf3
fe852d3
147ee00
c7ab2ad
7829c50
73c03a2
bc025fe
4dc4f10
293ebfa
d53985d
256db12
31bdf8e
a71f996
d0325b2
b4aca5f
312f476
a89399d
398bed5
cce4e35
4f2a6f3
5710482
34900b6
0c4c349
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,223 @@ | ||
require_relative 'vcs_internals' | ||
|
||
|
||
NodeValue = Struct.new(:key, :parent_x, :parent_y) | ||
|
||
|
||
def get_lines(input_file_path) | ||
lines = [] | ||
open(input_file_path, "r") do |input_file| | ||
while true | ||
data = input_file.gets | ||
if data.nil? | ||
break | ||
end | ||
lines.push(data) | ||
end | ||
end | ||
return lines | ||
end | ||
|
||
|
||
def get_lcs(old_lines, new_lines) | ||
lcs = Array.new(old_lines.length + 1) \ | ||
{Array.new(new_lines.length + 1, NodeValue.new(0, 0, 0))} | ||
for ii in (1..old_lines.length) | ||
for jj in (1..new_lines.length) | ||
if old_lines[ii - 1] == new_lines[jj - 1] | ||
lcs[ii][jj] = NodeValue.new( | ||
lcs[ii - 1][jj - 1].key + 1, | ||
ii - 1, | ||
jj - 1, | ||
) | ||
else | ||
lcs[ii][jj] = NodeValue.new(lcs[ii][jj - 1].key, ii, jj - 1) | ||
if lcs[ii - 1][jj].key > lcs[ii][jj].key | ||
lcs[ii][jj].key = lcs[ii - 1][jj].key | ||
lcs[ii][jj].parent_x = ii - 1 | ||
lcs[ii][jj].parent_y = jj | ||
end | ||
end | ||
end | ||
end | ||
equivalent_line_in_new_file = Array.new(old_lines.length + 1, 0) | ||
equivalent_line_in_old_file = Array.new(new_lines.length + 1, 0) | ||
current_x = old_lines.length | ||
current_y = new_lines.length | ||
while current_x != 0 || current_y != 0 | ||
parent_x = lcs[current_x][current_y].parent_x | ||
parent_y = lcs[current_x][current_y].parent_y | ||
if current_x - 1 == parent_x && current_y - 1 == parent_y | ||
equivalent_line_in_new_file[current_x] = current_y | ||
equivalent_line_in_old_file[current_y] = current_x | ||
end | ||
current_x = parent_x | ||
current_y = parent_y | ||
end | ||
return equivalent_line_in_new_file, equivalent_line_in_old_file | ||
end | ||
|
||
|
||
def display_diff_delete(lines, lcs, index, line_new) | ||
start_index = index | ||
end_index = index | ||
index += 1 | ||
while index <= lines.length && lcs[index] == 0 | ||
end_index += 1 | ||
index += 1 | ||
end | ||
if start_index == end_index | ||
puts "#{start_index}d#{line_new}" | ||
puts "< #{lines[start_index - 1]}" | ||
else | ||
puts "#{start_index},#{end_index}d#{line_new}" | ||
while start_index <= end_index | ||
puts "< #{lines[start_index - 1]}" | ||
start_index += 1 | ||
end | ||
end | ||
return index | ||
end | ||
|
||
|
||
def display_diff_append(lines, lcs, index, line_old, line_new) | ||
line_new += 1 | ||
start_index = index | ||
end_index = index | ||
index += 1 | ||
while index <= lines.length && lcs[index] == 0 | ||
index += 1 | ||
end_index += 1 | ||
end | ||
if start_index == end_index | ||
puts "#{line_old}a#{line_new}" | ||
puts "> #{lines[start_index - 1]}" | ||
else | ||
puts "#{line_old}a#{line_new},#{line_new + (end_index - start_index)}" | ||
line_new += (end_index - start_index) | ||
while start_index <= end_index | ||
puts "> #{lines[start_index - 1]}" | ||
start_index += 1 | ||
end | ||
end | ||
return index, line_new | ||
end | ||
|
||
|
||
def display_diff_change( | ||
old_lines, | ||
new_lines, | ||
equivalent_line_in_new_file, | ||
equivalent_line_in_old_file, | ||
start_old_index, | ||
start_new_index, | ||
line_new | ||
) | ||
line_new += 1 | ||
start_old = start_old_index | ||
start_new = start_new_index | ||
end_old = start_old_index | ||
end_new = start_new_index | ||
|
||
start_old_index += 1 | ||
start_new_index += 1 | ||
while ( | ||
start_old_index <= old_lines.length && | ||
start_new_index <= new_lines.length && | ||
equivalent_line_in_new_file[start_old_index] == 0 && | ||
equivalent_line_in_old_file[start_new_index] == 0 | ||
) | ||
start_old_index += 1 | ||
end_old += 1 | ||
start_new_index += 1 | ||
end_new += 1 | ||
end | ||
if start_old == end_old | ||
puts "#{start_old}c#{line_new}" | ||
puts "< #{old_lines[start_old - 1]}" | ||
puts "---" | ||
puts "> #{new_lines[start_new - 1]}" | ||
else | ||
print "#{start_old},#{end_old}c#{line_new}," | ||
print "#{line_new + (end_old - start_old)}\n" | ||
line_new += (end_old - start_old) | ||
while start_old <= end_old | ||
puts "< #{old_lines[start_old - 1]}" | ||
start_old += 1 | ||
end | ||
puts "---" | ||
while start_new <= end_new | ||
puts "> #{new_lines[start_new - 1]}" | ||
start_new += 1 | ||
end | ||
end | ||
return start_old_index, start_new_index, line_new | ||
end | ||
|
||
|
||
def find_diff(file1, file2) | ||
old_lines = [] | ||
new_lines = [] | ||
if File.file?(file1) | ||
old_lines = get_lines(file1) | ||
else | ||
old_lines.push(file1) | ||
end | ||
if File.file?(file2) | ||
new_lines = get_lines(file2) | ||
else | ||
new_lines.push(file2) | ||
end | ||
equivalent_line_in_new_file, equivalent_line_in_old_file = \ | ||
get_lcs(old_lines, new_lines) | ||
line_number_new = 0 | ||
old_index = 1 | ||
new_index = 1 | ||
while old_index <= old_lines.length || new_index <= new_lines.length | ||
if ( | ||
equivalent_line_in_new_file[old_index] == 0 && | ||
( | ||
new_index > new_lines.length || | ||
equivalent_line_in_old_file[new_index] != 0 | ||
) | ||
) | ||
old_index = display_diff_delete( | ||
old_lines, | ||
equivalent_line_in_new_file, | ||
old_index, | ||
line_number_new, | ||
) | ||
elsif ( | ||
equivalent_line_in_old_file[new_index] == 0 && | ||
( | ||
old_index > old_lines.length || | ||
equivalent_line_in_new_file[old_index] != 0 | ||
) | ||
) | ||
new_index, line_number_new = display_diff_append( | ||
new_lines, | ||
equivalent_line_in_old_file, | ||
new_index, | ||
old_index - 1, | ||
line_number_new, | ||
) | ||
elsif ( | ||
equivalent_line_in_new_file[old_index] == 0 && | ||
equivalent_line_in_old_file[new_index] == 0 | ||
) | ||
old_index, new_index, line_number_new = display_diff_change( | ||
old_lines, | ||
new_lines, | ||
equivalent_line_in_new_file, | ||
equivalent_line_in_old_file, | ||
old_index, | ||
new_index, | ||
line_number_new, | ||
) | ||
else equivalent_line_in_old_file[new_index] == old_index | ||
line_number_new += 1 | ||
old_index += 1 | ||
new_index += 1 | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
require 'fileutils' | ||
require_relative 'vcs_internals' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is non-intuitive that a module called "file storage" depends on "vcs". Might want to rename this to "vcs-objects" or something. |
||
|
||
|
||
SHA_SIZE = 40 | ||
|
||
|
||
class Blob | ||
|
||
def self.create_blob(content_file_path) | ||
header = "blob #{File.size(content_file_path)}#{NULL}" | ||
sha_hash = get_sha_hash(header, content_file_path) | ||
create_object_file(header, content_file_path, sha_hash) | ||
return sha_hash | ||
end | ||
|
||
def self.restore_blob(sha_hash, output_file_path) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ehh ... using static methods defeats the point of objects too. |
||
input_file_path = File.join(FilePath::OBJECTS, sha_hash) | ||
open(input_file_path, "r") do |input_file| | ||
open(output_file_path, "w") do |output_file| | ||
data = "" | ||
_header, data = extract_header(input_file) | ||
output_file.write(data) | ||
while true | ||
data = input_file.read(BLOCK_SIZE) | ||
if data.nil? | ||
break | ||
end | ||
output_file.write(data) | ||
end | ||
end | ||
end | ||
end | ||
end | ||
|
||
|
||
class Tree | ||
|
||
def self.build_tree(directory_path) | ||
files = {} | ||
for element in Dir.glob(File.join(directory_path, '*')) | ||
file_name = File.basename(element) | ||
if File.file?(element) | ||
sha_hash = Blob.create_blob(element) | ||
files[file_name] = sha_hash | ||
else | ||
sha_hash = Tree.build_tree(element) | ||
files[file_name] = sha_hash | ||
end | ||
end | ||
content = "" | ||
files.each do |file_name, sha_hash| | ||
content = content + file_name + NULL + sha_hash | ||
end | ||
header = "tree #{content.length}#{NULL}" | ||
sha_hash = get_sha_hash(header, content) | ||
create_object_file(header, content, sha_hash) | ||
return sha_hash | ||
end | ||
|
||
def self.build_working_directory(sha_hash, directory_path) | ||
files = Tree.get_directory_content(sha_hash) | ||
files.each do |file_name, sha_hash| | ||
if type(sha_hash).eql? "blob" | ||
restore_blob(sha_hash, File.join(directory_path, file_name)) | ||
else | ||
Dir.mkdir(File.join(directory_path, file_name)) | ||
build_working_directory( | ||
sha_hash, | ||
File.join(directory_path, file_name), | ||
) | ||
end | ||
end | ||
end | ||
|
||
def self.get_directory_content(sha_hash) | ||
input_file_path = File.join(FilePath::OBJECTS, sha_hash) | ||
header = nil | ||
files = {} | ||
open(input_file_path, "r") do |input_file| | ||
data = "" | ||
header, data = extract_header(input_file) | ||
while true | ||
while not data.include? NULL | ||
current_data = input_file.read(BLOCK_SIZE) | ||
if current_data.nil? | ||
break | ||
end | ||
data += current_data | ||
end | ||
if data.empty? | ||
break | ||
end | ||
file_name, _separator, data = data.partition(NULL) | ||
while data.length < SHA_SIZE | ||
current_data = input_file.read(BLOCK_SIZE) | ||
if current_data.nil? | ||
break | ||
end | ||
data += current_data | ||
end | ||
file_sha_hash = data[0,SHA_SIZE] | ||
data = data[SHA_SIZE..-1] | ||
files[file_name] = file_sha_hash | ||
end | ||
end | ||
return files | ||
end | ||
end | ||
|
||
|
||
class Commit | ||
|
||
def self.create_commit(commit_message, parent) | ||
config_parameters = {} | ||
open(FilePath::CONFIG, "r") do |config_file| | ||
while true | ||
data = config_file.gets | ||
if data.nil? | ||
break | ||
end | ||
key, _separator, value = data.partition(':') | ||
config_parameters[key] = value | ||
end | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reading config should probably be through API methods on a VCS class, instead of reading the file directly. |
||
header = "" | ||
content = "" | ||
content = "sha:#{Tree.build_tree(Dir.getwd)}#{EOL}" | ||
content += "parent:#{parent}#{EOL}" | ||
content += "author:#{config_parameters.fetch("author")}#{EOL}" | ||
content += "email:#{config_parameters.fetch("email")}#{EOL}" | ||
content += "time:#{Time.now}#{EOL}" | ||
content += "message:#{commit_message}" | ||
header = "commit #{content.length}#{NULL}" | ||
commit_hash = get_sha_hash(header, content) | ||
create_object_file(header, content, commit_hash) | ||
return commit_hash | ||
end | ||
|
||
def self.parse_commit_object(commit_hash) | ||
content = {} | ||
input_file_path = File.join(FilePath::OBJECTS, commit_hash) | ||
open(input_file_path, "r") do |input_file| | ||
data = "" | ||
_header, data = extract_header(input_file) | ||
while true | ||
current_data = input_file.read(BLOCK_SIZE) | ||
if current_data.nil? | ||
break | ||
end | ||
data += current_data | ||
end | ||
data = data.split(EOL) | ||
for parameter in data | ||
key, _separator, value = parameter.partition(':') | ||
content[key] = value | ||
end | ||
end | ||
return content | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO display_diff_change can handle the display_diff_append/display_diff_delete cases too, so you shouldn't need them.