##// END OF EJS Templates
changegroup3: add empty chunk separating directories and files...
Martin von Zweigbergk -
r27753:d4071cc7 default
parent child Browse files
Show More
@@ -1,528 +1,532 b''
1 # bundlerepo.py - repository class for viewing uncompressed bundles
1 # bundlerepo.py - repository class for viewing uncompressed bundles
2 #
2 #
3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Repository class for viewing uncompressed bundles.
8 """Repository class for viewing uncompressed bundles.
9
9
10 This provides a read-only repository interface to bundles as if they
10 This provides a read-only repository interface to bundles as if they
11 were part of the actual repository.
11 were part of the actual repository.
12 """
12 """
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import os
16 import os
17 import shutil
17 import shutil
18 import tempfile
18 import tempfile
19
19
20 from .i18n import _
20 from .i18n import _
21 from .node import nullid
21 from .node import nullid
22
22
23 from . import (
23 from . import (
24 bundle2,
24 bundle2,
25 changegroup,
25 changegroup,
26 changelog,
26 changelog,
27 cmdutil,
27 cmdutil,
28 discovery,
28 discovery,
29 error,
29 error,
30 exchange,
30 exchange,
31 filelog,
31 filelog,
32 localrepo,
32 localrepo,
33 manifest,
33 manifest,
34 mdiff,
34 mdiff,
35 pathutil,
35 pathutil,
36 phases,
36 phases,
37 revlog,
37 revlog,
38 scmutil,
38 scmutil,
39 util,
39 util,
40 )
40 )
41
41
42 class bundlerevlog(revlog.revlog):
42 class bundlerevlog(revlog.revlog):
43 def __init__(self, opener, indexfile, bundle, linkmapper):
43 def __init__(self, opener, indexfile, bundle, linkmapper):
44 # How it works:
44 # How it works:
45 # To retrieve a revision, we need to know the offset of the revision in
45 # To retrieve a revision, we need to know the offset of the revision in
46 # the bundle (an unbundle object). We store this offset in the index
46 # the bundle (an unbundle object). We store this offset in the index
47 # (start). The base of the delta is stored in the base field.
47 # (start). The base of the delta is stored in the base field.
48 #
48 #
49 # To differentiate a rev in the bundle from a rev in the revlog, we
49 # To differentiate a rev in the bundle from a rev in the revlog, we
50 # check revision against repotiprev.
50 # check revision against repotiprev.
51 opener = scmutil.readonlyvfs(opener)
51 opener = scmutil.readonlyvfs(opener)
52 revlog.revlog.__init__(self, opener, indexfile)
52 revlog.revlog.__init__(self, opener, indexfile)
53 self.bundle = bundle
53 self.bundle = bundle
54 n = len(self)
54 n = len(self)
55 self.repotiprev = n - 1
55 self.repotiprev = n - 1
56 chain = None
56 chain = None
57 self.bundlerevs = set() # used by 'bundle()' revset expression
57 self.bundlerevs = set() # used by 'bundle()' revset expression
58 while True:
58 while True:
59 chunkdata = bundle.deltachunk(chain)
59 chunkdata = bundle.deltachunk(chain)
60 if not chunkdata:
60 if not chunkdata:
61 break
61 break
62 node = chunkdata['node']
62 node = chunkdata['node']
63 p1 = chunkdata['p1']
63 p1 = chunkdata['p1']
64 p2 = chunkdata['p2']
64 p2 = chunkdata['p2']
65 cs = chunkdata['cs']
65 cs = chunkdata['cs']
66 deltabase = chunkdata['deltabase']
66 deltabase = chunkdata['deltabase']
67 delta = chunkdata['delta']
67 delta = chunkdata['delta']
68
68
69 size = len(delta)
69 size = len(delta)
70 start = bundle.tell() - size
70 start = bundle.tell() - size
71
71
72 link = linkmapper(cs)
72 link = linkmapper(cs)
73 if node in self.nodemap:
73 if node in self.nodemap:
74 # this can happen if two branches make the same change
74 # this can happen if two branches make the same change
75 chain = node
75 chain = node
76 self.bundlerevs.add(self.nodemap[node])
76 self.bundlerevs.add(self.nodemap[node])
77 continue
77 continue
78
78
79 for p in (p1, p2):
79 for p in (p1, p2):
80 if p not in self.nodemap:
80 if p not in self.nodemap:
81 raise error.LookupError(p, self.indexfile,
81 raise error.LookupError(p, self.indexfile,
82 _("unknown parent"))
82 _("unknown parent"))
83
83
84 if deltabase not in self.nodemap:
84 if deltabase not in self.nodemap:
85 raise LookupError(deltabase, self.indexfile,
85 raise LookupError(deltabase, self.indexfile,
86 _('unknown delta base'))
86 _('unknown delta base'))
87
87
88 baserev = self.rev(deltabase)
88 baserev = self.rev(deltabase)
89 # start, size, full unc. size, base (unused), link, p1, p2, node
89 # start, size, full unc. size, base (unused), link, p1, p2, node
90 e = (revlog.offset_type(start, 0), size, -1, baserev, link,
90 e = (revlog.offset_type(start, 0), size, -1, baserev, link,
91 self.rev(p1), self.rev(p2), node)
91 self.rev(p1), self.rev(p2), node)
92 self.index.insert(-1, e)
92 self.index.insert(-1, e)
93 self.nodemap[node] = n
93 self.nodemap[node] = n
94 self.bundlerevs.add(n)
94 self.bundlerevs.add(n)
95 chain = node
95 chain = node
96 n += 1
96 n += 1
97
97
98 def _chunk(self, rev):
98 def _chunk(self, rev):
99 # Warning: in case of bundle, the diff is against what we stored as
99 # Warning: in case of bundle, the diff is against what we stored as
100 # delta base, not against rev - 1
100 # delta base, not against rev - 1
101 # XXX: could use some caching
101 # XXX: could use some caching
102 if rev <= self.repotiprev:
102 if rev <= self.repotiprev:
103 return revlog.revlog._chunk(self, rev)
103 return revlog.revlog._chunk(self, rev)
104 self.bundle.seek(self.start(rev))
104 self.bundle.seek(self.start(rev))
105 return self.bundle.read(self.length(rev))
105 return self.bundle.read(self.length(rev))
106
106
107 def revdiff(self, rev1, rev2):
107 def revdiff(self, rev1, rev2):
108 """return or calculate a delta between two revisions"""
108 """return or calculate a delta between two revisions"""
109 if rev1 > self.repotiprev and rev2 > self.repotiprev:
109 if rev1 > self.repotiprev and rev2 > self.repotiprev:
110 # hot path for bundle
110 # hot path for bundle
111 revb = self.index[rev2][3]
111 revb = self.index[rev2][3]
112 if revb == rev1:
112 if revb == rev1:
113 return self._chunk(rev2)
113 return self._chunk(rev2)
114 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
114 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
115 return revlog.revlog.revdiff(self, rev1, rev2)
115 return revlog.revlog.revdiff(self, rev1, rev2)
116
116
117 return mdiff.textdiff(self.revision(self.node(rev1)),
117 return mdiff.textdiff(self.revision(self.node(rev1)),
118 self.revision(self.node(rev2)))
118 self.revision(self.node(rev2)))
119
119
120 def revision(self, nodeorrev):
120 def revision(self, nodeorrev):
121 """return an uncompressed revision of a given node or revision
121 """return an uncompressed revision of a given node or revision
122 number.
122 number.
123 """
123 """
124 if isinstance(nodeorrev, int):
124 if isinstance(nodeorrev, int):
125 rev = nodeorrev
125 rev = nodeorrev
126 node = self.node(rev)
126 node = self.node(rev)
127 else:
127 else:
128 node = nodeorrev
128 node = nodeorrev
129 rev = self.rev(node)
129 rev = self.rev(node)
130
130
131 if node == nullid:
131 if node == nullid:
132 return ""
132 return ""
133
133
134 text = None
134 text = None
135 chain = []
135 chain = []
136 iterrev = rev
136 iterrev = rev
137 # reconstruct the revision if it is from a changegroup
137 # reconstruct the revision if it is from a changegroup
138 while iterrev > self.repotiprev:
138 while iterrev > self.repotiprev:
139 if self._cache and self._cache[1] == iterrev:
139 if self._cache and self._cache[1] == iterrev:
140 text = self._cache[2]
140 text = self._cache[2]
141 break
141 break
142 chain.append(iterrev)
142 chain.append(iterrev)
143 iterrev = self.index[iterrev][3]
143 iterrev = self.index[iterrev][3]
144 if text is None:
144 if text is None:
145 text = self.baserevision(iterrev)
145 text = self.baserevision(iterrev)
146
146
147 while chain:
147 while chain:
148 delta = self._chunk(chain.pop())
148 delta = self._chunk(chain.pop())
149 text = mdiff.patches(text, [delta])
149 text = mdiff.patches(text, [delta])
150
150
151 self._checkhash(text, node, rev)
151 self._checkhash(text, node, rev)
152 self._cache = (node, rev, text)
152 self._cache = (node, rev, text)
153 return text
153 return text
154
154
155 def baserevision(self, nodeorrev):
155 def baserevision(self, nodeorrev):
156 # Revlog subclasses may override 'revision' method to modify format of
156 # Revlog subclasses may override 'revision' method to modify format of
157 # content retrieved from revlog. To use bundlerevlog with such class one
157 # content retrieved from revlog. To use bundlerevlog with such class one
158 # needs to override 'baserevision' and make more specific call here.
158 # needs to override 'baserevision' and make more specific call here.
159 return revlog.revlog.revision(self, nodeorrev)
159 return revlog.revlog.revision(self, nodeorrev)
160
160
161 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
161 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
162 raise NotImplementedError
162 raise NotImplementedError
163 def addgroup(self, revs, linkmapper, transaction):
163 def addgroup(self, revs, linkmapper, transaction):
164 raise NotImplementedError
164 raise NotImplementedError
165 def strip(self, rev, minlink):
165 def strip(self, rev, minlink):
166 raise NotImplementedError
166 raise NotImplementedError
167 def checksize(self):
167 def checksize(self):
168 raise NotImplementedError
168 raise NotImplementedError
169
169
170 class bundlechangelog(bundlerevlog, changelog.changelog):
170 class bundlechangelog(bundlerevlog, changelog.changelog):
171 def __init__(self, opener, bundle):
171 def __init__(self, opener, bundle):
172 changelog.changelog.__init__(self, opener)
172 changelog.changelog.__init__(self, opener)
173 linkmapper = lambda x: x
173 linkmapper = lambda x: x
174 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
174 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
175 linkmapper)
175 linkmapper)
176
176
177 def baserevision(self, nodeorrev):
177 def baserevision(self, nodeorrev):
178 # Although changelog doesn't override 'revision' method, some extensions
178 # Although changelog doesn't override 'revision' method, some extensions
179 # may replace this class with another that does. Same story with
179 # may replace this class with another that does. Same story with
180 # manifest and filelog classes.
180 # manifest and filelog classes.
181
181
182 # This bypasses filtering on changelog.node() and rev() because we need
182 # This bypasses filtering on changelog.node() and rev() because we need
183 # revision text of the bundle base even if it is hidden.
183 # revision text of the bundle base even if it is hidden.
184 oldfilter = self.filteredrevs
184 oldfilter = self.filteredrevs
185 try:
185 try:
186 self.filteredrevs = ()
186 self.filteredrevs = ()
187 return changelog.changelog.revision(self, nodeorrev)
187 return changelog.changelog.revision(self, nodeorrev)
188 finally:
188 finally:
189 self.filteredrevs = oldfilter
189 self.filteredrevs = oldfilter
190
190
191 class bundlemanifest(bundlerevlog, manifest.manifest):
191 class bundlemanifest(bundlerevlog, manifest.manifest):
192 def __init__(self, opener, bundle, linkmapper):
192 def __init__(self, opener, bundle, linkmapper):
193 manifest.manifest.__init__(self, opener)
193 manifest.manifest.__init__(self, opener)
194 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
194 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
195 linkmapper)
195 linkmapper)
196
196
197 def baserevision(self, nodeorrev):
197 def baserevision(self, nodeorrev):
198 node = nodeorrev
198 node = nodeorrev
199 if isinstance(node, int):
199 if isinstance(node, int):
200 node = self.node(node)
200 node = self.node(node)
201
201
202 if node in self._mancache:
202 if node in self._mancache:
203 result = self._mancache[node][0].text()
203 result = self._mancache[node][0].text()
204 else:
204 else:
205 result = manifest.manifest.revision(self, nodeorrev)
205 result = manifest.manifest.revision(self, nodeorrev)
206 return result
206 return result
207
207
208 class bundlefilelog(bundlerevlog, filelog.filelog):
208 class bundlefilelog(bundlerevlog, filelog.filelog):
209 def __init__(self, opener, path, bundle, linkmapper):
209 def __init__(self, opener, path, bundle, linkmapper):
210 filelog.filelog.__init__(self, opener, path)
210 filelog.filelog.__init__(self, opener, path)
211 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
211 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
212 linkmapper)
212 linkmapper)
213
213
214 def baserevision(self, nodeorrev):
214 def baserevision(self, nodeorrev):
215 return filelog.filelog.revision(self, nodeorrev)
215 return filelog.filelog.revision(self, nodeorrev)
216
216
217 class bundlepeer(localrepo.localpeer):
217 class bundlepeer(localrepo.localpeer):
218 def canpush(self):
218 def canpush(self):
219 return False
219 return False
220
220
221 class bundlephasecache(phases.phasecache):
221 class bundlephasecache(phases.phasecache):
222 def __init__(self, *args, **kwargs):
222 def __init__(self, *args, **kwargs):
223 super(bundlephasecache, self).__init__(*args, **kwargs)
223 super(bundlephasecache, self).__init__(*args, **kwargs)
224 if util.safehasattr(self, 'opener'):
224 if util.safehasattr(self, 'opener'):
225 self.opener = scmutil.readonlyvfs(self.opener)
225 self.opener = scmutil.readonlyvfs(self.opener)
226
226
227 def write(self):
227 def write(self):
228 raise NotImplementedError
228 raise NotImplementedError
229
229
230 def _write(self, fp):
230 def _write(self, fp):
231 raise NotImplementedError
231 raise NotImplementedError
232
232
233 def _updateroots(self, phase, newroots, tr):
233 def _updateroots(self, phase, newroots, tr):
234 self.phaseroots[phase] = newroots
234 self.phaseroots[phase] = newroots
235 self.invalidate()
235 self.invalidate()
236 self.dirty = True
236 self.dirty = True
237
237
238 class bundlerepository(localrepo.localrepository):
238 class bundlerepository(localrepo.localrepository):
239 def __init__(self, ui, path, bundlename):
239 def __init__(self, ui, path, bundlename):
240 def _writetempbundle(read, suffix, header=''):
240 def _writetempbundle(read, suffix, header=''):
241 """Write a temporary file to disk
241 """Write a temporary file to disk
242
242
243 This is closure because we need to make sure this tracked by
243 This is closure because we need to make sure this tracked by
244 self.tempfile for cleanup purposes."""
244 self.tempfile for cleanup purposes."""
245 fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
245 fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
246 suffix=".hg10un")
246 suffix=".hg10un")
247 self.tempfile = temp
247 self.tempfile = temp
248 fptemp = os.fdopen(fdtemp, 'wb')
248 fptemp = os.fdopen(fdtemp, 'wb')
249
249
250 try:
250 try:
251 fptemp.write(header)
251 fptemp.write(header)
252 while True:
252 while True:
253 chunk = read(2**18)
253 chunk = read(2**18)
254 if not chunk:
254 if not chunk:
255 break
255 break
256 fptemp.write(chunk)
256 fptemp.write(chunk)
257 finally:
257 finally:
258 fptemp.close()
258 fptemp.close()
259
259
260 return self.vfs.open(self.tempfile, mode="rb")
260 return self.vfs.open(self.tempfile, mode="rb")
261 self._tempparent = None
261 self._tempparent = None
262 try:
262 try:
263 localrepo.localrepository.__init__(self, ui, path)
263 localrepo.localrepository.__init__(self, ui, path)
264 except error.RepoError:
264 except error.RepoError:
265 self._tempparent = tempfile.mkdtemp()
265 self._tempparent = tempfile.mkdtemp()
266 localrepo.instance(ui, self._tempparent, 1)
266 localrepo.instance(ui, self._tempparent, 1)
267 localrepo.localrepository.__init__(self, ui, self._tempparent)
267 localrepo.localrepository.__init__(self, ui, self._tempparent)
268 self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
268 self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
269
269
270 if path:
270 if path:
271 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
271 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
272 else:
272 else:
273 self._url = 'bundle:' + bundlename
273 self._url = 'bundle:' + bundlename
274
274
275 self.tempfile = None
275 self.tempfile = None
276 f = util.posixfile(bundlename, "rb")
276 f = util.posixfile(bundlename, "rb")
277 self.bundlefile = self.bundle = exchange.readbundle(ui, f, bundlename)
277 self.bundlefile = self.bundle = exchange.readbundle(ui, f, bundlename)
278
278
279 if isinstance(self.bundle, bundle2.unbundle20):
279 if isinstance(self.bundle, bundle2.unbundle20):
280 cgstream = None
280 cgstream = None
281 for part in self.bundle.iterparts():
281 for part in self.bundle.iterparts():
282 if part.type == 'changegroup':
282 if part.type == 'changegroup':
283 if cgstream is not None:
283 if cgstream is not None:
284 raise NotImplementedError("can't process "
284 raise NotImplementedError("can't process "
285 "multiple changegroups")
285 "multiple changegroups")
286 cgstream = part
286 cgstream = part
287 version = part.params.get('version', '01')
287 version = part.params.get('version', '01')
288 if version not in changegroup.supportedversions(self):
288 if version not in changegroup.supportedversions(self):
289 msg = _('Unsupported changegroup version: %s')
289 msg = _('Unsupported changegroup version: %s')
290 raise error.Abort(msg % version)
290 raise error.Abort(msg % version)
291 if self.bundle.compressed():
291 if self.bundle.compressed():
292 cgstream = _writetempbundle(part.read,
292 cgstream = _writetempbundle(part.read,
293 ".cg%sun" % version)
293 ".cg%sun" % version)
294
294
295 if cgstream is None:
295 if cgstream is None:
296 raise error.Abort('No changegroups found')
296 raise error.Abort('No changegroups found')
297 cgstream.seek(0)
297 cgstream.seek(0)
298
298
299 self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
299 self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
300
300
301 elif self.bundle.compressed():
301 elif self.bundle.compressed():
302 f = _writetempbundle(self.bundle.read, '.hg10un', header='HG10UN')
302 f = _writetempbundle(self.bundle.read, '.hg10un', header='HG10UN')
303 self.bundlefile = self.bundle = exchange.readbundle(ui, f,
303 self.bundlefile = self.bundle = exchange.readbundle(ui, f,
304 bundlename,
304 bundlename,
305 self.vfs)
305 self.vfs)
306
306
307 # dict with the mapping 'filename' -> position in the bundle
307 # dict with the mapping 'filename' -> position in the bundle
308 self.bundlefilespos = {}
308 self.bundlefilespos = {}
309
309
310 self.firstnewrev = self.changelog.repotiprev + 1
310 self.firstnewrev = self.changelog.repotiprev + 1
311 phases.retractboundary(self, None, phases.draft,
311 phases.retractboundary(self, None, phases.draft,
312 [ctx.node() for ctx in self[self.firstnewrev:]])
312 [ctx.node() for ctx in self[self.firstnewrev:]])
313
313
314 @localrepo.unfilteredpropertycache
314 @localrepo.unfilteredpropertycache
315 def _phasecache(self):
315 def _phasecache(self):
316 return bundlephasecache(self, self._phasedefaults)
316 return bundlephasecache(self, self._phasedefaults)
317
317
318 @localrepo.unfilteredpropertycache
318 @localrepo.unfilteredpropertycache
319 def changelog(self):
319 def changelog(self):
320 # consume the header if it exists
320 # consume the header if it exists
321 self.bundle.changelogheader()
321 self.bundle.changelogheader()
322 c = bundlechangelog(self.svfs, self.bundle)
322 c = bundlechangelog(self.svfs, self.bundle)
323 self.manstart = self.bundle.tell()
323 self.manstart = self.bundle.tell()
324 return c
324 return c
325
325
326 @localrepo.unfilteredpropertycache
326 @localrepo.unfilteredpropertycache
327 def manifest(self):
327 def manifest(self):
328 self.bundle.seek(self.manstart)
328 self.bundle.seek(self.manstart)
329 # consume the header if it exists
329 # consume the header if it exists
330 self.bundle.manifestheader()
330 self.bundle.manifestheader()
331 m = bundlemanifest(self.svfs, self.bundle, self.changelog.rev)
331 m = bundlemanifest(self.svfs, self.bundle, self.changelog.rev)
332 # XXX: hack to work with changegroup3, but we still don't handle
333 # tree manifests correctly
334 if self.bundle.version == "03":
335 self.bundle.filelogheader()
332 self.filestart = self.bundle.tell()
336 self.filestart = self.bundle.tell()
333 return m
337 return m
334
338
335 @localrepo.unfilteredpropertycache
339 @localrepo.unfilteredpropertycache
336 def manstart(self):
340 def manstart(self):
337 self.changelog
341 self.changelog
338 return self.manstart
342 return self.manstart
339
343
340 @localrepo.unfilteredpropertycache
344 @localrepo.unfilteredpropertycache
341 def filestart(self):
345 def filestart(self):
342 self.manifest
346 self.manifest
343 return self.filestart
347 return self.filestart
344
348
345 def url(self):
349 def url(self):
346 return self._url
350 return self._url
347
351
348 def file(self, f):
352 def file(self, f):
349 if not self.bundlefilespos:
353 if not self.bundlefilespos:
350 self.bundle.seek(self.filestart)
354 self.bundle.seek(self.filestart)
351 while True:
355 while True:
352 chunkdata = self.bundle.filelogheader()
356 chunkdata = self.bundle.filelogheader()
353 if not chunkdata:
357 if not chunkdata:
354 break
358 break
355 fname = chunkdata['filename']
359 fname = chunkdata['filename']
356 self.bundlefilespos[fname] = self.bundle.tell()
360 self.bundlefilespos[fname] = self.bundle.tell()
357 while True:
361 while True:
358 c = self.bundle.deltachunk(None)
362 c = self.bundle.deltachunk(None)
359 if not c:
363 if not c:
360 break
364 break
361
365
362 if f in self.bundlefilespos:
366 if f in self.bundlefilespos:
363 self.bundle.seek(self.bundlefilespos[f])
367 self.bundle.seek(self.bundlefilespos[f])
364 return bundlefilelog(self.svfs, f, self.bundle, self.changelog.rev)
368 return bundlefilelog(self.svfs, f, self.bundle, self.changelog.rev)
365 else:
369 else:
366 return filelog.filelog(self.svfs, f)
370 return filelog.filelog(self.svfs, f)
367
371
368 def close(self):
372 def close(self):
369 """Close assigned bundle file immediately."""
373 """Close assigned bundle file immediately."""
370 self.bundlefile.close()
374 self.bundlefile.close()
371 if self.tempfile is not None:
375 if self.tempfile is not None:
372 self.vfs.unlink(self.tempfile)
376 self.vfs.unlink(self.tempfile)
373 if self._tempparent:
377 if self._tempparent:
374 shutil.rmtree(self._tempparent, True)
378 shutil.rmtree(self._tempparent, True)
375
379
376 def cancopy(self):
380 def cancopy(self):
377 return False
381 return False
378
382
379 def peer(self):
383 def peer(self):
380 return bundlepeer(self)
384 return bundlepeer(self)
381
385
382 def getcwd(self):
386 def getcwd(self):
383 return os.getcwd() # always outside the repo
387 return os.getcwd() # always outside the repo
384
388
385
389
386 def instance(ui, path, create):
390 def instance(ui, path, create):
387 if create:
391 if create:
388 raise error.Abort(_('cannot create new bundle repository'))
392 raise error.Abort(_('cannot create new bundle repository'))
389 # internal config: bundle.mainreporoot
393 # internal config: bundle.mainreporoot
390 parentpath = ui.config("bundle", "mainreporoot", "")
394 parentpath = ui.config("bundle", "mainreporoot", "")
391 if not parentpath:
395 if not parentpath:
392 # try to find the correct path to the working directory repo
396 # try to find the correct path to the working directory repo
393 parentpath = cmdutil.findrepo(os.getcwd())
397 parentpath = cmdutil.findrepo(os.getcwd())
394 if parentpath is None:
398 if parentpath is None:
395 parentpath = ''
399 parentpath = ''
396 if parentpath:
400 if parentpath:
397 # Try to make the full path relative so we get a nice, short URL.
401 # Try to make the full path relative so we get a nice, short URL.
398 # In particular, we don't want temp dir names in test outputs.
402 # In particular, we don't want temp dir names in test outputs.
399 cwd = os.getcwd()
403 cwd = os.getcwd()
400 if parentpath == cwd:
404 if parentpath == cwd:
401 parentpath = ''
405 parentpath = ''
402 else:
406 else:
403 cwd = pathutil.normasprefix(cwd)
407 cwd = pathutil.normasprefix(cwd)
404 if parentpath.startswith(cwd):
408 if parentpath.startswith(cwd):
405 parentpath = parentpath[len(cwd):]
409 parentpath = parentpath[len(cwd):]
406 u = util.url(path)
410 u = util.url(path)
407 path = u.localpath()
411 path = u.localpath()
408 if u.scheme == 'bundle':
412 if u.scheme == 'bundle':
409 s = path.split("+", 1)
413 s = path.split("+", 1)
410 if len(s) == 1:
414 if len(s) == 1:
411 repopath, bundlename = parentpath, s[0]
415 repopath, bundlename = parentpath, s[0]
412 else:
416 else:
413 repopath, bundlename = s
417 repopath, bundlename = s
414 else:
418 else:
415 repopath, bundlename = parentpath, path
419 repopath, bundlename = parentpath, path
416 return bundlerepository(ui, repopath, bundlename)
420 return bundlerepository(ui, repopath, bundlename)
417
421
418 class bundletransactionmanager(object):
422 class bundletransactionmanager(object):
419 def transaction(self):
423 def transaction(self):
420 return None
424 return None
421
425
422 def close(self):
426 def close(self):
423 raise NotImplementedError
427 raise NotImplementedError
424
428
425 def release(self):
429 def release(self):
426 raise NotImplementedError
430 raise NotImplementedError
427
431
428 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
432 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
429 force=False):
433 force=False):
430 '''obtains a bundle of changes incoming from other
434 '''obtains a bundle of changes incoming from other
431
435
432 "onlyheads" restricts the returned changes to those reachable from the
436 "onlyheads" restricts the returned changes to those reachable from the
433 specified heads.
437 specified heads.
434 "bundlename", if given, stores the bundle to this file path permanently;
438 "bundlename", if given, stores the bundle to this file path permanently;
435 otherwise it's stored to a temp file and gets deleted again when you call
439 otherwise it's stored to a temp file and gets deleted again when you call
436 the returned "cleanupfn".
440 the returned "cleanupfn".
437 "force" indicates whether to proceed on unrelated repos.
441 "force" indicates whether to proceed on unrelated repos.
438
442
439 Returns a tuple (local, csets, cleanupfn):
443 Returns a tuple (local, csets, cleanupfn):
440
444
441 "local" is a local repo from which to obtain the actual incoming
445 "local" is a local repo from which to obtain the actual incoming
442 changesets; it is a bundlerepo for the obtained bundle when the
446 changesets; it is a bundlerepo for the obtained bundle when the
443 original "other" is remote.
447 original "other" is remote.
444 "csets" lists the incoming changeset node ids.
448 "csets" lists the incoming changeset node ids.
445 "cleanupfn" must be called without arguments when you're done processing
449 "cleanupfn" must be called without arguments when you're done processing
446 the changes; it closes both the original "other" and the one returned
450 the changes; it closes both the original "other" and the one returned
447 here.
451 here.
448 '''
452 '''
449 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
453 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
450 force=force)
454 force=force)
451 common, incoming, rheads = tmp
455 common, incoming, rheads = tmp
452 if not incoming:
456 if not incoming:
453 try:
457 try:
454 if bundlename:
458 if bundlename:
455 os.unlink(bundlename)
459 os.unlink(bundlename)
456 except OSError:
460 except OSError:
457 pass
461 pass
458 return repo, [], other.close
462 return repo, [], other.close
459
463
460 commonset = set(common)
464 commonset = set(common)
461 rheads = [x for x in rheads if x not in commonset]
465 rheads = [x for x in rheads if x not in commonset]
462
466
463 bundle = None
467 bundle = None
464 bundlerepo = None
468 bundlerepo = None
465 localrepo = other.local()
469 localrepo = other.local()
466 if bundlename or not localrepo:
470 if bundlename or not localrepo:
467 # create a bundle (uncompressed if other repo is not local)
471 # create a bundle (uncompressed if other repo is not local)
468
472
469 canbundle2 = (ui.configbool('experimental', 'bundle2-exp', True)
473 canbundle2 = (ui.configbool('experimental', 'bundle2-exp', True)
470 and other.capable('getbundle')
474 and other.capable('getbundle')
471 and other.capable('bundle2'))
475 and other.capable('bundle2'))
472 if canbundle2:
476 if canbundle2:
473 kwargs = {}
477 kwargs = {}
474 kwargs['common'] = common
478 kwargs['common'] = common
475 kwargs['heads'] = rheads
479 kwargs['heads'] = rheads
476 kwargs['bundlecaps'] = exchange.caps20to10(repo)
480 kwargs['bundlecaps'] = exchange.caps20to10(repo)
477 kwargs['cg'] = True
481 kwargs['cg'] = True
478 b2 = other.getbundle('incoming', **kwargs)
482 b2 = other.getbundle('incoming', **kwargs)
479 fname = bundle = changegroup.writechunks(ui, b2._forwardchunks(),
483 fname = bundle = changegroup.writechunks(ui, b2._forwardchunks(),
480 bundlename)
484 bundlename)
481 else:
485 else:
482 if other.capable('getbundle'):
486 if other.capable('getbundle'):
483 cg = other.getbundle('incoming', common=common, heads=rheads)
487 cg = other.getbundle('incoming', common=common, heads=rheads)
484 elif onlyheads is None and not other.capable('changegroupsubset'):
488 elif onlyheads is None and not other.capable('changegroupsubset'):
485 # compat with older servers when pulling all remote heads
489 # compat with older servers when pulling all remote heads
486 cg = other.changegroup(incoming, "incoming")
490 cg = other.changegroup(incoming, "incoming")
487 rheads = None
491 rheads = None
488 else:
492 else:
489 cg = other.changegroupsubset(incoming, rheads, 'incoming')
493 cg = other.changegroupsubset(incoming, rheads, 'incoming')
490 if localrepo:
494 if localrepo:
491 bundletype = "HG10BZ"
495 bundletype = "HG10BZ"
492 else:
496 else:
493 bundletype = "HG10UN"
497 bundletype = "HG10UN"
494 fname = bundle = changegroup.writebundle(ui, cg, bundlename,
498 fname = bundle = changegroup.writebundle(ui, cg, bundlename,
495 bundletype)
499 bundletype)
496 # keep written bundle?
500 # keep written bundle?
497 if bundlename:
501 if bundlename:
498 bundle = None
502 bundle = None
499 if not localrepo:
503 if not localrepo:
500 # use the created uncompressed bundlerepo
504 # use the created uncompressed bundlerepo
501 localrepo = bundlerepo = bundlerepository(repo.baseui, repo.root,
505 localrepo = bundlerepo = bundlerepository(repo.baseui, repo.root,
502 fname)
506 fname)
503 # this repo contains local and other now, so filter out local again
507 # this repo contains local and other now, so filter out local again
504 common = repo.heads()
508 common = repo.heads()
505 if localrepo:
509 if localrepo:
506 # Part of common may be remotely filtered
510 # Part of common may be remotely filtered
507 # So use an unfiltered version
511 # So use an unfiltered version
508 # The discovery process probably need cleanup to avoid that
512 # The discovery process probably need cleanup to avoid that
509 localrepo = localrepo.unfiltered()
513 localrepo = localrepo.unfiltered()
510
514
511 csets = localrepo.changelog.findmissing(common, rheads)
515 csets = localrepo.changelog.findmissing(common, rheads)
512
516
513 if bundlerepo:
517 if bundlerepo:
514 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev:]]
518 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev:]]
515 remotephases = other.listkeys('phases')
519 remotephases = other.listkeys('phases')
516
520
517 pullop = exchange.pulloperation(bundlerepo, other, heads=reponodes)
521 pullop = exchange.pulloperation(bundlerepo, other, heads=reponodes)
518 pullop.trmanager = bundletransactionmanager()
522 pullop.trmanager = bundletransactionmanager()
519 exchange._pullapplyphases(pullop, remotephases)
523 exchange._pullapplyphases(pullop, remotephases)
520
524
521 def cleanup():
525 def cleanup():
522 if bundlerepo:
526 if bundlerepo:
523 bundlerepo.close()
527 bundlerepo.close()
524 if bundle:
528 if bundle:
525 os.unlink(bundle)
529 os.unlink(bundle)
526 other.close()
530 other.close()
527
531
528 return (localrepo, csets, cleanup)
532 return (localrepo, csets, cleanup)
@@ -1,1102 +1,1107 b''
1 # changegroup.py - Mercurial changegroup manipulation functions
1 # changegroup.py - Mercurial changegroup manipulation functions
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import os
10 import os
11 import struct
11 import struct
12 import tempfile
12 import tempfile
13 import weakref
13 import weakref
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 hex,
17 hex,
18 nullid,
18 nullid,
19 nullrev,
19 nullrev,
20 short,
20 short,
21 )
21 )
22
22
23 from . import (
23 from . import (
24 branchmap,
24 branchmap,
25 dagutil,
25 dagutil,
26 discovery,
26 discovery,
27 error,
27 error,
28 mdiff,
28 mdiff,
29 phases,
29 phases,
30 util,
30 util,
31 )
31 )
32
32
33 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
33 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
34 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
34 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
35 _CHANGEGROUPV3_DELTA_HEADER = ">20s20s20s20s20sH"
35 _CHANGEGROUPV3_DELTA_HEADER = ">20s20s20s20s20sH"
36
36
37 def readexactly(stream, n):
37 def readexactly(stream, n):
38 '''read n bytes from stream.read and abort if less was available'''
38 '''read n bytes from stream.read and abort if less was available'''
39 s = stream.read(n)
39 s = stream.read(n)
40 if len(s) < n:
40 if len(s) < n:
41 raise error.Abort(_("stream ended unexpectedly"
41 raise error.Abort(_("stream ended unexpectedly"
42 " (got %d bytes, expected %d)")
42 " (got %d bytes, expected %d)")
43 % (len(s), n))
43 % (len(s), n))
44 return s
44 return s
45
45
46 def getchunk(stream):
46 def getchunk(stream):
47 """return the next chunk from stream as a string"""
47 """return the next chunk from stream as a string"""
48 d = readexactly(stream, 4)
48 d = readexactly(stream, 4)
49 l = struct.unpack(">l", d)[0]
49 l = struct.unpack(">l", d)[0]
50 if l <= 4:
50 if l <= 4:
51 if l:
51 if l:
52 raise error.Abort(_("invalid chunk length %d") % l)
52 raise error.Abort(_("invalid chunk length %d") % l)
53 return ""
53 return ""
54 return readexactly(stream, l - 4)
54 return readexactly(stream, l - 4)
55
55
56 def chunkheader(length):
56 def chunkheader(length):
57 """return a changegroup chunk header (string)"""
57 """return a changegroup chunk header (string)"""
58 return struct.pack(">l", length + 4)
58 return struct.pack(">l", length + 4)
59
59
60 def closechunk():
60 def closechunk():
61 """return a changegroup chunk header (string) for a zero-length chunk"""
61 """return a changegroup chunk header (string) for a zero-length chunk"""
62 return struct.pack(">l", 0)
62 return struct.pack(">l", 0)
63
63
64 def combineresults(results):
64 def combineresults(results):
65 """logic to combine 0 or more addchangegroup results into one"""
65 """logic to combine 0 or more addchangegroup results into one"""
66 changedheads = 0
66 changedheads = 0
67 result = 1
67 result = 1
68 for ret in results:
68 for ret in results:
69 # If any changegroup result is 0, return 0
69 # If any changegroup result is 0, return 0
70 if ret == 0:
70 if ret == 0:
71 result = 0
71 result = 0
72 break
72 break
73 if ret < -1:
73 if ret < -1:
74 changedheads += ret + 1
74 changedheads += ret + 1
75 elif ret > 1:
75 elif ret > 1:
76 changedheads += ret - 1
76 changedheads += ret - 1
77 if changedheads > 0:
77 if changedheads > 0:
78 result = 1 + changedheads
78 result = 1 + changedheads
79 elif changedheads < 0:
79 elif changedheads < 0:
80 result = -1 + changedheads
80 result = -1 + changedheads
81 return result
81 return result
82
82
83 bundletypes = {
83 bundletypes = {
84 "": ("", None), # only when using unbundle on ssh and old http servers
84 "": ("", None), # only when using unbundle on ssh and old http servers
85 # since the unification ssh accepts a header but there
85 # since the unification ssh accepts a header but there
86 # is no capability signaling it.
86 # is no capability signaling it.
87 "HG20": (), # special-cased below
87 "HG20": (), # special-cased below
88 "HG10UN": ("HG10UN", None),
88 "HG10UN": ("HG10UN", None),
89 "HG10BZ": ("HG10", 'BZ'),
89 "HG10BZ": ("HG10", 'BZ'),
90 "HG10GZ": ("HG10GZ", 'GZ'),
90 "HG10GZ": ("HG10GZ", 'GZ'),
91 }
91 }
92
92
93 # hgweb uses this list to communicate its preferred type
93 # hgweb uses this list to communicate its preferred type
94 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
94 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
95
95
96 def writechunks(ui, chunks, filename, vfs=None):
96 def writechunks(ui, chunks, filename, vfs=None):
97 """Write chunks to a file and return its filename.
97 """Write chunks to a file and return its filename.
98
98
99 The stream is assumed to be a bundle file.
99 The stream is assumed to be a bundle file.
100 Existing files will not be overwritten.
100 Existing files will not be overwritten.
101 If no filename is specified, a temporary file is created.
101 If no filename is specified, a temporary file is created.
102 """
102 """
103 fh = None
103 fh = None
104 cleanup = None
104 cleanup = None
105 try:
105 try:
106 if filename:
106 if filename:
107 if vfs:
107 if vfs:
108 fh = vfs.open(filename, "wb")
108 fh = vfs.open(filename, "wb")
109 else:
109 else:
110 fh = open(filename, "wb")
110 fh = open(filename, "wb")
111 else:
111 else:
112 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
112 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
113 fh = os.fdopen(fd, "wb")
113 fh = os.fdopen(fd, "wb")
114 cleanup = filename
114 cleanup = filename
115 for c in chunks:
115 for c in chunks:
116 fh.write(c)
116 fh.write(c)
117 cleanup = None
117 cleanup = None
118 return filename
118 return filename
119 finally:
119 finally:
120 if fh is not None:
120 if fh is not None:
121 fh.close()
121 fh.close()
122 if cleanup is not None:
122 if cleanup is not None:
123 if filename and vfs:
123 if filename and vfs:
124 vfs.unlink(cleanup)
124 vfs.unlink(cleanup)
125 else:
125 else:
126 os.unlink(cleanup)
126 os.unlink(cleanup)
127
127
128 def writebundle(ui, cg, filename, bundletype, vfs=None, compression=None):
128 def writebundle(ui, cg, filename, bundletype, vfs=None, compression=None):
129 """Write a bundle file and return its filename.
129 """Write a bundle file and return its filename.
130
130
131 Existing files will not be overwritten.
131 Existing files will not be overwritten.
132 If no filename is specified, a temporary file is created.
132 If no filename is specified, a temporary file is created.
133 bz2 compression can be turned off.
133 bz2 compression can be turned off.
134 The bundle file will be deleted in case of errors.
134 The bundle file will be deleted in case of errors.
135 """
135 """
136
136
137 if bundletype == "HG20":
137 if bundletype == "HG20":
138 from . import bundle2
138 from . import bundle2
139 bundle = bundle2.bundle20(ui)
139 bundle = bundle2.bundle20(ui)
140 bundle.setcompression(compression)
140 bundle.setcompression(compression)
141 part = bundle.newpart('changegroup', data=cg.getchunks())
141 part = bundle.newpart('changegroup', data=cg.getchunks())
142 part.addparam('version', cg.version)
142 part.addparam('version', cg.version)
143 chunkiter = bundle.getchunks()
143 chunkiter = bundle.getchunks()
144 else:
144 else:
145 # compression argument is only for the bundle2 case
145 # compression argument is only for the bundle2 case
146 assert compression is None
146 assert compression is None
147 if cg.version != '01':
147 if cg.version != '01':
148 raise error.Abort(_('old bundle types only supports v1 '
148 raise error.Abort(_('old bundle types only supports v1 '
149 'changegroups'))
149 'changegroups'))
150 header, comp = bundletypes[bundletype]
150 header, comp = bundletypes[bundletype]
151 if comp not in util.compressors:
151 if comp not in util.compressors:
152 raise error.Abort(_('unknown stream compression type: %s')
152 raise error.Abort(_('unknown stream compression type: %s')
153 % comp)
153 % comp)
154 z = util.compressors[comp]()
154 z = util.compressors[comp]()
155 subchunkiter = cg.getchunks()
155 subchunkiter = cg.getchunks()
156 def chunkiter():
156 def chunkiter():
157 yield header
157 yield header
158 for chunk in subchunkiter:
158 for chunk in subchunkiter:
159 yield z.compress(chunk)
159 yield z.compress(chunk)
160 yield z.flush()
160 yield z.flush()
161 chunkiter = chunkiter()
161 chunkiter = chunkiter()
162
162
163 # parse the changegroup data, otherwise we will block
163 # parse the changegroup data, otherwise we will block
164 # in case of sshrepo because we don't know the end of the stream
164 # in case of sshrepo because we don't know the end of the stream
165
165
166 # an empty chunkgroup is the end of the changegroup
166 # an empty chunkgroup is the end of the changegroup
167 # a changegroup has at least 2 chunkgroups (changelog and manifest).
167 # a changegroup has at least 2 chunkgroups (changelog and manifest).
168 # after that, an empty chunkgroup is the end of the changegroup
168 # after that, an empty chunkgroup is the end of the changegroup
169 return writechunks(ui, chunkiter, filename, vfs=vfs)
169 return writechunks(ui, chunkiter, filename, vfs=vfs)
170
170
171 class cg1unpacker(object):
171 class cg1unpacker(object):
172 """Unpacker for cg1 changegroup streams.
172 """Unpacker for cg1 changegroup streams.
173
173
174 A changegroup unpacker handles the framing of the revision data in
174 A changegroup unpacker handles the framing of the revision data in
175 the wire format. Most consumers will want to use the apply()
175 the wire format. Most consumers will want to use the apply()
176 method to add the changes from the changegroup to a repository.
176 method to add the changes from the changegroup to a repository.
177
177
178 If you're forwarding a changegroup unmodified to another consumer,
178 If you're forwarding a changegroup unmodified to another consumer,
179 use getchunks(), which returns an iterator of changegroup
179 use getchunks(), which returns an iterator of changegroup
180 chunks. This is mostly useful for cases where you need to know the
180 chunks. This is mostly useful for cases where you need to know the
181 data stream has ended by observing the end of the changegroup.
181 data stream has ended by observing the end of the changegroup.
182
182
183 deltachunk() is useful only if you're applying delta data. Most
183 deltachunk() is useful only if you're applying delta data. Most
184 consumers should prefer apply() instead.
184 consumers should prefer apply() instead.
185
185
186 A few other public methods exist. Those are used only for
186 A few other public methods exist. Those are used only for
187 bundlerepo and some debug commands - their use is discouraged.
187 bundlerepo and some debug commands - their use is discouraged.
188 """
188 """
189 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
189 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
190 deltaheadersize = struct.calcsize(deltaheader)
190 deltaheadersize = struct.calcsize(deltaheader)
191 version = '01'
191 version = '01'
192 def __init__(self, fh, alg):
192 def __init__(self, fh, alg):
193 if alg == 'UN':
193 if alg == 'UN':
194 alg = None # get more modern without breaking too much
194 alg = None # get more modern without breaking too much
195 if not alg in util.decompressors:
195 if not alg in util.decompressors:
196 raise error.Abort(_('unknown stream compression type: %s')
196 raise error.Abort(_('unknown stream compression type: %s')
197 % alg)
197 % alg)
198 if alg == 'BZ':
198 if alg == 'BZ':
199 alg = '_truncatedBZ'
199 alg = '_truncatedBZ'
200 self._stream = util.decompressors[alg](fh)
200 self._stream = util.decompressors[alg](fh)
201 self._type = alg
201 self._type = alg
202 self.callback = None
202 self.callback = None
203
203
204 # These methods (compressed, read, seek, tell) all appear to only
204 # These methods (compressed, read, seek, tell) all appear to only
205 # be used by bundlerepo, but it's a little hard to tell.
205 # be used by bundlerepo, but it's a little hard to tell.
206 def compressed(self):
206 def compressed(self):
207 return self._type is not None
207 return self._type is not None
208 def read(self, l):
208 def read(self, l):
209 return self._stream.read(l)
209 return self._stream.read(l)
210 def seek(self, pos):
210 def seek(self, pos):
211 return self._stream.seek(pos)
211 return self._stream.seek(pos)
212 def tell(self):
212 def tell(self):
213 return self._stream.tell()
213 return self._stream.tell()
214 def close(self):
214 def close(self):
215 return self._stream.close()
215 return self._stream.close()
216
216
217 def _chunklength(self):
217 def _chunklength(self):
218 d = readexactly(self._stream, 4)
218 d = readexactly(self._stream, 4)
219 l = struct.unpack(">l", d)[0]
219 l = struct.unpack(">l", d)[0]
220 if l <= 4:
220 if l <= 4:
221 if l:
221 if l:
222 raise error.Abort(_("invalid chunk length %d") % l)
222 raise error.Abort(_("invalid chunk length %d") % l)
223 return 0
223 return 0
224 if self.callback:
224 if self.callback:
225 self.callback()
225 self.callback()
226 return l - 4
226 return l - 4
227
227
228 def changelogheader(self):
228 def changelogheader(self):
229 """v10 does not have a changelog header chunk"""
229 """v10 does not have a changelog header chunk"""
230 return {}
230 return {}
231
231
232 def manifestheader(self):
232 def manifestheader(self):
233 """v10 does not have a manifest header chunk"""
233 """v10 does not have a manifest header chunk"""
234 return {}
234 return {}
235
235
236 def filelogheader(self):
236 def filelogheader(self):
237 """return the header of the filelogs chunk, v10 only has the filename"""
237 """return the header of the filelogs chunk, v10 only has the filename"""
238 l = self._chunklength()
238 l = self._chunklength()
239 if not l:
239 if not l:
240 return {}
240 return {}
241 fname = readexactly(self._stream, l)
241 fname = readexactly(self._stream, l)
242 return {'filename': fname}
242 return {'filename': fname}
243
243
244 def _deltaheader(self, headertuple, prevnode):
244 def _deltaheader(self, headertuple, prevnode):
245 node, p1, p2, cs = headertuple
245 node, p1, p2, cs = headertuple
246 if prevnode is None:
246 if prevnode is None:
247 deltabase = p1
247 deltabase = p1
248 else:
248 else:
249 deltabase = prevnode
249 deltabase = prevnode
250 flags = 0
250 flags = 0
251 return node, p1, p2, deltabase, cs, flags
251 return node, p1, p2, deltabase, cs, flags
252
252
253 def deltachunk(self, prevnode):
253 def deltachunk(self, prevnode):
254 l = self._chunklength()
254 l = self._chunklength()
255 if not l:
255 if not l:
256 return {}
256 return {}
257 headerdata = readexactly(self._stream, self.deltaheadersize)
257 headerdata = readexactly(self._stream, self.deltaheadersize)
258 header = struct.unpack(self.deltaheader, headerdata)
258 header = struct.unpack(self.deltaheader, headerdata)
259 delta = readexactly(self._stream, l - self.deltaheadersize)
259 delta = readexactly(self._stream, l - self.deltaheadersize)
260 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
260 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
261 return {'node': node, 'p1': p1, 'p2': p2, 'cs': cs,
261 return {'node': node, 'p1': p1, 'p2': p2, 'cs': cs,
262 'deltabase': deltabase, 'delta': delta, 'flags': flags}
262 'deltabase': deltabase, 'delta': delta, 'flags': flags}
263
263
264 def getchunks(self):
264 def getchunks(self):
265 """returns all the chunks contains in the bundle
265 """returns all the chunks contains in the bundle
266
266
267 Used when you need to forward the binary stream to a file or another
267 Used when you need to forward the binary stream to a file or another
268 network API. To do so, it parse the changegroup data, otherwise it will
268 network API. To do so, it parse the changegroup data, otherwise it will
269 block in case of sshrepo because it don't know the end of the stream.
269 block in case of sshrepo because it don't know the end of the stream.
270 """
270 """
271 # an empty chunkgroup is the end of the changegroup
271 # an empty chunkgroup is the end of the changegroup
272 # a changegroup has at least 2 chunkgroups (changelog and manifest).
272 # a changegroup has at least 2 chunkgroups (changelog and manifest).
273 # after that, an empty chunkgroup is the end of the changegroup
273 # after that, an empty chunkgroup is the end of the changegroup
274 empty = False
274 empty = False
275 count = 0
275 count = 0
276 while not empty or count <= 2:
276 while not empty or count <= 2:
277 empty = True
277 empty = True
278 count += 1
278 count += 1
279 while True:
279 while True:
280 chunk = getchunk(self)
280 chunk = getchunk(self)
281 if not chunk:
281 if not chunk:
282 break
282 break
283 empty = False
283 empty = False
284 yield chunkheader(len(chunk))
284 yield chunkheader(len(chunk))
285 pos = 0
285 pos = 0
286 while pos < len(chunk):
286 while pos < len(chunk):
287 next = pos + 2**20
287 next = pos + 2**20
288 yield chunk[pos:next]
288 yield chunk[pos:next]
289 pos = next
289 pos = next
290 yield closechunk()
290 yield closechunk()
291
291
292 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
292 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
293 # We know that we'll never have more manifests than we had
293 # We know that we'll never have more manifests than we had
294 # changesets.
294 # changesets.
295 self.callback = prog(_('manifests'), numchanges)
295 self.callback = prog(_('manifests'), numchanges)
296 # no need to check for empty manifest group here:
296 # no need to check for empty manifest group here:
297 # if the result of the merge of 1 and 2 is the same in 3 and 4,
297 # if the result of the merge of 1 and 2 is the same in 3 and 4,
298 # no new manifest will be created and the manifest group will
298 # no new manifest will be created and the manifest group will
299 # be empty during the pull
299 # be empty during the pull
300 self.manifestheader()
300 self.manifestheader()
301 repo.manifest.addgroup(self, revmap, trp)
301 repo.manifest.addgroup(self, revmap, trp)
302 repo.ui.progress(_('manifests'), None)
302 repo.ui.progress(_('manifests'), None)
303
303
304 def apply(self, repo, srctype, url, emptyok=False,
304 def apply(self, repo, srctype, url, emptyok=False,
305 targetphase=phases.draft, expectedtotal=None):
305 targetphase=phases.draft, expectedtotal=None):
306 """Add the changegroup returned by source.read() to this repo.
306 """Add the changegroup returned by source.read() to this repo.
307 srctype is a string like 'push', 'pull', or 'unbundle'. url is
307 srctype is a string like 'push', 'pull', or 'unbundle'. url is
308 the URL of the repo where this changegroup is coming from.
308 the URL of the repo where this changegroup is coming from.
309
309
310 Return an integer summarizing the change to this repo:
310 Return an integer summarizing the change to this repo:
311 - nothing changed or no source: 0
311 - nothing changed or no source: 0
312 - more heads than before: 1+added heads (2..n)
312 - more heads than before: 1+added heads (2..n)
313 - fewer heads than before: -1-removed heads (-2..-n)
313 - fewer heads than before: -1-removed heads (-2..-n)
314 - number of heads stays the same: 1
314 - number of heads stays the same: 1
315 """
315 """
316 repo = repo.unfiltered()
316 repo = repo.unfiltered()
317 def csmap(x):
317 def csmap(x):
318 repo.ui.debug("add changeset %s\n" % short(x))
318 repo.ui.debug("add changeset %s\n" % short(x))
319 return len(cl)
319 return len(cl)
320
320
321 def revmap(x):
321 def revmap(x):
322 return cl.rev(x)
322 return cl.rev(x)
323
323
324 changesets = files = revisions = 0
324 changesets = files = revisions = 0
325
325
326 tr = repo.transaction("\n".join([srctype, util.hidepassword(url)]))
326 tr = repo.transaction("\n".join([srctype, util.hidepassword(url)]))
327 try:
327 try:
328 # The transaction could have been created before and already
328 # The transaction could have been created before and already
329 # carries source information. In this case we use the top
329 # carries source information. In this case we use the top
330 # level data. We overwrite the argument because we need to use
330 # level data. We overwrite the argument because we need to use
331 # the top level value (if they exist) in this function.
331 # the top level value (if they exist) in this function.
332 srctype = tr.hookargs.setdefault('source', srctype)
332 srctype = tr.hookargs.setdefault('source', srctype)
333 url = tr.hookargs.setdefault('url', url)
333 url = tr.hookargs.setdefault('url', url)
334 repo.hook('prechangegroup', throw=True, **tr.hookargs)
334 repo.hook('prechangegroup', throw=True, **tr.hookargs)
335
335
336 # write changelog data to temp files so concurrent readers
336 # write changelog data to temp files so concurrent readers
337 # will not see an inconsistent view
337 # will not see an inconsistent view
338 cl = repo.changelog
338 cl = repo.changelog
339 cl.delayupdate(tr)
339 cl.delayupdate(tr)
340 oldheads = cl.heads()
340 oldheads = cl.heads()
341
341
342 trp = weakref.proxy(tr)
342 trp = weakref.proxy(tr)
343 # pull off the changeset group
343 # pull off the changeset group
344 repo.ui.status(_("adding changesets\n"))
344 repo.ui.status(_("adding changesets\n"))
345 clstart = len(cl)
345 clstart = len(cl)
346 class prog(object):
346 class prog(object):
347 def __init__(self, step, total):
347 def __init__(self, step, total):
348 self._step = step
348 self._step = step
349 self._total = total
349 self._total = total
350 self._count = 1
350 self._count = 1
351 def __call__(self):
351 def __call__(self):
352 repo.ui.progress(self._step, self._count, unit=_('chunks'),
352 repo.ui.progress(self._step, self._count, unit=_('chunks'),
353 total=self._total)
353 total=self._total)
354 self._count += 1
354 self._count += 1
355 self.callback = prog(_('changesets'), expectedtotal)
355 self.callback = prog(_('changesets'), expectedtotal)
356
356
357 efiles = set()
357 efiles = set()
358 def onchangelog(cl, node):
358 def onchangelog(cl, node):
359 efiles.update(cl.read(node)[3])
359 efiles.update(cl.read(node)[3])
360
360
361 self.changelogheader()
361 self.changelogheader()
362 srccontent = cl.addgroup(self, csmap, trp,
362 srccontent = cl.addgroup(self, csmap, trp,
363 addrevisioncb=onchangelog)
363 addrevisioncb=onchangelog)
364 efiles = len(efiles)
364 efiles = len(efiles)
365
365
366 if not (srccontent or emptyok):
366 if not (srccontent or emptyok):
367 raise error.Abort(_("received changelog group is empty"))
367 raise error.Abort(_("received changelog group is empty"))
368 clend = len(cl)
368 clend = len(cl)
369 changesets = clend - clstart
369 changesets = clend - clstart
370 repo.ui.progress(_('changesets'), None)
370 repo.ui.progress(_('changesets'), None)
371
371
372 # pull off the manifest group
372 # pull off the manifest group
373 repo.ui.status(_("adding manifests\n"))
373 repo.ui.status(_("adding manifests\n"))
374 self._unpackmanifests(repo, revmap, trp, prog, changesets)
374 self._unpackmanifests(repo, revmap, trp, prog, changesets)
375
375
376 needfiles = {}
376 needfiles = {}
377 if repo.ui.configbool('server', 'validate', default=False):
377 if repo.ui.configbool('server', 'validate', default=False):
378 # validate incoming csets have their manifests
378 # validate incoming csets have their manifests
379 for cset in xrange(clstart, clend):
379 for cset in xrange(clstart, clend):
380 mfnode = repo.changelog.read(repo.changelog.node(cset))[0]
380 mfnode = repo.changelog.read(repo.changelog.node(cset))[0]
381 mfest = repo.manifest.readdelta(mfnode)
381 mfest = repo.manifest.readdelta(mfnode)
382 # store file nodes we must see
382 # store file nodes we must see
383 for f, n in mfest.iteritems():
383 for f, n in mfest.iteritems():
384 needfiles.setdefault(f, set()).add(n)
384 needfiles.setdefault(f, set()).add(n)
385
385
386 # process the files
386 # process the files
387 repo.ui.status(_("adding file changes\n"))
387 repo.ui.status(_("adding file changes\n"))
388 self.callback = None
388 self.callback = None
389 pr = prog(_('files'), efiles)
389 pr = prog(_('files'), efiles)
390 newrevs, newfiles = _addchangegroupfiles(
390 newrevs, newfiles = _addchangegroupfiles(
391 repo, self, revmap, trp, pr, needfiles)
391 repo, self, revmap, trp, pr, needfiles)
392 revisions += newrevs
392 revisions += newrevs
393 files += newfiles
393 files += newfiles
394
394
395 dh = 0
395 dh = 0
396 if oldheads:
396 if oldheads:
397 heads = cl.heads()
397 heads = cl.heads()
398 dh = len(heads) - len(oldheads)
398 dh = len(heads) - len(oldheads)
399 for h in heads:
399 for h in heads:
400 if h not in oldheads and repo[h].closesbranch():
400 if h not in oldheads and repo[h].closesbranch():
401 dh -= 1
401 dh -= 1
402 htext = ""
402 htext = ""
403 if dh:
403 if dh:
404 htext = _(" (%+d heads)") % dh
404 htext = _(" (%+d heads)") % dh
405
405
406 repo.ui.status(_("added %d changesets"
406 repo.ui.status(_("added %d changesets"
407 " with %d changes to %d files%s\n")
407 " with %d changes to %d files%s\n")
408 % (changesets, revisions, files, htext))
408 % (changesets, revisions, files, htext))
409 repo.invalidatevolatilesets()
409 repo.invalidatevolatilesets()
410
410
411 if changesets > 0:
411 if changesets > 0:
412 if 'node' not in tr.hookargs:
412 if 'node' not in tr.hookargs:
413 tr.hookargs['node'] = hex(cl.node(clstart))
413 tr.hookargs['node'] = hex(cl.node(clstart))
414 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
414 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
415 hookargs = dict(tr.hookargs)
415 hookargs = dict(tr.hookargs)
416 else:
416 else:
417 hookargs = dict(tr.hookargs)
417 hookargs = dict(tr.hookargs)
418 hookargs['node'] = hex(cl.node(clstart))
418 hookargs['node'] = hex(cl.node(clstart))
419 hookargs['node_last'] = hex(cl.node(clend - 1))
419 hookargs['node_last'] = hex(cl.node(clend - 1))
420 repo.hook('pretxnchangegroup', throw=True, **hookargs)
420 repo.hook('pretxnchangegroup', throw=True, **hookargs)
421
421
422 added = [cl.node(r) for r in xrange(clstart, clend)]
422 added = [cl.node(r) for r in xrange(clstart, clend)]
423 publishing = repo.publishing()
423 publishing = repo.publishing()
424 if srctype in ('push', 'serve'):
424 if srctype in ('push', 'serve'):
425 # Old servers can not push the boundary themselves.
425 # Old servers can not push the boundary themselves.
426 # New servers won't push the boundary if changeset already
426 # New servers won't push the boundary if changeset already
427 # exists locally as secret
427 # exists locally as secret
428 #
428 #
429 # We should not use added here but the list of all change in
429 # We should not use added here but the list of all change in
430 # the bundle
430 # the bundle
431 if publishing:
431 if publishing:
432 phases.advanceboundary(repo, tr, phases.public, srccontent)
432 phases.advanceboundary(repo, tr, phases.public, srccontent)
433 else:
433 else:
434 # Those changesets have been pushed from the outside, their
434 # Those changesets have been pushed from the outside, their
435 # phases are going to be pushed alongside. Therefor
435 # phases are going to be pushed alongside. Therefor
436 # `targetphase` is ignored.
436 # `targetphase` is ignored.
437 phases.advanceboundary(repo, tr, phases.draft, srccontent)
437 phases.advanceboundary(repo, tr, phases.draft, srccontent)
438 phases.retractboundary(repo, tr, phases.draft, added)
438 phases.retractboundary(repo, tr, phases.draft, added)
439 elif srctype != 'strip':
439 elif srctype != 'strip':
440 # publishing only alter behavior during push
440 # publishing only alter behavior during push
441 #
441 #
442 # strip should not touch boundary at all
442 # strip should not touch boundary at all
443 phases.retractboundary(repo, tr, targetphase, added)
443 phases.retractboundary(repo, tr, targetphase, added)
444
444
445 if changesets > 0:
445 if changesets > 0:
446 if srctype != 'strip':
446 if srctype != 'strip':
447 # During strip, branchcache is invalid but coming call to
447 # During strip, branchcache is invalid but coming call to
448 # `destroyed` will repair it.
448 # `destroyed` will repair it.
449 # In other case we can safely update cache on disk.
449 # In other case we can safely update cache on disk.
450 branchmap.updatecache(repo.filtered('served'))
450 branchmap.updatecache(repo.filtered('served'))
451
451
452 def runhooks():
452 def runhooks():
453 # These hooks run when the lock releases, not when the
453 # These hooks run when the lock releases, not when the
454 # transaction closes. So it's possible for the changelog
454 # transaction closes. So it's possible for the changelog
455 # to have changed since we last saw it.
455 # to have changed since we last saw it.
456 if clstart >= len(repo):
456 if clstart >= len(repo):
457 return
457 return
458
458
459 # forcefully update the on-disk branch cache
459 # forcefully update the on-disk branch cache
460 repo.ui.debug("updating the branch cache\n")
460 repo.ui.debug("updating the branch cache\n")
461 repo.hook("changegroup", **hookargs)
461 repo.hook("changegroup", **hookargs)
462
462
463 for n in added:
463 for n in added:
464 args = hookargs.copy()
464 args = hookargs.copy()
465 args['node'] = hex(n)
465 args['node'] = hex(n)
466 del args['node_last']
466 del args['node_last']
467 repo.hook("incoming", **args)
467 repo.hook("incoming", **args)
468
468
469 newheads = [h for h in repo.heads() if h not in oldheads]
469 newheads = [h for h in repo.heads() if h not in oldheads]
470 repo.ui.log("incoming",
470 repo.ui.log("incoming",
471 "%s incoming changes - new heads: %s\n",
471 "%s incoming changes - new heads: %s\n",
472 len(added),
472 len(added),
473 ', '.join([hex(c[:6]) for c in newheads]))
473 ', '.join([hex(c[:6]) for c in newheads]))
474
474
475 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
475 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
476 lambda tr: repo._afterlock(runhooks))
476 lambda tr: repo._afterlock(runhooks))
477
477
478 tr.close()
478 tr.close()
479
479
480 finally:
480 finally:
481 tr.release()
481 tr.release()
482 repo.ui.flush()
482 repo.ui.flush()
483 # never return 0 here:
483 # never return 0 here:
484 if dh < 0:
484 if dh < 0:
485 return dh - 1
485 return dh - 1
486 else:
486 else:
487 return dh + 1
487 return dh + 1
488
488
489 class cg2unpacker(cg1unpacker):
489 class cg2unpacker(cg1unpacker):
490 """Unpacker for cg2 streams.
490 """Unpacker for cg2 streams.
491
491
492 cg2 streams add support for generaldelta, so the delta header
492 cg2 streams add support for generaldelta, so the delta header
493 format is slightly different. All other features about the data
493 format is slightly different. All other features about the data
494 remain the same.
494 remain the same.
495 """
495 """
496 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
496 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
497 deltaheadersize = struct.calcsize(deltaheader)
497 deltaheadersize = struct.calcsize(deltaheader)
498 version = '02'
498 version = '02'
499
499
500 def _deltaheader(self, headertuple, prevnode):
500 def _deltaheader(self, headertuple, prevnode):
501 node, p1, p2, deltabase, cs = headertuple
501 node, p1, p2, deltabase, cs = headertuple
502 flags = 0
502 flags = 0
503 return node, p1, p2, deltabase, cs, flags
503 return node, p1, p2, deltabase, cs, flags
504
504
505 class cg3unpacker(cg2unpacker):
505 class cg3unpacker(cg2unpacker):
506 """Unpacker for cg3 streams.
506 """Unpacker for cg3 streams.
507
507
508 cg3 streams add support for exchanging treemanifests and revlog
508 cg3 streams add support for exchanging treemanifests and revlog
509 flags, so the only changes from cg2 are the delta header and
509 flags. It adds the revlog flags to the delta header and an empty chunk
510 version number.
510 separating manifests and files.
511 """
511 """
512 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
512 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
513 deltaheadersize = struct.calcsize(deltaheader)
513 deltaheadersize = struct.calcsize(deltaheader)
514 version = '03'
514 version = '03'
515
515
516 def _deltaheader(self, headertuple, prevnode):
516 def _deltaheader(self, headertuple, prevnode):
517 node, p1, p2, deltabase, cs, flags = headertuple
517 node, p1, p2, deltabase, cs, flags = headertuple
518 return node, p1, p2, deltabase, cs, flags
518 return node, p1, p2, deltabase, cs, flags
519
519
520 class headerlessfixup(object):
520 class headerlessfixup(object):
521 def __init__(self, fh, h):
521 def __init__(self, fh, h):
522 self._h = h
522 self._h = h
523 self._fh = fh
523 self._fh = fh
524 def read(self, n):
524 def read(self, n):
525 if self._h:
525 if self._h:
526 d, self._h = self._h[:n], self._h[n:]
526 d, self._h = self._h[:n], self._h[n:]
527 if len(d) < n:
527 if len(d) < n:
528 d += readexactly(self._fh, n - len(d))
528 d += readexactly(self._fh, n - len(d))
529 return d
529 return d
530 return readexactly(self._fh, n)
530 return readexactly(self._fh, n)
531
531
532 def _moddirs(files):
532 def _moddirs(files):
533 """Given a set of modified files, find the list of modified directories.
533 """Given a set of modified files, find the list of modified directories.
534
534
535 This returns a list of (path to changed dir, changed dir) tuples,
535 This returns a list of (path to changed dir, changed dir) tuples,
536 as that's what the one client needs anyway.
536 as that's what the one client needs anyway.
537
537
538 >>> _moddirs(['a/b/c.py', 'a/b/c.txt', 'a/d/e/f/g.txt', 'i.txt', ])
538 >>> _moddirs(['a/b/c.py', 'a/b/c.txt', 'a/d/e/f/g.txt', 'i.txt', ])
539 [('/', 'a/'), ('a/', 'b/'), ('a/', 'd/'), ('a/d/', 'e/'), ('a/d/e/', 'f/')]
539 [('/', 'a/'), ('a/', 'b/'), ('a/', 'd/'), ('a/d/', 'e/'), ('a/d/e/', 'f/')]
540
540
541 """
541 """
542 alldirs = set()
542 alldirs = set()
543 for f in files:
543 for f in files:
544 path = f.split('/')[:-1]
544 path = f.split('/')[:-1]
545 for i in xrange(len(path) - 1, -1, -1):
545 for i in xrange(len(path) - 1, -1, -1):
546 dn = '/'.join(path[:i])
546 dn = '/'.join(path[:i])
547 current = dn + '/', path[i] + '/'
547 current = dn + '/', path[i] + '/'
548 if current in alldirs:
548 if current in alldirs:
549 break
549 break
550 alldirs.add(current)
550 alldirs.add(current)
551 return sorted(alldirs)
551 return sorted(alldirs)
552
552
553 class cg1packer(object):
553 class cg1packer(object):
554 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
554 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
555 version = '01'
555 version = '01'
556 def __init__(self, repo, bundlecaps=None):
556 def __init__(self, repo, bundlecaps=None):
557 """Given a source repo, construct a bundler.
557 """Given a source repo, construct a bundler.
558
558
559 bundlecaps is optional and can be used to specify the set of
559 bundlecaps is optional and can be used to specify the set of
560 capabilities which can be used to build the bundle.
560 capabilities which can be used to build the bundle.
561 """
561 """
562 # Set of capabilities we can use to build the bundle.
562 # Set of capabilities we can use to build the bundle.
563 if bundlecaps is None:
563 if bundlecaps is None:
564 bundlecaps = set()
564 bundlecaps = set()
565 self._bundlecaps = bundlecaps
565 self._bundlecaps = bundlecaps
566 # experimental config: bundle.reorder
566 # experimental config: bundle.reorder
567 reorder = repo.ui.config('bundle', 'reorder', 'auto')
567 reorder = repo.ui.config('bundle', 'reorder', 'auto')
568 if reorder == 'auto':
568 if reorder == 'auto':
569 reorder = None
569 reorder = None
570 else:
570 else:
571 reorder = util.parsebool(reorder)
571 reorder = util.parsebool(reorder)
572 self._repo = repo
572 self._repo = repo
573 self._reorder = reorder
573 self._reorder = reorder
574 self._progress = repo.ui.progress
574 self._progress = repo.ui.progress
575 if self._repo.ui.verbose and not self._repo.ui.debugflag:
575 if self._repo.ui.verbose and not self._repo.ui.debugflag:
576 self._verbosenote = self._repo.ui.note
576 self._verbosenote = self._repo.ui.note
577 else:
577 else:
578 self._verbosenote = lambda s: None
578 self._verbosenote = lambda s: None
579
579
580 def close(self):
580 def close(self):
581 return closechunk()
581 return closechunk()
582
582
583 def fileheader(self, fname):
583 def fileheader(self, fname):
584 return chunkheader(len(fname)) + fname
584 return chunkheader(len(fname)) + fname
585
585
586 def group(self, nodelist, revlog, lookup, units=None):
586 def group(self, nodelist, revlog, lookup, units=None):
587 """Calculate a delta group, yielding a sequence of changegroup chunks
587 """Calculate a delta group, yielding a sequence of changegroup chunks
588 (strings).
588 (strings).
589
589
590 Given a list of changeset revs, return a set of deltas and
590 Given a list of changeset revs, return a set of deltas and
591 metadata corresponding to nodes. The first delta is
591 metadata corresponding to nodes. The first delta is
592 first parent(nodelist[0]) -> nodelist[0], the receiver is
592 first parent(nodelist[0]) -> nodelist[0], the receiver is
593 guaranteed to have this parent as it has all history before
593 guaranteed to have this parent as it has all history before
594 these changesets. In the case firstparent is nullrev the
594 these changesets. In the case firstparent is nullrev the
595 changegroup starts with a full revision.
595 changegroup starts with a full revision.
596
596
597 If units is not None, progress detail will be generated, units specifies
597 If units is not None, progress detail will be generated, units specifies
598 the type of revlog that is touched (changelog, manifest, etc.).
598 the type of revlog that is touched (changelog, manifest, etc.).
599 """
599 """
600 # if we don't have any revisions touched by these changesets, bail
600 # if we don't have any revisions touched by these changesets, bail
601 if len(nodelist) == 0:
601 if len(nodelist) == 0:
602 yield self.close()
602 yield self.close()
603 return
603 return
604
604
605 # for generaldelta revlogs, we linearize the revs; this will both be
605 # for generaldelta revlogs, we linearize the revs; this will both be
606 # much quicker and generate a much smaller bundle
606 # much quicker and generate a much smaller bundle
607 if (revlog._generaldelta and self._reorder is None) or self._reorder:
607 if (revlog._generaldelta and self._reorder is None) or self._reorder:
608 dag = dagutil.revlogdag(revlog)
608 dag = dagutil.revlogdag(revlog)
609 revs = set(revlog.rev(n) for n in nodelist)
609 revs = set(revlog.rev(n) for n in nodelist)
610 revs = dag.linearize(revs)
610 revs = dag.linearize(revs)
611 else:
611 else:
612 revs = sorted([revlog.rev(n) for n in nodelist])
612 revs = sorted([revlog.rev(n) for n in nodelist])
613
613
614 # add the parent of the first rev
614 # add the parent of the first rev
615 p = revlog.parentrevs(revs[0])[0]
615 p = revlog.parentrevs(revs[0])[0]
616 revs.insert(0, p)
616 revs.insert(0, p)
617
617
618 # build deltas
618 # build deltas
619 total = len(revs) - 1
619 total = len(revs) - 1
620 msgbundling = _('bundling')
620 msgbundling = _('bundling')
621 for r in xrange(len(revs) - 1):
621 for r in xrange(len(revs) - 1):
622 if units is not None:
622 if units is not None:
623 self._progress(msgbundling, r + 1, unit=units, total=total)
623 self._progress(msgbundling, r + 1, unit=units, total=total)
624 prev, curr = revs[r], revs[r + 1]
624 prev, curr = revs[r], revs[r + 1]
625 linknode = lookup(revlog.node(curr))
625 linknode = lookup(revlog.node(curr))
626 for c in self.revchunk(revlog, curr, prev, linknode):
626 for c in self.revchunk(revlog, curr, prev, linknode):
627 yield c
627 yield c
628
628
629 if units is not None:
629 if units is not None:
630 self._progress(msgbundling, None)
630 self._progress(msgbundling, None)
631 yield self.close()
631 yield self.close()
632
632
633 # filter any nodes that claim to be part of the known set
633 # filter any nodes that claim to be part of the known set
634 def prune(self, revlog, missing, commonrevs):
634 def prune(self, revlog, missing, commonrevs):
635 rr, rl = revlog.rev, revlog.linkrev
635 rr, rl = revlog.rev, revlog.linkrev
636 return [n for n in missing if rl(rr(n)) not in commonrevs]
636 return [n for n in missing if rl(rr(n)) not in commonrevs]
637
637
638 def _packmanifests(self, mfnodes, tmfnodes, lookuplinknode):
638 def _packmanifests(self, mfnodes, tmfnodes, lookuplinknode):
639 """Pack flat manifests into a changegroup stream."""
639 """Pack flat manifests into a changegroup stream."""
640 ml = self._repo.manifest
640 ml = self._repo.manifest
641 size = 0
641 size = 0
642 for chunk in self.group(
642 for chunk in self.group(
643 mfnodes, ml, lookuplinknode, units=_('manifests')):
643 mfnodes, ml, lookuplinknode, units=_('manifests')):
644 size += len(chunk)
644 size += len(chunk)
645 yield chunk
645 yield chunk
646 self._verbosenote(_('%8.i (manifests)\n') % size)
646 self._verbosenote(_('%8.i (manifests)\n') % size)
647 # It looks odd to assert this here, but tmfnodes doesn't get
647 # It looks odd to assert this here, but tmfnodes doesn't get
648 # filled in until after we've called lookuplinknode for
648 # filled in until after we've called lookuplinknode for
649 # sending root manifests, so the only way to tell the streams
649 # sending root manifests, so the only way to tell the streams
650 # got crossed is to check after we've done all the work.
650 # got crossed is to check after we've done all the work.
651 assert not tmfnodes
651 assert not tmfnodes
652
652
653 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
653 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
654 '''yield a sequence of changegroup chunks (strings)'''
654 '''yield a sequence of changegroup chunks (strings)'''
655 repo = self._repo
655 repo = self._repo
656 cl = repo.changelog
656 cl = repo.changelog
657 ml = repo.manifest
657 ml = repo.manifest
658
658
659 clrevorder = {}
659 clrevorder = {}
660 mfs = {} # needed manifests
660 mfs = {} # needed manifests
661 tmfnodes = {}
661 tmfnodes = {}
662 fnodes = {} # needed file nodes
662 fnodes = {} # needed file nodes
663 # maps manifest node id -> set(changed files)
663 # maps manifest node id -> set(changed files)
664 mfchangedfiles = {}
664 mfchangedfiles = {}
665
665
666 # Callback for the changelog, used to collect changed files and manifest
666 # Callback for the changelog, used to collect changed files and manifest
667 # nodes.
667 # nodes.
668 # Returns the linkrev node (identity in the changelog case).
668 # Returns the linkrev node (identity in the changelog case).
669 def lookupcl(x):
669 def lookupcl(x):
670 c = cl.read(x)
670 c = cl.read(x)
671 clrevorder[x] = len(clrevorder)
671 clrevorder[x] = len(clrevorder)
672 n = c[0]
672 n = c[0]
673 # record the first changeset introducing this manifest version
673 # record the first changeset introducing this manifest version
674 mfs.setdefault(n, x)
674 mfs.setdefault(n, x)
675 # Record a complete list of potentially-changed files in
675 # Record a complete list of potentially-changed files in
676 # this manifest.
676 # this manifest.
677 mfchangedfiles.setdefault(n, set()).update(c[3])
677 mfchangedfiles.setdefault(n, set()).update(c[3])
678 return x
678 return x
679
679
680 self._verbosenote(_('uncompressed size of bundle content:\n'))
680 self._verbosenote(_('uncompressed size of bundle content:\n'))
681 size = 0
681 size = 0
682 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
682 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
683 size += len(chunk)
683 size += len(chunk)
684 yield chunk
684 yield chunk
685 self._verbosenote(_('%8.i (changelog)\n') % size)
685 self._verbosenote(_('%8.i (changelog)\n') % size)
686
686
687 # We need to make sure that the linkrev in the changegroup refers to
687 # We need to make sure that the linkrev in the changegroup refers to
688 # the first changeset that introduced the manifest or file revision.
688 # the first changeset that introduced the manifest or file revision.
689 # The fastpath is usually safer than the slowpath, because the filelogs
689 # The fastpath is usually safer than the slowpath, because the filelogs
690 # are walked in revlog order.
690 # are walked in revlog order.
691 #
691 #
692 # When taking the slowpath with reorder=None and the manifest revlog
692 # When taking the slowpath with reorder=None and the manifest revlog
693 # uses generaldelta, the manifest may be walked in the "wrong" order.
693 # uses generaldelta, the manifest may be walked in the "wrong" order.
694 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
694 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
695 # cc0ff93d0c0c).
695 # cc0ff93d0c0c).
696 #
696 #
697 # When taking the fastpath, we are only vulnerable to reordering
697 # When taking the fastpath, we are only vulnerable to reordering
698 # of the changelog itself. The changelog never uses generaldelta, so
698 # of the changelog itself. The changelog never uses generaldelta, so
699 # it is only reordered when reorder=True. To handle this case, we
699 # it is only reordered when reorder=True. To handle this case, we
700 # simply take the slowpath, which already has the 'clrevorder' logic.
700 # simply take the slowpath, which already has the 'clrevorder' logic.
701 # This was also fixed in cc0ff93d0c0c.
701 # This was also fixed in cc0ff93d0c0c.
702 fastpathlinkrev = fastpathlinkrev and not self._reorder
702 fastpathlinkrev = fastpathlinkrev and not self._reorder
703 # Treemanifests don't work correctly with fastpathlinkrev
703 # Treemanifests don't work correctly with fastpathlinkrev
704 # either, because we don't discover which directory nodes to
704 # either, because we don't discover which directory nodes to
705 # send along with files. This could probably be fixed.
705 # send along with files. This could probably be fixed.
706 fastpathlinkrev = fastpathlinkrev and (
706 fastpathlinkrev = fastpathlinkrev and (
707 'treemanifest' not in repo.requirements)
707 'treemanifest' not in repo.requirements)
708 # Callback for the manifest, used to collect linkrevs for filelog
708 # Callback for the manifest, used to collect linkrevs for filelog
709 # revisions.
709 # revisions.
710 # Returns the linkrev node (collected in lookupcl).
710 # Returns the linkrev node (collected in lookupcl).
711 if fastpathlinkrev:
711 if fastpathlinkrev:
712 lookupmflinknode = mfs.__getitem__
712 lookupmflinknode = mfs.__getitem__
713 else:
713 else:
714 def lookupmflinknode(x):
714 def lookupmflinknode(x):
715 """Callback for looking up the linknode for manifests.
715 """Callback for looking up the linknode for manifests.
716
716
717 Returns the linkrev node for the specified manifest.
717 Returns the linkrev node for the specified manifest.
718
718
719 SIDE EFFECT:
719 SIDE EFFECT:
720
720
721 1) fclnodes gets populated with the list of relevant
721 1) fclnodes gets populated with the list of relevant
722 file nodes if we're not using fastpathlinkrev
722 file nodes if we're not using fastpathlinkrev
723 2) When treemanifests are in use, collects treemanifest nodes
723 2) When treemanifests are in use, collects treemanifest nodes
724 to send
724 to send
725
725
726 Note that this means manifests must be completely sent to
726 Note that this means manifests must be completely sent to
727 the client before you can trust the list of files and
727 the client before you can trust the list of files and
728 treemanifests to send.
728 treemanifests to send.
729 """
729 """
730 clnode = mfs[x]
730 clnode = mfs[x]
731 # We no longer actually care about reading deltas of
731 # We no longer actually care about reading deltas of
732 # the manifest here, because we already know the list
732 # the manifest here, because we already know the list
733 # of changed files, so for treemanifests (which
733 # of changed files, so for treemanifests (which
734 # lazily-load anyway to *generate* a readdelta) we can
734 # lazily-load anyway to *generate* a readdelta) we can
735 # just load them with read() and then we'll actually
735 # just load them with read() and then we'll actually
736 # be able to correctly load node IDs from the
736 # be able to correctly load node IDs from the
737 # submanifest entries.
737 # submanifest entries.
738 if 'treemanifest' in repo.requirements:
738 if 'treemanifest' in repo.requirements:
739 mdata = ml.read(x)
739 mdata = ml.read(x)
740 else:
740 else:
741 mdata = ml.readfast(x)
741 mdata = ml.readfast(x)
742 for f in mfchangedfiles[x]:
742 for f in mfchangedfiles[x]:
743 try:
743 try:
744 n = mdata[f]
744 n = mdata[f]
745 except KeyError:
745 except KeyError:
746 continue
746 continue
747 # record the first changeset introducing this filelog
747 # record the first changeset introducing this filelog
748 # version
748 # version
749 fclnodes = fnodes.setdefault(f, {})
749 fclnodes = fnodes.setdefault(f, {})
750 fclnode = fclnodes.setdefault(n, clnode)
750 fclnode = fclnodes.setdefault(n, clnode)
751 if clrevorder[clnode] < clrevorder[fclnode]:
751 if clrevorder[clnode] < clrevorder[fclnode]:
752 fclnodes[n] = clnode
752 fclnodes[n] = clnode
753 # gather list of changed treemanifest nodes
753 # gather list of changed treemanifest nodes
754 if 'treemanifest' in repo.requirements:
754 if 'treemanifest' in repo.requirements:
755 submfs = {'/': mdata}
755 submfs = {'/': mdata}
756 for dn, bn in _moddirs(mfchangedfiles[x]):
756 for dn, bn in _moddirs(mfchangedfiles[x]):
757 submf = submfs[dn]
757 submf = submfs[dn]
758 submf = submf._dirs[bn]
758 submf = submf._dirs[bn]
759 submfs[submf.dir()] = submf
759 submfs[submf.dir()] = submf
760 tmfclnodes = tmfnodes.setdefault(submf.dir(), {})
760 tmfclnodes = tmfnodes.setdefault(submf.dir(), {})
761 tmfclnodes.setdefault(submf._node, clnode)
761 tmfclnodes.setdefault(submf._node, clnode)
762 if clrevorder[clnode] < clrevorder[fclnode]:
762 if clrevorder[clnode] < clrevorder[fclnode]:
763 tmfclnodes[n] = clnode
763 tmfclnodes[n] = clnode
764 return clnode
764 return clnode
765
765
766 mfnodes = self.prune(ml, mfs, commonrevs)
766 mfnodes = self.prune(ml, mfs, commonrevs)
767 for x in self._packmanifests(
767 for x in self._packmanifests(
768 mfnodes, tmfnodes, lookupmflinknode):
768 mfnodes, tmfnodes, lookupmflinknode):
769 yield x
769 yield x
770
770
771 mfs.clear()
771 mfs.clear()
772 clrevs = set(cl.rev(x) for x in clnodes)
772 clrevs = set(cl.rev(x) for x in clnodes)
773
773
774 if not fastpathlinkrev:
774 if not fastpathlinkrev:
775 def linknodes(unused, fname):
775 def linknodes(unused, fname):
776 return fnodes.get(fname, {})
776 return fnodes.get(fname, {})
777 else:
777 else:
778 cln = cl.node
778 cln = cl.node
779 def linknodes(filerevlog, fname):
779 def linknodes(filerevlog, fname):
780 llr = filerevlog.linkrev
780 llr = filerevlog.linkrev
781 fln = filerevlog.node
781 fln = filerevlog.node
782 revs = ((r, llr(r)) for r in filerevlog)
782 revs = ((r, llr(r)) for r in filerevlog)
783 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
783 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
784
784
785 changedfiles = set()
785 changedfiles = set()
786 for x in mfchangedfiles.itervalues():
786 for x in mfchangedfiles.itervalues():
787 changedfiles.update(x)
787 changedfiles.update(x)
788 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
788 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
789 source):
789 source):
790 yield chunk
790 yield chunk
791
791
792 yield self.close()
792 yield self.close()
793
793
794 if clnodes:
794 if clnodes:
795 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
795 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
796
796
797 # The 'source' parameter is useful for extensions
797 # The 'source' parameter is useful for extensions
798 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
798 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
799 repo = self._repo
799 repo = self._repo
800 progress = self._progress
800 progress = self._progress
801 msgbundling = _('bundling')
801 msgbundling = _('bundling')
802
802
803 total = len(changedfiles)
803 total = len(changedfiles)
804 # for progress output
804 # for progress output
805 msgfiles = _('files')
805 msgfiles = _('files')
806 for i, fname in enumerate(sorted(changedfiles)):
806 for i, fname in enumerate(sorted(changedfiles)):
807 filerevlog = repo.file(fname)
807 filerevlog = repo.file(fname)
808 if not filerevlog:
808 if not filerevlog:
809 raise error.Abort(_("empty or missing revlog for %s") % fname)
809 raise error.Abort(_("empty or missing revlog for %s") % fname)
810
810
811 linkrevnodes = linknodes(filerevlog, fname)
811 linkrevnodes = linknodes(filerevlog, fname)
812 # Lookup for filenodes, we collected the linkrev nodes above in the
812 # Lookup for filenodes, we collected the linkrev nodes above in the
813 # fastpath case and with lookupmf in the slowpath case.
813 # fastpath case and with lookupmf in the slowpath case.
814 def lookupfilelog(x):
814 def lookupfilelog(x):
815 return linkrevnodes[x]
815 return linkrevnodes[x]
816
816
817 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
817 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
818 if filenodes:
818 if filenodes:
819 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
819 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
820 total=total)
820 total=total)
821 h = self.fileheader(fname)
821 h = self.fileheader(fname)
822 size = len(h)
822 size = len(h)
823 yield h
823 yield h
824 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
824 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
825 size += len(chunk)
825 size += len(chunk)
826 yield chunk
826 yield chunk
827 self._verbosenote(_('%8.i %s\n') % (size, fname))
827 self._verbosenote(_('%8.i %s\n') % (size, fname))
828 progress(msgbundling, None)
828 progress(msgbundling, None)
829
829
830 def deltaparent(self, revlog, rev, p1, p2, prev):
830 def deltaparent(self, revlog, rev, p1, p2, prev):
831 return prev
831 return prev
832
832
833 def revchunk(self, revlog, rev, prev, linknode):
833 def revchunk(self, revlog, rev, prev, linknode):
834 node = revlog.node(rev)
834 node = revlog.node(rev)
835 p1, p2 = revlog.parentrevs(rev)
835 p1, p2 = revlog.parentrevs(rev)
836 base = self.deltaparent(revlog, rev, p1, p2, prev)
836 base = self.deltaparent(revlog, rev, p1, p2, prev)
837
837
838 prefix = ''
838 prefix = ''
839 if revlog.iscensored(base) or revlog.iscensored(rev):
839 if revlog.iscensored(base) or revlog.iscensored(rev):
840 try:
840 try:
841 delta = revlog.revision(node)
841 delta = revlog.revision(node)
842 except error.CensoredNodeError as e:
842 except error.CensoredNodeError as e:
843 delta = e.tombstone
843 delta = e.tombstone
844 if base == nullrev:
844 if base == nullrev:
845 prefix = mdiff.trivialdiffheader(len(delta))
845 prefix = mdiff.trivialdiffheader(len(delta))
846 else:
846 else:
847 baselen = revlog.rawsize(base)
847 baselen = revlog.rawsize(base)
848 prefix = mdiff.replacediffheader(baselen, len(delta))
848 prefix = mdiff.replacediffheader(baselen, len(delta))
849 elif base == nullrev:
849 elif base == nullrev:
850 delta = revlog.revision(node)
850 delta = revlog.revision(node)
851 prefix = mdiff.trivialdiffheader(len(delta))
851 prefix = mdiff.trivialdiffheader(len(delta))
852 else:
852 else:
853 delta = revlog.revdiff(base, rev)
853 delta = revlog.revdiff(base, rev)
854 p1n, p2n = revlog.parents(node)
854 p1n, p2n = revlog.parents(node)
855 basenode = revlog.node(base)
855 basenode = revlog.node(base)
856 flags = revlog.flags(rev)
856 flags = revlog.flags(rev)
857 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode, flags)
857 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode, flags)
858 meta += prefix
858 meta += prefix
859 l = len(meta) + len(delta)
859 l = len(meta) + len(delta)
860 yield chunkheader(l)
860 yield chunkheader(l)
861 yield meta
861 yield meta
862 yield delta
862 yield delta
863 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
863 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
864 # do nothing with basenode, it is implicitly the previous one in HG10
864 # do nothing with basenode, it is implicitly the previous one in HG10
865 # do nothing with flags, it is implicitly 0 for cg1 and cg2
865 # do nothing with flags, it is implicitly 0 for cg1 and cg2
866 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
866 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
867
867
868 class cg2packer(cg1packer):
868 class cg2packer(cg1packer):
869 version = '02'
869 version = '02'
870 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
870 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
871
871
872 def __init__(self, repo, bundlecaps=None):
872 def __init__(self, repo, bundlecaps=None):
873 super(cg2packer, self).__init__(repo, bundlecaps)
873 super(cg2packer, self).__init__(repo, bundlecaps)
874 if self._reorder is None:
874 if self._reorder is None:
875 # Since generaldelta is directly supported by cg2, reordering
875 # Since generaldelta is directly supported by cg2, reordering
876 # generally doesn't help, so we disable it by default (treating
876 # generally doesn't help, so we disable it by default (treating
877 # bundle.reorder=auto just like bundle.reorder=False).
877 # bundle.reorder=auto just like bundle.reorder=False).
878 self._reorder = False
878 self._reorder = False
879
879
880 def deltaparent(self, revlog, rev, p1, p2, prev):
880 def deltaparent(self, revlog, rev, p1, p2, prev):
881 dp = revlog.deltaparent(rev)
881 dp = revlog.deltaparent(rev)
882 # avoid storing full revisions; pick prev in those cases
882 # avoid storing full revisions; pick prev in those cases
883 # also pick prev when we can't be sure remote has dp
883 # also pick prev when we can't be sure remote has dp
884 if dp == nullrev or (dp != p1 and dp != p2 and dp != prev):
884 if dp == nullrev or (dp != p1 and dp != p2 and dp != prev):
885 return prev
885 return prev
886 return dp
886 return dp
887
887
888 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
888 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
889 # Do nothing with flags, it is implicitly 0 in cg1 and cg2
889 # Do nothing with flags, it is implicitly 0 in cg1 and cg2
890 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
890 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
891
891
892 class cg3packer(cg2packer):
892 class cg3packer(cg2packer):
893 version = '03'
893 version = '03'
894 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
894 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
895
895
896 def _packmanifests(self, mfnodes, tmfnodes, lookuplinknode):
896 def _packmanifests(self, mfnodes, tmfnodes, lookuplinknode):
897 # Note that debug prints are super confusing in this code, as
897 # Note that debug prints are super confusing in this code, as
898 # tmfnodes gets populated by the calls to lookuplinknode in
898 # tmfnodes gets populated by the calls to lookuplinknode in
899 # the superclass's manifest packer. In the future we should
899 # the superclass's manifest packer. In the future we should
900 # probably see if we can refactor this somehow to be less
900 # probably see if we can refactor this somehow to be less
901 # confusing.
901 # confusing.
902 for x in super(cg3packer, self)._packmanifests(
902 for x in super(cg3packer, self)._packmanifests(
903 mfnodes, {}, lookuplinknode):
903 mfnodes, {}, lookuplinknode):
904 yield x
904 yield x
905 dirlog = self._repo.manifest.dirlog
905 dirlog = self._repo.manifest.dirlog
906 for name, nodes in tmfnodes.iteritems():
906 for name, nodes in tmfnodes.iteritems():
907 # For now, directory headers are simply file headers with
907 # For now, directory headers are simply file headers with
908 # a trailing '/' on the path (already in the name).
908 # a trailing '/' on the path (already in the name).
909 yield self.fileheader(name)
909 yield self.fileheader(name)
910 for chunk in self.group(nodes, dirlog(name), nodes.get):
910 for chunk in self.group(nodes, dirlog(name), nodes.get):
911 yield chunk
911 yield chunk
912 yield self.close()
912
913
913 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
914 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
914 return struct.pack(
915 return struct.pack(
915 self.deltaheader, node, p1n, p2n, basenode, linknode, flags)
916 self.deltaheader, node, p1n, p2n, basenode, linknode, flags)
916
917
917 _packermap = {'01': (cg1packer, cg1unpacker),
918 _packermap = {'01': (cg1packer, cg1unpacker),
918 # cg2 adds support for exchanging generaldelta
919 # cg2 adds support for exchanging generaldelta
919 '02': (cg2packer, cg2unpacker),
920 '02': (cg2packer, cg2unpacker),
920 # cg3 adds support for exchanging treemanifests
921 # cg3 adds support for exchanging revlog flags and treemanifests
921 '03': (cg3packer, cg3unpacker),
922 '03': (cg3packer, cg3unpacker),
922 }
923 }
923
924
924 def supportedversions(repo):
925 def supportedversions(repo):
925 versions = _packermap.keys()
926 versions = _packermap.keys()
926 cg3 = ('treemanifest' in repo.requirements or
927 cg3 = ('treemanifest' in repo.requirements or
927 repo.ui.configbool('experimental', 'changegroup3') or
928 repo.ui.configbool('experimental', 'changegroup3') or
928 repo.ui.configbool('experimental', 'treemanifest'))
929 repo.ui.configbool('experimental', 'treemanifest'))
929 if not cg3:
930 if not cg3:
930 versions.remove('03')
931 versions.remove('03')
931 return versions
932 return versions
932
933
933 def getbundler(version, repo, bundlecaps=None):
934 def getbundler(version, repo, bundlecaps=None):
934 assert version in supportedversions(repo)
935 assert version in supportedversions(repo)
935 return _packermap[version][0](repo, bundlecaps)
936 return _packermap[version][0](repo, bundlecaps)
936
937
937 def getunbundler(version, fh, alg):
938 def getunbundler(version, fh, alg):
938 return _packermap[version][1](fh, alg)
939 return _packermap[version][1](fh, alg)
939
940
940 def _changegroupinfo(repo, nodes, source):
941 def _changegroupinfo(repo, nodes, source):
941 if repo.ui.verbose or source == 'bundle':
942 if repo.ui.verbose or source == 'bundle':
942 repo.ui.status(_("%d changesets found\n") % len(nodes))
943 repo.ui.status(_("%d changesets found\n") % len(nodes))
943 if repo.ui.debugflag:
944 if repo.ui.debugflag:
944 repo.ui.debug("list of changesets:\n")
945 repo.ui.debug("list of changesets:\n")
945 for node in nodes:
946 for node in nodes:
946 repo.ui.debug("%s\n" % hex(node))
947 repo.ui.debug("%s\n" % hex(node))
947
948
948 def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
949 def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
949 repo = repo.unfiltered()
950 repo = repo.unfiltered()
950 commonrevs = outgoing.common
951 commonrevs = outgoing.common
951 csets = outgoing.missing
952 csets = outgoing.missing
952 heads = outgoing.missingheads
953 heads = outgoing.missingheads
953 # We go through the fast path if we get told to, or if all (unfiltered
954 # We go through the fast path if we get told to, or if all (unfiltered
954 # heads have been requested (since we then know there all linkrevs will
955 # heads have been requested (since we then know there all linkrevs will
955 # be pulled by the client).
956 # be pulled by the client).
956 heads.sort()
957 heads.sort()
957 fastpathlinkrev = fastpath or (
958 fastpathlinkrev = fastpath or (
958 repo.filtername is None and heads == sorted(repo.heads()))
959 repo.filtername is None and heads == sorted(repo.heads()))
959
960
960 repo.hook('preoutgoing', throw=True, source=source)
961 repo.hook('preoutgoing', throw=True, source=source)
961 _changegroupinfo(repo, csets, source)
962 _changegroupinfo(repo, csets, source)
962 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
963 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
963
964
964 def getsubset(repo, outgoing, bundler, source, fastpath=False):
965 def getsubset(repo, outgoing, bundler, source, fastpath=False):
965 gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
966 gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
966 return getunbundler(bundler.version, util.chunkbuffer(gengroup), None)
967 return getunbundler(bundler.version, util.chunkbuffer(gengroup), None)
967
968
968 def changegroupsubset(repo, roots, heads, source, version='01'):
969 def changegroupsubset(repo, roots, heads, source, version='01'):
969 """Compute a changegroup consisting of all the nodes that are
970 """Compute a changegroup consisting of all the nodes that are
970 descendants of any of the roots and ancestors of any of the heads.
971 descendants of any of the roots and ancestors of any of the heads.
971 Return a chunkbuffer object whose read() method will return
972 Return a chunkbuffer object whose read() method will return
972 successive changegroup chunks.
973 successive changegroup chunks.
973
974
974 It is fairly complex as determining which filenodes and which
975 It is fairly complex as determining which filenodes and which
975 manifest nodes need to be included for the changeset to be complete
976 manifest nodes need to be included for the changeset to be complete
976 is non-trivial.
977 is non-trivial.
977
978
978 Another wrinkle is doing the reverse, figuring out which changeset in
979 Another wrinkle is doing the reverse, figuring out which changeset in
979 the changegroup a particular filenode or manifestnode belongs to.
980 the changegroup a particular filenode or manifestnode belongs to.
980 """
981 """
981 cl = repo.changelog
982 cl = repo.changelog
982 if not roots:
983 if not roots:
983 roots = [nullid]
984 roots = [nullid]
984 discbases = []
985 discbases = []
985 for n in roots:
986 for n in roots:
986 discbases.extend([p for p in cl.parents(n) if p != nullid])
987 discbases.extend([p for p in cl.parents(n) if p != nullid])
987 # TODO: remove call to nodesbetween.
988 # TODO: remove call to nodesbetween.
988 csets, roots, heads = cl.nodesbetween(roots, heads)
989 csets, roots, heads = cl.nodesbetween(roots, heads)
989 included = set(csets)
990 included = set(csets)
990 discbases = [n for n in discbases if n not in included]
991 discbases = [n for n in discbases if n not in included]
991 outgoing = discovery.outgoing(cl, discbases, heads)
992 outgoing = discovery.outgoing(cl, discbases, heads)
992 bundler = getbundler(version, repo)
993 bundler = getbundler(version, repo)
993 return getsubset(repo, outgoing, bundler, source)
994 return getsubset(repo, outgoing, bundler, source)
994
995
995 def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
996 def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
996 version='01'):
997 version='01'):
997 """Like getbundle, but taking a discovery.outgoing as an argument.
998 """Like getbundle, but taking a discovery.outgoing as an argument.
998
999
999 This is only implemented for local repos and reuses potentially
1000 This is only implemented for local repos and reuses potentially
1000 precomputed sets in outgoing. Returns a raw changegroup generator."""
1001 precomputed sets in outgoing. Returns a raw changegroup generator."""
1001 if not outgoing.missing:
1002 if not outgoing.missing:
1002 return None
1003 return None
1003 bundler = getbundler(version, repo, bundlecaps)
1004 bundler = getbundler(version, repo, bundlecaps)
1004 return getsubsetraw(repo, outgoing, bundler, source)
1005 return getsubsetraw(repo, outgoing, bundler, source)
1005
1006
1006 def getlocalchangegroup(repo, source, outgoing, bundlecaps=None,
1007 def getlocalchangegroup(repo, source, outgoing, bundlecaps=None,
1007 version='01'):
1008 version='01'):
1008 """Like getbundle, but taking a discovery.outgoing as an argument.
1009 """Like getbundle, but taking a discovery.outgoing as an argument.
1009
1010
1010 This is only implemented for local repos and reuses potentially
1011 This is only implemented for local repos and reuses potentially
1011 precomputed sets in outgoing."""
1012 precomputed sets in outgoing."""
1012 if not outgoing.missing:
1013 if not outgoing.missing:
1013 return None
1014 return None
1014 bundler = getbundler(version, repo, bundlecaps)
1015 bundler = getbundler(version, repo, bundlecaps)
1015 return getsubset(repo, outgoing, bundler, source)
1016 return getsubset(repo, outgoing, bundler, source)
1016
1017
1017 def computeoutgoing(repo, heads, common):
1018 def computeoutgoing(repo, heads, common):
1018 """Computes which revs are outgoing given a set of common
1019 """Computes which revs are outgoing given a set of common
1019 and a set of heads.
1020 and a set of heads.
1020
1021
1021 This is a separate function so extensions can have access to
1022 This is a separate function so extensions can have access to
1022 the logic.
1023 the logic.
1023
1024
1024 Returns a discovery.outgoing object.
1025 Returns a discovery.outgoing object.
1025 """
1026 """
1026 cl = repo.changelog
1027 cl = repo.changelog
1027 if common:
1028 if common:
1028 hasnode = cl.hasnode
1029 hasnode = cl.hasnode
1029 common = [n for n in common if hasnode(n)]
1030 common = [n for n in common if hasnode(n)]
1030 else:
1031 else:
1031 common = [nullid]
1032 common = [nullid]
1032 if not heads:
1033 if not heads:
1033 heads = cl.heads()
1034 heads = cl.heads()
1034 return discovery.outgoing(cl, common, heads)
1035 return discovery.outgoing(cl, common, heads)
1035
1036
1036 def getchangegroup(repo, source, heads=None, common=None, bundlecaps=None,
1037 def getchangegroup(repo, source, heads=None, common=None, bundlecaps=None,
1037 version='01'):
1038 version='01'):
1038 """Like changegroupsubset, but returns the set difference between the
1039 """Like changegroupsubset, but returns the set difference between the
1039 ancestors of heads and the ancestors common.
1040 ancestors of heads and the ancestors common.
1040
1041
1041 If heads is None, use the local heads. If common is None, use [nullid].
1042 If heads is None, use the local heads. If common is None, use [nullid].
1042
1043
1043 The nodes in common might not all be known locally due to the way the
1044 The nodes in common might not all be known locally due to the way the
1044 current discovery protocol works.
1045 current discovery protocol works.
1045 """
1046 """
1046 outgoing = computeoutgoing(repo, heads, common)
1047 outgoing = computeoutgoing(repo, heads, common)
1047 return getlocalchangegroup(repo, source, outgoing, bundlecaps=bundlecaps,
1048 return getlocalchangegroup(repo, source, outgoing, bundlecaps=bundlecaps,
1048 version=version)
1049 version=version)
1049
1050
1050 def changegroup(repo, basenodes, source):
1051 def changegroup(repo, basenodes, source):
1051 # to avoid a race we use changegroupsubset() (issue1320)
1052 # to avoid a race we use changegroupsubset() (issue1320)
1052 return changegroupsubset(repo, basenodes, repo.heads(), source)
1053 return changegroupsubset(repo, basenodes, repo.heads(), source)
1053
1054
1054 def _addchangegroupfiles(repo, source, revmap, trp, pr, needfiles):
1055 def _addchangegroupfiles(repo, source, revmap, trp, pr, needfiles):
1055 revisions = 0
1056 revisions = 0
1056 files = 0
1057 files = 0
1058 submfsdone = False
1057 while True:
1059 while True:
1058 chunkdata = source.filelogheader()
1060 chunkdata = source.filelogheader()
1059 if not chunkdata:
1061 if not chunkdata:
1062 if source.version == "03" and not submfsdone:
1063 submfsdone = True
1064 continue
1060 break
1065 break
1061 f = chunkdata["filename"]
1066 f = chunkdata["filename"]
1062 repo.ui.debug("adding %s revisions\n" % f)
1067 repo.ui.debug("adding %s revisions\n" % f)
1063 pr()
1068 pr()
1064 directory = (f[-1] == '/')
1069 directory = (f[-1] == '/')
1065 if directory:
1070 if directory:
1066 # a directory using treemanifests
1071 # a directory using treemanifests
1067 fl = repo.manifest.dirlog(f)
1072 fl = repo.manifest.dirlog(f)
1068 else:
1073 else:
1069 fl = repo.file(f)
1074 fl = repo.file(f)
1070 o = len(fl)
1075 o = len(fl)
1071 try:
1076 try:
1072 if not fl.addgroup(source, revmap, trp):
1077 if not fl.addgroup(source, revmap, trp):
1073 raise error.Abort(_("received file revlog group is empty"))
1078 raise error.Abort(_("received file revlog group is empty"))
1074 except error.CensoredBaseError as e:
1079 except error.CensoredBaseError as e:
1075 raise error.Abort(_("received delta base is censored: %s") % e)
1080 raise error.Abort(_("received delta base is censored: %s") % e)
1076 if not directory:
1081 if not directory:
1077 revisions += len(fl) - o
1082 revisions += len(fl) - o
1078 files += 1
1083 files += 1
1079 if f in needfiles:
1084 if f in needfiles:
1080 needs = needfiles[f]
1085 needs = needfiles[f]
1081 for new in xrange(o, len(fl)):
1086 for new in xrange(o, len(fl)):
1082 n = fl.node(new)
1087 n = fl.node(new)
1083 if n in needs:
1088 if n in needs:
1084 needs.remove(n)
1089 needs.remove(n)
1085 else:
1090 else:
1086 raise error.Abort(
1091 raise error.Abort(
1087 _("received spurious file revlog entry"))
1092 _("received spurious file revlog entry"))
1088 if not needs:
1093 if not needs:
1089 del needfiles[f]
1094 del needfiles[f]
1090 repo.ui.progress(_('files'), None)
1095 repo.ui.progress(_('files'), None)
1091
1096
1092 for f, needs in needfiles.iteritems():
1097 for f, needs in needfiles.iteritems():
1093 fl = repo.file(f)
1098 fl = repo.file(f)
1094 for n in needs:
1099 for n in needs:
1095 try:
1100 try:
1096 fl.rev(n)
1101 fl.rev(n)
1097 except error.LookupError:
1102 except error.LookupError:
1098 raise error.Abort(
1103 raise error.Abort(
1099 _('missing file data for %s:%s - run hg verify') %
1104 _('missing file data for %s:%s - run hg verify') %
1100 (f, hex(n)))
1105 (f, hex(n)))
1101
1106
1102 return revisions, files
1107 return revisions, files
General Comments 0
You need to be logged in to leave comments. Login now