This repository has been archived by the owner on Jun 21, 2023. It is now read-only.
forked from mojombo/grit
-
Notifications
You must be signed in to change notification settings - Fork 14
/
loose.rb
159 lines (140 loc) · 4.71 KB
/
loose.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#
# converted from the gitrb project
#
# authors:
# Matthias Lederhofer <matled@gmx.net>
# Simon 'corecode' Schubert <corecode@fs.ei.tum.de>
# Scott Chacon <schacon@gmail.com>
#
# provides native ruby access to git objects and pack files
#
require 'zlib'
require 'digest/sha1'
require 'grit/git-ruby/internal/raw_object'
require 'tempfile'
module Grit
module GitRuby
module Internal
class LooseObjectError < StandardError
end
class LooseStorage
def initialize(directory)
@directory = directory
end
def [](sha1)
sha1 = sha1.unpack("H*")[0]
begin
return nil unless sha1[0...2] && sha1[2..39]
path = @directory + '/' + sha1[0...2] + '/' + sha1[2..39]
get_raw_object(open(path, 'rb') { |f| f.read })
rescue Errno::ENOENT
nil
end
end
def get_raw_object(buf)
if buf.bytesize < 2
raise LooseObjectError, "object file too small"
end
if legacy_loose_object?(buf)
content = Zlib::Inflate.inflate(buf)
header, content = content.split(/\0/, 2)
if !header || !content
raise LooseObjectError, "invalid object header"
end
type, size = header.split(/ /, 2)
if !%w(blob tree commit tag).include?(type) || size !~ /^\d+$/
raise LooseObjectError, "invalid object header"
end
type = type.to_sym
size = size.to_i
else
type, size, used = unpack_object_header_gently(buf)
content = Zlib::Inflate.inflate(buf[used..-1])
end
raise LooseObjectError, "size mismatch" if content.bytesize != size
return RawObject.new(type, content)
end
# write an object to a temporary file, then atomically rename it
# into place; this ensures readers never see a half-written file
def safe_write(path, content)
f =
if RUBY_VERSION >= '1.9'
Tempfile.open("tmp_obj_", File.dirname(path), :opt => "wb")
else
Tempfile.open("tmp_obj_", File.dirname(path))
end
begin
f.write content
f.fsync
File.link(f.path, path)
rescue Errno::EEXIST
# The path already exists; we raced with another process,
# but it's OK, because by definition the content is the
# same. So we can just ignore the error.
ensure
f.unlink
f.close
end
end
# currently, I'm using the legacy format because it's easier to do
# this function takes content and a type and writes out the loose object and returns a sha
def put_raw_object(content, type)
size = content.bytesize.to_s
LooseStorage.verify_header(type, size)
header = "#{type} #{size}\0"
store = header + content
sha1 = Digest::SHA1.hexdigest(store)
path = @directory+'/'+sha1[0...2]+'/'+sha1[2..40]
if !File.exists?(path)
content = Zlib::Deflate.deflate(store)
FileUtils.mkdir_p(@directory+'/'+sha1[0...2])
safe_write(path, content)
end
return sha1
end
# simply figure out the sha
def self.calculate_sha(content, type)
size = content.bytesize.to_s
verify_header(type, size)
header = "#{type} #{size}\0"
store = header + content
Digest::SHA1.hexdigest(store)
end
def self.verify_header(type, size)
if !%w(blob tree commit tag).include?(type) || size !~ /^\d+$/
raise LooseObjectError, "invalid object header"
end
end
# private
def unpack_object_header_gently(buf)
used = 0
c = buf.getord(used)
used += 1
type = (c >> 4) & 7;
size = c & 15;
shift = 4;
while c & 0x80 != 0
if buf.bytesize <= used
raise LooseObjectError, "object file too short"
end
c = buf.getord(used)
used += 1
size += (c & 0x7f) << shift
shift += 7
end
type = OBJ_TYPES[type]
if ![:blob, :tree, :commit, :tag].include?(type)
raise LooseObjectError, "invalid loose object type"
end
return [type, size, used]
end
private :unpack_object_header_gently
def legacy_loose_object?(buf)
word = (buf.getord(0) << 8) + buf.getord(1)
buf.getord(0) == 0x78 && word % 31 == 0
end
private :legacy_loose_object?
end
end
end
end