Class: GitStore
- Inherits:
-
Object
- Object
- GitStore
- Includes:
- Enumerable
- Defined in:
- lib/git_store.rb,
lib/git_store/tag.rb,
lib/git_store/blob.rb,
lib/git_store/diff.rb,
lib/git_store/pack.rb,
lib/git_store/tree.rb,
lib/git_store/user.rb,
lib/git_store/commit.rb,
lib/git_store/handlers.rb
Overview
GitStore implements a versioned data store based on the revision management system git. You can store object hierarchies as nested hashes, which will be mapped on the directory structure of a git repository.
GitStore supports transactions, so that updates to the store either fail or succeed completely.
GitStore manages concurrent access by a file locking scheme. So only one process can start a transaction at one time. This is implemented by locking the ‘refs/head/<branch>.lock` file, which is also respected by the git binary.
A regular commit should be atomic by the nature of git, as the only critical part is writing the 40 bytes SHA1 hash of the commit object to the file ‘refs/head/<branch>`, which is done atomically by the operating system.
So reading a repository should be always consistent in a git repository. The head of a branch points to a commit object, which in turn points to a tree object, which itself is a snapshot of the GitStore at commit time. All involved objects are keyed by their SHA1 value, so there is no chance for another process to write to the same files.
Defined Under Namespace
Classes: Blob, Commit, DefaultHandler, Diff, Mmap, PackFormatError, PackStorage, Tag, Tree, User, YAMLHandler
Constant Summary collapse
- TYPE_CLASS =
{ 'tree' => Tree, 'blob' => Blob, 'commit' => Commit, 'tag' => Tag }
- CLASS_TYPE =
{ Tree => 'tree', Blob => 'blob', Commit => 'commit', Tag => 'tag' }
- PACK_SIGNATURE =
"PACK"
- PACK_IDX_SIGNATURE =
"\377tOc"
- OBJ_NONE =
0
- OBJ_COMMIT =
1
- OBJ_TREE =
2
- OBJ_BLOB =
3
- OBJ_TAG =
4
- OBJ_TYPES =
[nil, 'commit', 'tree', 'blob', 'tag'].freeze
Instance Attribute Summary collapse
-
#bare ⇒ Object
readonly
Returns the value of attribute bare.
-
#branch ⇒ Object
readonly
Returns the value of attribute branch.
-
#handler ⇒ Object
readonly
Returns the value of attribute handler.
-
#head ⇒ Object
readonly
Returns the value of attribute head.
-
#index ⇒ Object
readonly
Returns the value of attribute index.
-
#lock_file ⇒ Object
readonly
Returns the value of attribute lock_file.
-
#objects ⇒ Object
readonly
Returns the value of attribute objects.
-
#packs ⇒ Object
readonly
Returns the value of attribute packs.
-
#path ⇒ Object
readonly
Returns the value of attribute path.
-
#root ⇒ Object
readonly
Returns the value of attribute root.
Instance Method Summary collapse
-
#[](path) ⇒ Object
Read an object for the specified path.
-
#[]=(path, data) ⇒ Object
Write an object to the specified path.
-
#changed? ⇒ Boolean
Has our store been changed on disk?.
-
#commit(message = '', author = User.from_config, committer = author) ⇒ Object
Write a commit object to disk and set the head of the current branch.
-
#commits(limit = 10, start = head) ⇒ Object
Returns a list of commits starting from head commit.
-
#delete(path) ⇒ Object
Remove given path from store.
-
#each(&block) ⇒ Object
Iterate over all key-values pairs found in this store.
-
#finish_transaction ⇒ Object
Finish the transaction.
-
#get(id) ⇒ Object
Get an object by its id.
-
#get_object(id) ⇒ Object
Read the raw object with the given id from the repository.
-
#git_path ⇒ Object
Returns the path to the git data directory.
-
#handler_for(path) ⇒ Object
Return a handler for a given path.
-
#head_path ⇒ Object
Returns the path to the current head file.
-
#id_for(type, content) ⇒ Object
Calculate the id for a given type and raw data string.
-
#in_transaction? ⇒ Boolean
Is there any transaction going on?.
-
#initialize(path, branch = 'master', bare = false) ⇒ GitStore
constructor
Initialize a store.
-
#inspect ⇒ Object
Inspect the store.
-
#load(from_disk = false) ⇒ Object
Load the current head version from repository.
- #load_from_disk ⇒ Object
-
#object_path(id) ⇒ Object
Returns the path to the object file for given id.
-
#paths ⇒ Object
Returns all paths found in this store.
-
#put(object) ⇒ Object
Save a git object to the store.
-
#put_object(type, content) ⇒ Object
Write a raw object to the repository.
-
#read_head_id ⇒ Object
Read the id of the head commit.
-
#refresh! ⇒ Object
Reload the store, if it has been changed on disk.
-
#rollback ⇒ Object
Restore the state of the store.
-
#sha(str) ⇒ Object
Returns the hash value of an object string.
-
#start_transaction ⇒ Object
Start a transaction.
-
#to_hash ⇒ Object
Returns the store as a hash tree.
-
#transaction(message = "", author = User.from_config, committer = author) ⇒ Object
All changes made inside a transaction are atomic.
-
#tree(path) ⇒ Object
Find or create a tree object with given path.
-
#values ⇒ Object
Returns all values found in this store.
Constructor Details
#initialize(path, branch = 'master', bare = false) ⇒ GitStore
Initialize a store.
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/git_store.rb', line 61 def initialize(path, branch = 'master', = false) if && !File.exists?("#{path}") or ! && !File.exists?("#{path}/.git") raise ArgumentError, "first argument must be a valid Git repository: `#{path}'" end @bare = @path = path.chomp('/') @branch = branch @root = Tree.new(self) @packs = {} @objects = {} @handler = { 'yml' => YAMLHandler.new } @handler.default = DefaultHandler.new load_packs("#{git_path}/objects/pack") load end |
Instance Attribute Details
#bare ⇒ Object (readonly)
Returns the value of attribute bare.
58 59 60 |
# File 'lib/git_store.rb', line 58 def @bare end |
#branch ⇒ Object (readonly)
Returns the value of attribute branch.
58 59 60 |
# File 'lib/git_store.rb', line 58 def branch @branch end |
#handler ⇒ Object (readonly)
Returns the value of attribute handler.
58 59 60 |
# File 'lib/git_store.rb', line 58 def handler @handler end |
#head ⇒ Object (readonly)
Returns the value of attribute head.
58 59 60 |
# File 'lib/git_store.rb', line 58 def head @head end |
#index ⇒ Object (readonly)
Returns the value of attribute index.
58 59 60 |
# File 'lib/git_store.rb', line 58 def index @index end |
#lock_file ⇒ Object (readonly)
Returns the value of attribute lock_file.
58 59 60 |
# File 'lib/git_store.rb', line 58 def lock_file @lock_file end |
#objects ⇒ Object (readonly)
Returns the value of attribute objects.
58 59 60 |
# File 'lib/git_store.rb', line 58 def objects @objects end |
#packs ⇒ Object (readonly)
Returns the value of attribute packs.
58 59 60 |
# File 'lib/git_store.rb', line 58 def packs @packs end |
#path ⇒ Object (readonly)
Returns the value of attribute path.
58 59 60 |
# File 'lib/git_store.rb', line 58 def path @path end |
#root ⇒ Object (readonly)
Returns the value of attribute root.
58 59 60 |
# File 'lib/git_store.rb', line 58 def root @root end |
Instance Method Details
#[](path) ⇒ Object
Read an object for the specified path.
117 118 119 |
# File 'lib/git_store.rb', line 117 def [](path) root[path] end |
#[]=(path, data) ⇒ Object
Write an object to the specified path.
122 123 124 |
# File 'lib/git_store.rb', line 122 def []=(path, data) root[path] = data end |
#changed? ⇒ Boolean
Has our store been changed on disk?
162 163 164 |
# File 'lib/git_store.rb', line 162 def changed? head.nil? or head.id != read_head_id end |
#commit(message = '', author = User.from_config, committer = author) ⇒ Object
Write a commit object to disk and set the head of the current branch.
Returns the commit object
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 |
# File 'lib/git_store.rb', line 249 def commit( = '', = User.from_config, committer = ) root.write commit = Commit.new(self) commit.tree = root commit.parent << head.id if head commit. = commit.committer = committer commit. = commit.write open(head_path, "wb") do |file| file.write(commit.id) end @head = commit end |
#commits(limit = 10, start = head) ⇒ Object
Returns a list of commits starting from head commit.
268 269 270 271 272 273 274 275 276 277 278 |
# File 'lib/git_store.rb', line 268 def commits(limit = 10, start = head) entries = [] current = start while current and entries.size < limit entries << current current = get(current.parent.first) end entries end |
#delete(path) ⇒ Object
Remove given path from store.
142 143 144 |
# File 'lib/git_store.rb', line 142 def delete(path) root.delete(path) end |
#each(&block) ⇒ Object
Iterate over all key-values pairs found in this store.
127 128 129 |
# File 'lib/git_store.rb', line 127 def each(&block) root.each(&block) end |
#finish_transaction ⇒ Object
Finish the transaction.
Release the lock file.
239 240 241 242 243 244 |
# File 'lib/git_store.rb', line 239 def finish_transaction Thread.current['git_store_lock'].close rescue nil Thread.current['git_store_lock'] = nil File.unlink("#{head_path}.lock") rescue nil end |
#get(id) ⇒ Object
Get an object by its id.
Returns a tree, blob, commit or tag object.
283 284 285 286 287 288 289 290 291 292 293 |
# File 'lib/git_store.rb', line 283 def get(id) return nil if id.nil? return objects[id] if objects.has_key?(id) type, content = get_object(id) klass = TYPE_CLASS[type] or raise NotImplementedError, "type not supported: #{type}" objects[id] = klass.new(self, id, content) end |
#get_object(id) ⇒ Object
Read the raw object with the given id from the repository.
Returns a pair of content and type of the object
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'lib/git_store.rb', line 321 def get_object(id) path = object_path(id) if File.exists?(path) buf = open(path, "rb") { |f| f.read } raise "not a loose object: #{id}" if not legacy_loose_object?(buf) header, content = Zlib::Inflate.inflate(buf).split(/\0/, 2) type, size = header.split(/ /, 2) raise "bad object: #{id}" if content.length != size.to_i else content, type = get_object_from_pack(id) end return type, content end |
#git_path ⇒ Object
Returns the path to the git data directory.
96 97 98 99 100 101 102 |
# File 'lib/git_store.rb', line 96 def git_path if "#{path}" else "#{path}/.git" end end |
#handler_for(path) ⇒ Object
Return a handler for a given path.
112 113 114 |
# File 'lib/git_store.rb', line 112 def handler_for(path) handler[ path.split('.').last ] end |
#head_path ⇒ Object
Returns the path to the current head file.
86 87 88 |
# File 'lib/git_store.rb', line 86 def head_path "#{git_path}/refs/heads/#{branch}" end |
#id_for(type, content) ⇒ Object
Calculate the id for a given type and raw data string.
314 315 316 |
# File 'lib/git_store.rb', line 314 def id_for(type, content) sha "#{type} #{content.length}\0#{content}" end |
#in_transaction? ⇒ Boolean
Is there any transaction going on?
191 192 193 |
# File 'lib/git_store.rb', line 191 def in_transaction? Thread.current['git_store_lock'] end |
#inspect ⇒ Object
Inspect the store.
157 158 159 |
# File 'lib/git_store.rb', line 157 def inspect "#<GitStore #{path} #{branch}>" end |
#load(from_disk = false) ⇒ Object
Load the current head version from repository.
167 168 169 170 171 172 173 174 |
# File 'lib/git_store.rb', line 167 def load(from_disk = false) if id = read_head_id @head = get(id) @root = @head.tree end load_from_disk if from_disk end |
#load_from_disk ⇒ Object
176 177 178 179 180 181 182 183 |
# File 'lib/git_store.rb', line 176 def load_from_disk root.each_blob do |path, blob| file = "#{self.path}/#{path}" if File.file?(file) blob.data = File.read(file) end end end |
#object_path(id) ⇒ Object
Returns the path to the object file for given id.
91 92 93 |
# File 'lib/git_store.rb', line 91 def object_path(id) "#{git_path}/objects/#{ id[0...2] }/#{ id[2..39] }" end |
#paths ⇒ Object
Returns all paths found in this store.
132 133 134 |
# File 'lib/git_store.rb', line 132 def paths root.paths end |
#put(object) ⇒ Object
Save a git object to the store.
Returns the object id.
298 299 300 301 302 303 304 305 306 |
# File 'lib/git_store.rb', line 298 def put(object) type = CLASS_TYPE[object.class] or raise NotImplementedError, "class not supported: #{object.class}" id = put_object(type, object.dump) objects[id] = object id end |
#put_object(type, content) ⇒ Object
Write a raw object to the repository.
Returns the object id.
343 344 345 346 347 348 349 350 351 352 353 354 355 356 |
# File 'lib/git_store.rb', line 343 def put_object(type, content) data = "#{type} #{content.length}\0#{content}" id = sha(data) path = object_path(id) unless File.exists?(path) FileUtils.mkpath(File.dirname(path)) open(path, 'wb') do |f| f.write Zlib::Deflate.deflate(data) end end id end |
#read_head_id ⇒ Object
Read the id of the head commit.
Returns the object id of the last commit.
107 108 109 |
# File 'lib/git_store.rb', line 107 def read_head_id File.read(head_path).strip if File.exists?(head_path) end |
#refresh! ⇒ Object
Reload the store, if it has been changed on disk.
186 187 188 |
# File 'lib/git_store.rb', line 186 def refresh! load if changed? end |
#rollback ⇒ Object
Restore the state of the store.
Any changes made to the store are discarded.
230 231 232 233 234 |
# File 'lib/git_store.rb', line 230 def rollback objects.clear load finish_transaction end |
#sha(str) ⇒ Object
Returns the hash value of an object string.
309 310 311 |
# File 'lib/git_store.rb', line 309 def sha(str) Digest::SHA1.hexdigest(str)[0, 40] end |
#start_transaction ⇒ Object
Start a transaction.
Tries to get lock on lock file, reload the this store if has changed in the repository.
218 219 220 221 222 223 224 225 |
# File 'lib/git_store.rb', line 218 def start_transaction file = open("#{head_path}.lock", "w") file.flock(File::LOCK_EX) Thread.current['git_store_lock'] = file load if changed? end |
#to_hash ⇒ Object
Returns the store as a hash tree.
152 153 154 |
# File 'lib/git_store.rb', line 152 def to_hash root.to_hash end |
#transaction(message = "", author = User.from_config, committer = author) ⇒ Object
All changes made inside a transaction are atomic. If some exception occurs the transaction will be rolled back.
Example:
store.transaction { store['a'] = 'b' }
201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/git_store.rb', line 201 def transaction( = "", = User.from_config, committer = ) start_transaction result = yield commit , , committer result rescue rollback raise ensure finish_transaction end |
#tree(path) ⇒ Object
Find or create a tree object with given path.
147 148 149 |
# File 'lib/git_store.rb', line 147 def tree(path) root.tree(path) end |
#values ⇒ Object
Returns all values found in this store.
137 138 139 |
# File 'lib/git_store.rb', line 137 def values root.values end |