##// END OF EJS Templates
bundlerepository: get rid of temporary bundle files (issue2478)...
Klaus Koch -
r12962:ff083040 stable
parent child Browse files
Show More
@@ -1,322 +1,324
1 1 # bundlerepo.py - repository class for viewing uncompressed bundles
2 2 #
3 3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 """Repository class for viewing uncompressed bundles.
9 9
10 10 This provides a read-only repository interface to bundles as if they
11 11 were part of the actual repository.
12 12 """
13 13
14 14 from node import nullid
15 15 from i18n import _
16 16 import os, struct, tempfile, shutil
17 17 import changegroup, util, mdiff, discovery
18 18 import localrepo, changelog, manifest, filelog, revlog, error
19 19
20 20 class bundlerevlog(revlog.revlog):
21 21 def __init__(self, opener, indexfile, bundle,
22 22 linkmapper=None):
23 23 # How it works:
24 24 # to retrieve a revision, we need to know the offset of
25 25 # the revision in the bundle (an unbundle object).
26 26 #
27 27 # We store this offset in the index (start), to differentiate a
28 28 # rev in the bundle and from a rev in the revlog, we check
29 29 # len(index[r]). If the tuple is bigger than 7, it is a bundle
30 30 # (it is bigger since we store the node to which the delta is)
31 31 #
32 32 revlog.revlog.__init__(self, opener, indexfile)
33 33 self.bundle = bundle
34 34 self.basemap = {}
35 35 def chunkpositer():
36 36 while 1:
37 37 chunk = bundle.chunk()
38 38 if not chunk:
39 39 break
40 40 pos = bundle.tell()
41 41 yield chunk, pos - len(chunk)
42 42 n = len(self)
43 43 prev = None
44 44 for chunk, start in chunkpositer():
45 45 size = len(chunk)
46 46 if size < 80:
47 47 raise util.Abort(_("invalid changegroup"))
48 48 start += 80
49 49 size -= 80
50 50 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
51 51 if node in self.nodemap:
52 52 prev = node
53 53 continue
54 54 for p in (p1, p2):
55 55 if not p in self.nodemap:
56 56 raise error.LookupError(p, self.indexfile,
57 57 _("unknown parent"))
58 58 if linkmapper is None:
59 59 link = n
60 60 else:
61 61 link = linkmapper(cs)
62 62
63 63 if not prev:
64 64 prev = p1
65 65 # start, size, full unc. size, base (unused), link, p1, p2, node
66 66 e = (revlog.offset_type(start, 0), size, -1, -1, link,
67 67 self.rev(p1), self.rev(p2), node)
68 68 self.basemap[n] = prev
69 69 self.index.insert(-1, e)
70 70 self.nodemap[node] = n
71 71 prev = node
72 72 n += 1
73 73
74 74 def inbundle(self, rev):
75 75 """is rev from the bundle"""
76 76 if rev < 0:
77 77 return False
78 78 return rev in self.basemap
79 79 def bundlebase(self, rev):
80 80 return self.basemap[rev]
81 81 def _chunk(self, rev):
82 82 # Warning: in case of bundle, the diff is against bundlebase,
83 83 # not against rev - 1
84 84 # XXX: could use some caching
85 85 if not self.inbundle(rev):
86 86 return revlog.revlog._chunk(self, rev)
87 87 self.bundle.seek(self.start(rev))
88 88 return self.bundle.read(self.length(rev))
89 89
90 90 def revdiff(self, rev1, rev2):
91 91 """return or calculate a delta between two revisions"""
92 92 if self.inbundle(rev1) and self.inbundle(rev2):
93 93 # hot path for bundle
94 94 revb = self.rev(self.bundlebase(rev2))
95 95 if revb == rev1:
96 96 return self._chunk(rev2)
97 97 elif not self.inbundle(rev1) and not self.inbundle(rev2):
98 98 return revlog.revlog.revdiff(self, rev1, rev2)
99 99
100 100 return mdiff.textdiff(self.revision(self.node(rev1)),
101 101 self.revision(self.node(rev2)))
102 102
103 103 def revision(self, node):
104 104 """return an uncompressed revision of a given"""
105 105 if node == nullid:
106 106 return ""
107 107
108 108 text = None
109 109 chain = []
110 110 iter_node = node
111 111 rev = self.rev(iter_node)
112 112 # reconstruct the revision if it is from a changegroup
113 113 while self.inbundle(rev):
114 114 if self._cache and self._cache[0] == iter_node:
115 115 text = self._cache[2]
116 116 break
117 117 chain.append(rev)
118 118 iter_node = self.bundlebase(rev)
119 119 rev = self.rev(iter_node)
120 120 if text is None:
121 121 text = revlog.revlog.revision(self, iter_node)
122 122
123 123 while chain:
124 124 delta = self._chunk(chain.pop())
125 125 text = mdiff.patches(text, [delta])
126 126
127 127 p1, p2 = self.parents(node)
128 128 if node != revlog.hash(text, p1, p2):
129 129 raise error.RevlogError(_("integrity check failed on %s:%d")
130 130 % (self.datafile, self.rev(node)))
131 131
132 132 self._cache = (node, self.rev(node), text)
133 133 return text
134 134
135 135 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
136 136 raise NotImplementedError
137 137 def addgroup(self, revs, linkmapper, transaction):
138 138 raise NotImplementedError
139 139 def strip(self, rev, minlink):
140 140 raise NotImplementedError
141 141 def checksize(self):
142 142 raise NotImplementedError
143 143
144 144 class bundlechangelog(bundlerevlog, changelog.changelog):
145 145 def __init__(self, opener, bundle):
146 146 changelog.changelog.__init__(self, opener)
147 147 bundlerevlog.__init__(self, opener, self.indexfile, bundle)
148 148
149 149 class bundlemanifest(bundlerevlog, manifest.manifest):
150 150 def __init__(self, opener, bundle, linkmapper):
151 151 manifest.manifest.__init__(self, opener)
152 152 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
153 153 linkmapper)
154 154
155 155 class bundlefilelog(bundlerevlog, filelog.filelog):
156 156 def __init__(self, opener, path, bundle, linkmapper):
157 157 filelog.filelog.__init__(self, opener, path)
158 158 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
159 159 linkmapper)
160 160
161 161 class bundlerepository(localrepo.localrepository):
162 162 def __init__(self, ui, path, bundlename):
163 163 self._tempparent = None
164 164 try:
165 165 localrepo.localrepository.__init__(self, ui, path)
166 166 except error.RepoError:
167 167 self._tempparent = tempfile.mkdtemp()
168 168 localrepo.instance(ui, self._tempparent, 1)
169 169 localrepo.localrepository.__init__(self, ui, self._tempparent)
170 170
171 171 if path:
172 172 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
173 173 else:
174 174 self._url = 'bundle:' + bundlename
175 175
176 176 self.tempfile = None
177 177 f = open(bundlename, "rb")
178 178 self.bundle = changegroup.readbundle(f, bundlename)
179 179 if self.bundle.compressed():
180 180 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
181 181 suffix=".hg10un", dir=self.path)
182 182 self.tempfile = temp
183 183 fptemp = os.fdopen(fdtemp, 'wb')
184 184
185 185 try:
186 186 fptemp.write("HG10UN")
187 187 while 1:
188 188 chunk = self.bundle.read(2**18)
189 189 if not chunk:
190 190 break
191 191 fptemp.write(chunk)
192 192 finally:
193 193 fptemp.close()
194 194
195 195 f = open(self.tempfile, "rb")
196 196 self.bundle = changegroup.readbundle(f, bundlename)
197 197
198 198 # dict with the mapping 'filename' -> position in the bundle
199 199 self.bundlefilespos = {}
200 200
201 201 @util.propertycache
202 202 def changelog(self):
203 203 c = bundlechangelog(self.sopener, self.bundle)
204 204 self.manstart = self.bundle.tell()
205 205 return c
206 206
207 207 @util.propertycache
208 208 def manifest(self):
209 209 self.bundle.seek(self.manstart)
210 210 m = bundlemanifest(self.sopener, self.bundle, self.changelog.rev)
211 211 self.filestart = self.bundle.tell()
212 212 return m
213 213
214 214 @util.propertycache
215 215 def manstart(self):
216 216 self.changelog
217 217 return self.manstart
218 218
219 219 @util.propertycache
220 220 def filestart(self):
221 221 self.manifest
222 222 return self.filestart
223 223
224 224 def url(self):
225 225 return self._url
226 226
227 227 def file(self, f):
228 228 if not self.bundlefilespos:
229 229 self.bundle.seek(self.filestart)
230 230 while 1:
231 231 chunk = self.bundle.chunk()
232 232 if not chunk:
233 233 break
234 234 self.bundlefilespos[chunk] = self.bundle.tell()
235 235 while 1:
236 236 c = self.bundle.chunk()
237 237 if not c:
238 238 break
239 239
240 240 if f[0] == '/':
241 241 f = f[1:]
242 242 if f in self.bundlefilespos:
243 243 self.bundle.seek(self.bundlefilespos[f])
244 244 return bundlefilelog(self.sopener, f, self.bundle,
245 245 self.changelog.rev)
246 246 else:
247 247 return filelog.filelog(self.sopener, f)
248 248
249 249 def close(self):
250 250 """Close assigned bundle file immediately."""
251 251 self.bundle.close()
252 if self.tempfile is not None:
253 os.unlink(self.tempfile)
252 254
253 255 def __del__(self):
254 256 del self.bundle
255 257 if self.tempfile is not None:
256 258 os.unlink(self.tempfile)
257 259 if self._tempparent:
258 260 shutil.rmtree(self._tempparent, True)
259 261
260 262 def cancopy(self):
261 263 return False
262 264
263 265 def getcwd(self):
264 266 return os.getcwd() # always outside the repo
265 267
266 268 def instance(ui, path, create):
267 269 if create:
268 270 raise util.Abort(_('cannot create new bundle repository'))
269 271 parentpath = ui.config("bundle", "mainreporoot", "")
270 272 if parentpath:
271 273 # Try to make the full path relative so we get a nice, short URL.
272 274 # In particular, we don't want temp dir names in test outputs.
273 275 cwd = os.getcwd()
274 276 if parentpath == cwd:
275 277 parentpath = ''
276 278 else:
277 279 cwd = os.path.join(cwd,'')
278 280 if parentpath.startswith(cwd):
279 281 parentpath = parentpath[len(cwd):]
280 282 path = util.drop_scheme('file', path)
281 283 if path.startswith('bundle:'):
282 284 path = util.drop_scheme('bundle', path)
283 285 s = path.split("+", 1)
284 286 if len(s) == 1:
285 287 repopath, bundlename = parentpath, s[0]
286 288 else:
287 289 repopath, bundlename = s
288 290 else:
289 291 repopath, bundlename = parentpath, path
290 292 return bundlerepository(ui, repopath, bundlename)
291 293
292 294 def getremotechanges(ui, repo, other, revs=None, bundlename=None, force=False):
293 295 tmp = discovery.findcommonincoming(repo, other, heads=revs, force=force)
294 296 common, incoming, rheads = tmp
295 297 if not incoming:
296 298 try:
297 299 os.unlink(bundlename)
298 300 except:
299 301 pass
300 302 return other, None, None
301 303
302 304 bundle = None
303 305 if bundlename or not other.local():
304 306 # create a bundle (uncompressed if other repo is not local)
305 307
306 308 if revs is None and other.capable('changegroupsubset'):
307 309 revs = rheads
308 310
309 311 if revs is None:
310 312 cg = other.changegroup(incoming, "incoming")
311 313 else:
312 314 cg = other.changegroupsubset(incoming, revs, 'incoming')
313 315 bundletype = other.local() and "HG10BZ" or "HG10UN"
314 316 fname = bundle = changegroup.writebundle(cg, bundlename, bundletype)
315 317 # keep written bundle?
316 318 if bundlename:
317 319 bundle = None
318 320 if not other.local():
319 321 # use the created uncompressed bundlerepo
320 322 other = bundlerepository(ui, repo.root, fname)
321 323 return (other, incoming, bundle)
322 324
@@ -1,131 +1,135
1 1 $ cat <<EOF >> $HGRCPATH
2 2 > [extensions]
3 3 > mq=
4 4 > [alias]
5 5 > tlog = log --template "{rev}: {desc}\\n"
6 6 > theads = heads --template "{rev}: {desc}\\n"
7 7 > tincoming = incoming --template "{rev}: {desc}\\n"
8 8 > EOF
9 9
10 10 Setup main:
11 11
12 12 $ hg init base
13 13 $ cd base
14 14 $ echo "One" > one
15 15 $ hg add
16 16 adding one
17 17 $ hg ci -m "main: one added"
18 18 $ echo "++" >> one
19 19 $ hg ci -m "main: one updated"
20 20
21 21 Bundle main:
22 22
23 23 $ hg bundle --base=null ../main.hg
24 24 2 changesets found
25 25
26 26 $ cd ..
27 27
28 28 Incoming to fresh repo:
29 29
30 30 $ hg init fresh
31 31
32 32 $ hg -R fresh tincoming main.hg
33 33 comparing with main.hg
34 34 0: main: one added
35 35 1: main: one updated
36 $ test -f ./fresh/.hg/hg-bundle* && echo 'temp. bundle file remained' || true
36 37
37 38 $ hg -R fresh tincoming bundle:fresh+main.hg
38 39 comparing with bundle:fresh+main.hg
39 40 0: main: one added
40 41 1: main: one updated
41 42
42 43
43 44 Setup queue:
44 45
45 46 $ cd base
46 47 $ hg qinit -c
47 48 $ hg qnew -m "patch: two added" two.patch
48 49 $ echo two > two
49 50 $ hg add
50 51 adding two
51 52 $ hg qrefresh
52 53 $ hg qcommit -m "queue: two.patch added"
53 54 $ hg qpop -a
54 55 popping two.patch
55 56 patch queue now empty
56 57
57 58 Bundle queue:
58 59
59 60 $ hg -R .hg/patches bundle --base=null ../queue.hgq
60 61 1 changesets found
62 $ test -f ./fresh/.hg/hg-bundle* && echo 'temp. bundle file remained' || true
61 63
62 64 $ cd ..
63 65
64 66
65 67 Clone base:
66 68
67 69 $ hg clone base copy
68 70 updating to branch default
69 71 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
70 72 $ cd copy
71 73 $ hg qinit -c
72 74
73 75 Incoming queue bundle:
74 76
75 77 $ hg -R .hg/patches tincoming ../queue.hgq
76 78 comparing with ../queue.hgq
77 79 0: queue: two.patch added
80 $ test -f .hg/hg-bundle* && echo 'temp. bundle file remained' || true
78 81
79 82 Pull queue bundle:
80 83
81 84 $ hg -R .hg/patches pull --update ../queue.hgq
82 85 pulling from ../queue.hgq
83 86 requesting all changes
84 87 adding changesets
85 88 adding manifests
86 89 adding file changes
87 90 added 1 changesets with 3 changes to 3 files
88 91 merging series
89 92 2 files updated, 1 files merged, 0 files removed, 0 files unresolved
93 $ test -f .hg/patches/hg-bundle* && echo 'temp. bundle file remained' || true
90 94
91 95 $ hg -R .hg/patches theads
92 96 0: queue: two.patch added
93 97
94 98 $ hg -R .hg/patches tlog
95 99 0: queue: two.patch added
96 100
97 101 $ hg qseries
98 102 two.patch
99 103
100 104 $ cd ..
101 105
102 106
103 107 Clone base again:
104 108
105 109 $ hg clone base copy2
106 110 updating to branch default
107 111 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
108 112 $ cd copy2
109 113 $ hg qinit -c
110 114
111 115 Unbundle queue bundle:
112 116
113 117 $ hg -R .hg/patches unbundle --update ../queue.hgq
114 118 adding changesets
115 119 adding manifests
116 120 adding file changes
117 121 added 1 changesets with 3 changes to 3 files
118 122 merging series
119 123 2 files updated, 1 files merged, 0 files removed, 0 files unresolved
120 124
121 125 $ hg -R .hg/patches theads
122 126 0: queue: two.patch added
123 127
124 128 $ hg -R .hg/patches tlog
125 129 0: queue: two.patch added
126 130
127 131 $ hg qseries
128 132 two.patch
129 133
130 134 $ cd ..
131 135
General Comments 0
You need to be logged in to leave comments. Login now