Skip ^{} tags.
[jelmer/dulwich-libgit2.git] / dulwich / object_store.py
1 # object_store.py -- Object store for git objects 
2 # Copyright (C) 2008 Jelmer Vernooij <jelmer@samba.org>
3
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # or (at your option) a later version of the License.
8
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 # MA  02110-1301, USA.
18
19 import os
20 import tempfile
21 import urllib2
22
23 from dulwich.objects import (
24     hex_to_sha,
25     sha_to_hex,
26     ShaFile,
27     )
28 from dulwich.pack import (
29     iter_sha1, 
30     load_packs, 
31     write_pack,
32     write_pack_data,
33     write_pack_index_v2,
34     PackData, 
35     )
36
37 PACKDIR = 'pack'
38
39 class ObjectStore(object):
40
41     def __init__(self, path):
42         self.path = path
43         self._packs = None
44
45     def determine_wants_all(self, refs):
46             return [sha for (ref, sha) in refs.iteritems() if not sha in self and not ref.endswith("^{}")]
47
48     def iter_shas(self, shas):
49         return ObjectStoreIterator(self, shas)
50
51     def pack_dir(self):
52         return os.path.join(self.path, PACKDIR)
53
54     def __contains__(self, sha):
55         # TODO: This can be more efficient
56         try:
57             self[sha]
58             return True
59         except KeyError:
60             return False
61
62     @property
63     def packs(self):
64         """List with pack objects."""
65         if self._packs is None:
66             self._packs = list(load_packs(self.pack_dir()))
67         return self._packs
68
69     def _get_shafile_path(self, sha):
70         dir = sha[:2]
71         file = sha[2:]
72         # Check from object dir
73         return os.path.join(self.path, dir, file)
74
75     def _get_shafile(self, sha):
76         path = self._get_shafile_path(sha)
77         if os.path.exists(path):
78           return ShaFile.from_file(path)
79         return None
80
81     def _add_shafile(self, sha, o):
82         path = self._get_shafile_path(sha)
83         f = os.path.open(path, 'w')
84         try:
85             f.write(o._header())
86             f.write(o._text)
87         finally:
88             f.close()
89
90     def get_raw(self, sha):
91         """Obtain the raw text for an object.
92         
93         :param sha: Sha for the object.
94         :return: tuple with object type and object contents.
95         """
96         for pack in self.packs:
97             if sha in pack:
98                 return pack.get_raw(sha, self.get_raw)
99         # FIXME: Are pack deltas ever against on-disk shafiles ?
100         ret = self._get_shafile(sha)
101         if ret is not None:
102             return ret.as_raw_string()
103         raise KeyError(sha)
104
105     def __getitem__(self, sha):
106         assert len(sha) == 40, "Incorrect length sha: %s" % str(sha)
107         ret = self._get_shafile(sha)
108         if ret is not None:
109             return ret
110         # Check from packs
111         type, uncomp = self.get_raw(sha)
112         return ShaFile.from_raw_string(type, uncomp)
113
114     def move_in_thin_pack(self, path):
115         """Move a specific file containing a pack into the pack directory.
116
117         :note: The file should be on the same file system as the 
118             packs directory.
119
120         :param path: Path to the pack file.
121         """
122         p = PackData(path)
123         temppath = os.path.join(self.pack_dir(), sha_to_hex(urllib2.randombytes(20))+".temppack")
124         write_pack(temppath, p.iterobjects(self.get_raw), len(p))
125         pack_sha = PackIndex(temppath+".idx").objects_sha1()
126         os.rename(temppath+".pack", 
127             os.path.join(self.pack_dir(), "pack-%s.pack" % pack_sha))
128         os.rename(temppath+".idx", 
129             os.path.join(self.pack_dir(), "pack-%s.idx" % pack_sha))
130
131     def move_in_pack(self, path):
132         """Move a specific file containing a pack into the pack directory.
133
134         :note: The file should be on the same file system as the 
135             packs directory.
136
137         :param path: Path to the pack file.
138         """
139         p = PackData(path)
140         entries = p.sorted_entries()
141         basename = os.path.join(self.pack_dir(), 
142             "pack-%s" % iter_sha1(entry[0] for entry in entries))
143         write_pack_index_v2(basename+".idx", entries, p.calculate_checksum())
144         os.rename(path, basename + ".pack")
145
146     def add_thin_pack(self):
147         """Add a new thin pack to this object store.
148
149         Thin packs are packs that contain deltas with parents that exist 
150         in a different pack.
151         """
152         fd, path = tempfile.mkstemp(dir=self.pack_dir(), suffix=".pack")
153         f = os.fdopen(fd, 'w')
154         def commit():
155             if os.path.getsize(path) > 0:
156                 self.move_in_thin_pack(path)
157         return f, commit
158
159     def add_pack(self):
160         """Add a new pack to this object store. 
161
162         :return: Fileobject to write to and a commit function to 
163             call when the pack is finished.
164         """
165         fd, path = tempfile.mkstemp(dir=self.pack_dir(), suffix=".pack")
166         f = os.fdopen(fd, 'w')
167         def commit():
168             if os.path.getsize(path) > 0:
169                 self.move_in_pack(path)
170         return f, commit
171
172     def add_objects(self, objects):
173         if len(objects) == 0:
174             return
175         f, commit = self.add_pack()
176         write_pack_data(f, objects, len(objects))
177         commit()
178
179
180 class ObjectImporter(object):
181
182     def __init__(self, count):
183         self.count = count
184
185     def add_object(self, object):
186         raise NotImplementedError(self.add_object)
187
188     def finish(self, object):
189         raise NotImplementedError(self.finish)
190
191
192 class ObjectIterator(object):
193
194     def iterobjects(self):
195         raise NotImplementedError(self.iterobjects)
196
197
198 class ObjectStoreIterator(ObjectIterator):
199
200     def __init__(self, store, shas):
201         self.store = store
202         self.shas = shas
203
204     def __iter__(self):
205         return ((self.store[sha], path) for sha, path in self.shas)
206
207     def iterobjects(self):
208         for o, path in self:
209             yield o
210
211     def __contains__(self, needle):
212         """Check if an object is present.
213
214         :param needle: SHA1 of the object to check for
215         """
216         # FIXME: This could be more efficient
217         for sha, path in self.shas:
218             if sha == needle:
219                 return True
220         return False
221
222     def __getitem__(self, key):
223         """Find an object by SHA1."""
224         return self.store[key]
225
226     def __len__(self):
227         """Return the number of objects."""
228         return len(self.shas)
229
230