Skip to content

Instantly share code, notes, and snippets.

@domgetter
Forked from anonymous/git_blob.rb
Last active February 17, 2019 14:14
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save domgetter/e84a75c429e5a0217fc7 to your computer and use it in GitHub Desktop.
Save domgetter/e84a75c429e5a0217fc7 to your computer and use it in GitHub Desktop.
An implementation of different objects in the object store of Git
require 'digest/sha1'
require 'zlib'
require 'pp'
module Git
OBJECTS = {}
class Object
def initialize(content)
@uncompressed_content = header(content) + content
puts hash
return hash
end
def hash
@sha1 ||= Digest::SHA1.hexdigest(@uncompressed_content)
end
def compressed
@compressed_content ||= Zlib::Deflate.deflate(@uncompressed_content)
end
def path
@path ||= ".git/objects/" + hash[0..1] + "/" + hash[2..-1]
end
def header(type, content)
"#{type} #{content.length}\0"
end
def save
OBJECTS[path.to_sym] ||= compressed
end
end
class Blob < Object
def header(content)
super("blob", content)
end
def hash_bytes
eval(hash.scan(/../).map {|p| "\x" + p.upcase}.join)
end
end
class Tree < Object
def initialize(content)
validate(content)
super(content)
end
def header(content)
super("tree", content)
end
private
def validate(content)
# must be a list of trees and blobs
#
end
end
class Commit < Object
def initialize(content)
validate(content)
super(content)
end
def header(content)
super("commit", content)
end
private
def validate(content)
# must refer to a tree hash, zero or more parent commit hashes,
# an author (with datetime), a commiter (with datetime), and a message
end
end
class Tag < Object
def initialize(content)
validate(content)
super(content)
end
private
def validate(content)
#tag must have an object hash, object type, tag name, tagger (with datetime), and message
# example:
#object 7e7b6a09dc5e466b7992fea125732c67239ac92b
#type commit
#tag v2.0
#tagger Joe Schmo <jo.shmo@gmail.com> 1430542850 -0700
#
#tag test
#
end
def save
super
#and also add a file in .git/refs/tags whose name is the tag name and
# whose content is the sha1 of the tag object created in the object store
end
end
end
# Every command in git either makes one or more objects in the database,
# changes a reference, or displays an object or reference.
# The only exception to this rule is modifying your config file.
# git add some_file.txt
# results in the following:
# a new blob is created, and added to the object store
file_name = "some_file.txt"
file_content = "some text in a file"
blob = Git::Blob.new(file_content)
blob.save
pp Git::OBJECTS
# it also adds the new blob to the index so that when you commit
# the following tree is created
tree_content = "100644 #{file_name}\x00#{blob.hash_bytes}"
tree = Git::Tree.new(tree_content)
tree.save
pp Git::OBJECTS
commit_content = (tree = "tree #{tree.hash}\n")
commit_content += (author = "author Dominic Muller <nicklink483@gmail.com> 1430522440 -0700\n")
commit_content += (committer = "committer Dominic Muller <nicklink483@gmail.com> 1430522440 -0700\n")
commit_content += (message = "\nI'm a commit message!\n")
# git commit -m "I'm a commit message!"
# this will now result in a commit object being created that points to that tree
# that the index made for you.
# The author and commiter will be grabbed from your config files
commit = Git::Commit.new(commit_content)
commit.save
pp Git::OBJECTS
# congrats! Now the object store has 3 new key-value pairs. One blob, one tree, and one commit
# The files are saved after being compressed with the zlib deflate compression.
# so the key is the hash of the content of the object (including the header prepended),
# and the value is the compressed content (including the header prepended).
# A branch is a reference to some commit
# When you make your first commit, Git sets the master reference to point to its hash
master = commit.hash
# Git also sets a reference called HEAD to point to some branch.
# Which in turn points at some commit hash, but we'll just use the branch name for now
# let's make another commit!
file_content = "new file here!"
file_name2 = "new_file.txt"
blob2 = Git::Blob.new(file_content)
blob2.save
# and now to make a tree which has BOTH files so far
tree_content = (tree_file_1 = "100644 #{file_name}\x00#{blob.hash_bytes}")
tree_content += (tree_file_2 = "100644 #{file_name2}\x00#{blob2.hash_bytes}")
second_tree = Git::Tree.new(tree_content)
second_tree.save
# And of course, if we're going to make history,
# we have to tell the new commit where he came from
# which requires adding a parent line
new_commit_content = (tree = "tree #{second_tree.hash}\n")
new_commit_content += (parent = "parent #{commit.hash}\n")
new_commit_content += (author = "author Dominic Muller <nicklink483@gmail.com> 1430523736 -0700\n")
new_commit_content += (committer = "committer Dominic Muller <nicklink483@gmail.com> 1430523736 -0700\n")
new_commit_content += (message = "\nA second commit. I have a parent!\n")
new_commit = Git::Commit.new(new_commit_content)
new_commit.save
master = new_commit.hash
pp Git::OBJECTS
# If we want, we can go re-inflate any compressed objects
compressed_commit = Git::OBJECTS[:".git/objects/#{new_commit.hash[0..1]}/#{new_commit.hash[2..-1]}"]
puts Zlib::Inflate.inflate(compressed_commit).split("\0")[1..-1].join
# btw, that last line is exactly what `git cat-file -p HEAD` would have done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment