##// END OF EJS Templates
changegroup: move manifest unpacking into its own method...
Augie Fackler -
r26712:04176eaf default
parent child Browse files
Show More
@@ -1,946 +1,950 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
35
36 def readexactly(stream, n):
36 def readexactly(stream, n):
37 '''read n bytes from stream.read and abort if less was available'''
37 '''read n bytes from stream.read and abort if less was available'''
38 s = stream.read(n)
38 s = stream.read(n)
39 if len(s) < n:
39 if len(s) < n:
40 raise error.Abort(_("stream ended unexpectedly"
40 raise error.Abort(_("stream ended unexpectedly"
41 " (got %d bytes, expected %d)")
41 " (got %d bytes, expected %d)")
42 % (len(s), n))
42 % (len(s), n))
43 return s
43 return s
44
44
45 def getchunk(stream):
45 def getchunk(stream):
46 """return the next chunk from stream as a string"""
46 """return the next chunk from stream as a string"""
47 d = readexactly(stream, 4)
47 d = readexactly(stream, 4)
48 l = struct.unpack(">l", d)[0]
48 l = struct.unpack(">l", d)[0]
49 if l <= 4:
49 if l <= 4:
50 if l:
50 if l:
51 raise error.Abort(_("invalid chunk length %d") % l)
51 raise error.Abort(_("invalid chunk length %d") % l)
52 return ""
52 return ""
53 return readexactly(stream, l - 4)
53 return readexactly(stream, l - 4)
54
54
55 def chunkheader(length):
55 def chunkheader(length):
56 """return a changegroup chunk header (string)"""
56 """return a changegroup chunk header (string)"""
57 return struct.pack(">l", length + 4)
57 return struct.pack(">l", length + 4)
58
58
59 def closechunk():
59 def closechunk():
60 """return a changegroup chunk header (string) for a zero-length chunk"""
60 """return a changegroup chunk header (string) for a zero-length chunk"""
61 return struct.pack(">l", 0)
61 return struct.pack(">l", 0)
62
62
63 def combineresults(results):
63 def combineresults(results):
64 """logic to combine 0 or more addchangegroup results into one"""
64 """logic to combine 0 or more addchangegroup results into one"""
65 changedheads = 0
65 changedheads = 0
66 result = 1
66 result = 1
67 for ret in results:
67 for ret in results:
68 # If any changegroup result is 0, return 0
68 # If any changegroup result is 0, return 0
69 if ret == 0:
69 if ret == 0:
70 result = 0
70 result = 0
71 break
71 break
72 if ret < -1:
72 if ret < -1:
73 changedheads += ret + 1
73 changedheads += ret + 1
74 elif ret > 1:
74 elif ret > 1:
75 changedheads += ret - 1
75 changedheads += ret - 1
76 if changedheads > 0:
76 if changedheads > 0:
77 result = 1 + changedheads
77 result = 1 + changedheads
78 elif changedheads < 0:
78 elif changedheads < 0:
79 result = -1 + changedheads
79 result = -1 + changedheads
80 return result
80 return result
81
81
82 bundletypes = {
82 bundletypes = {
83 "": ("", None), # only when using unbundle on ssh and old http servers
83 "": ("", None), # only when using unbundle on ssh and old http servers
84 # since the unification ssh accepts a header but there
84 # since the unification ssh accepts a header but there
85 # is no capability signaling it.
85 # is no capability signaling it.
86 "HG20": (), # special-cased below
86 "HG20": (), # special-cased below
87 "HG10UN": ("HG10UN", None),
87 "HG10UN": ("HG10UN", None),
88 "HG10BZ": ("HG10", 'BZ'),
88 "HG10BZ": ("HG10", 'BZ'),
89 "HG10GZ": ("HG10GZ", 'GZ'),
89 "HG10GZ": ("HG10GZ", 'GZ'),
90 }
90 }
91
91
92 # hgweb uses this list to communicate its preferred type
92 # hgweb uses this list to communicate its preferred type
93 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
93 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
94
94
95 def writechunks(ui, chunks, filename, vfs=None):
95 def writechunks(ui, chunks, filename, vfs=None):
96 """Write chunks to a file and return its filename.
96 """Write chunks to a file and return its filename.
97
97
98 The stream is assumed to be a bundle file.
98 The stream is assumed to be a bundle file.
99 Existing files will not be overwritten.
99 Existing files will not be overwritten.
100 If no filename is specified, a temporary file is created.
100 If no filename is specified, a temporary file is created.
101 """
101 """
102 fh = None
102 fh = None
103 cleanup = None
103 cleanup = None
104 try:
104 try:
105 if filename:
105 if filename:
106 if vfs:
106 if vfs:
107 fh = vfs.open(filename, "wb")
107 fh = vfs.open(filename, "wb")
108 else:
108 else:
109 fh = open(filename, "wb")
109 fh = open(filename, "wb")
110 else:
110 else:
111 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
111 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
112 fh = os.fdopen(fd, "wb")
112 fh = os.fdopen(fd, "wb")
113 cleanup = filename
113 cleanup = filename
114 for c in chunks:
114 for c in chunks:
115 fh.write(c)
115 fh.write(c)
116 cleanup = None
116 cleanup = None
117 return filename
117 return filename
118 finally:
118 finally:
119 if fh is not None:
119 if fh is not None:
120 fh.close()
120 fh.close()
121 if cleanup is not None:
121 if cleanup is not None:
122 if filename and vfs:
122 if filename and vfs:
123 vfs.unlink(cleanup)
123 vfs.unlink(cleanup)
124 else:
124 else:
125 os.unlink(cleanup)
125 os.unlink(cleanup)
126
126
127 def writebundle(ui, cg, filename, bundletype, vfs=None, compression=None):
127 def writebundle(ui, cg, filename, bundletype, vfs=None, compression=None):
128 """Write a bundle file and return its filename.
128 """Write a bundle file and return its filename.
129
129
130 Existing files will not be overwritten.
130 Existing files will not be overwritten.
131 If no filename is specified, a temporary file is created.
131 If no filename is specified, a temporary file is created.
132 bz2 compression can be turned off.
132 bz2 compression can be turned off.
133 The bundle file will be deleted in case of errors.
133 The bundle file will be deleted in case of errors.
134 """
134 """
135
135
136 if bundletype == "HG20":
136 if bundletype == "HG20":
137 from . import bundle2
137 from . import bundle2
138 bundle = bundle2.bundle20(ui)
138 bundle = bundle2.bundle20(ui)
139 bundle.setcompression(compression)
139 bundle.setcompression(compression)
140 part = bundle.newpart('changegroup', data=cg.getchunks())
140 part = bundle.newpart('changegroup', data=cg.getchunks())
141 part.addparam('version', cg.version)
141 part.addparam('version', cg.version)
142 chunkiter = bundle.getchunks()
142 chunkiter = bundle.getchunks()
143 else:
143 else:
144 # compression argument is only for the bundle2 case
144 # compression argument is only for the bundle2 case
145 assert compression is None
145 assert compression is None
146 if cg.version != '01':
146 if cg.version != '01':
147 raise error.Abort(_('old bundle types only supports v1 '
147 raise error.Abort(_('old bundle types only supports v1 '
148 'changegroups'))
148 'changegroups'))
149 header, comp = bundletypes[bundletype]
149 header, comp = bundletypes[bundletype]
150 if comp not in util.compressors:
150 if comp not in util.compressors:
151 raise error.Abort(_('unknown stream compression type: %s')
151 raise error.Abort(_('unknown stream compression type: %s')
152 % comp)
152 % comp)
153 z = util.compressors[comp]()
153 z = util.compressors[comp]()
154 subchunkiter = cg.getchunks()
154 subchunkiter = cg.getchunks()
155 def chunkiter():
155 def chunkiter():
156 yield header
156 yield header
157 for chunk in subchunkiter:
157 for chunk in subchunkiter:
158 yield z.compress(chunk)
158 yield z.compress(chunk)
159 yield z.flush()
159 yield z.flush()
160 chunkiter = chunkiter()
160 chunkiter = chunkiter()
161
161
162 # parse the changegroup data, otherwise we will block
162 # parse the changegroup data, otherwise we will block
163 # in case of sshrepo because we don't know the end of the stream
163 # in case of sshrepo because we don't know the end of the stream
164
164
165 # an empty chunkgroup is the end of the changegroup
165 # an empty chunkgroup is the end of the changegroup
166 # a changegroup has at least 2 chunkgroups (changelog and manifest).
166 # a changegroup has at least 2 chunkgroups (changelog and manifest).
167 # after that, an empty chunkgroup is the end of the changegroup
167 # after that, an empty chunkgroup is the end of the changegroup
168 return writechunks(ui, chunkiter, filename, vfs=vfs)
168 return writechunks(ui, chunkiter, filename, vfs=vfs)
169
169
170 class cg1unpacker(object):
170 class cg1unpacker(object):
171 """Unpacker for cg1 changegroup streams.
171 """Unpacker for cg1 changegroup streams.
172
172
173 A changegroup unpacker handles the framing of the revision data in
173 A changegroup unpacker handles the framing of the revision data in
174 the wire format. Most consumers will want to use the apply()
174 the wire format. Most consumers will want to use the apply()
175 method to add the changes from the changegroup to a repository.
175 method to add the changes from the changegroup to a repository.
176
176
177 If you're forwarding a changegroup unmodified to another consumer,
177 If you're forwarding a changegroup unmodified to another consumer,
178 use getchunks(), which returns an iterator of changegroup
178 use getchunks(), which returns an iterator of changegroup
179 chunks. This is mostly useful for cases where you need to know the
179 chunks. This is mostly useful for cases where you need to know the
180 data stream has ended by observing the end of the changegroup.
180 data stream has ended by observing the end of the changegroup.
181
181
182 deltachunk() is useful only if you're applying delta data. Most
182 deltachunk() is useful only if you're applying delta data. Most
183 consumers should prefer apply() instead.
183 consumers should prefer apply() instead.
184
184
185 A few other public methods exist. Those are used only for
185 A few other public methods exist. Those are used only for
186 bundlerepo and some debug commands - their use is discouraged.
186 bundlerepo and some debug commands - their use is discouraged.
187 """
187 """
188 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
188 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
189 deltaheadersize = struct.calcsize(deltaheader)
189 deltaheadersize = struct.calcsize(deltaheader)
190 version = '01'
190 version = '01'
191 def __init__(self, fh, alg):
191 def __init__(self, fh, alg):
192 if alg == 'UN':
192 if alg == 'UN':
193 alg = None # get more modern without breaking too much
193 alg = None # get more modern without breaking too much
194 if not alg in util.decompressors:
194 if not alg in util.decompressors:
195 raise error.Abort(_('unknown stream compression type: %s')
195 raise error.Abort(_('unknown stream compression type: %s')
196 % alg)
196 % alg)
197 if alg == 'BZ':
197 if alg == 'BZ':
198 alg = '_truncatedBZ'
198 alg = '_truncatedBZ'
199 self._stream = util.decompressors[alg](fh)
199 self._stream = util.decompressors[alg](fh)
200 self._type = alg
200 self._type = alg
201 self.callback = None
201 self.callback = None
202
202
203 # These methods (compressed, read, seek, tell) all appear to only
203 # These methods (compressed, read, seek, tell) all appear to only
204 # be used by bundlerepo, but it's a little hard to tell.
204 # be used by bundlerepo, but it's a little hard to tell.
205 def compressed(self):
205 def compressed(self):
206 return self._type is not None
206 return self._type is not None
207 def read(self, l):
207 def read(self, l):
208 return self._stream.read(l)
208 return self._stream.read(l)
209 def seek(self, pos):
209 def seek(self, pos):
210 return self._stream.seek(pos)
210 return self._stream.seek(pos)
211 def tell(self):
211 def tell(self):
212 return self._stream.tell()
212 return self._stream.tell()
213 def close(self):
213 def close(self):
214 return self._stream.close()
214 return self._stream.close()
215
215
216 def _chunklength(self):
216 def _chunklength(self):
217 d = readexactly(self._stream, 4)
217 d = readexactly(self._stream, 4)
218 l = struct.unpack(">l", d)[0]
218 l = struct.unpack(">l", d)[0]
219 if l <= 4:
219 if l <= 4:
220 if l:
220 if l:
221 raise error.Abort(_("invalid chunk length %d") % l)
221 raise error.Abort(_("invalid chunk length %d") % l)
222 return 0
222 return 0
223 if self.callback:
223 if self.callback:
224 self.callback()
224 self.callback()
225 return l - 4
225 return l - 4
226
226
227 def changelogheader(self):
227 def changelogheader(self):
228 """v10 does not have a changelog header chunk"""
228 """v10 does not have a changelog header chunk"""
229 return {}
229 return {}
230
230
231 def manifestheader(self):
231 def manifestheader(self):
232 """v10 does not have a manifest header chunk"""
232 """v10 does not have a manifest header chunk"""
233 return {}
233 return {}
234
234
235 def filelogheader(self):
235 def filelogheader(self):
236 """return the header of the filelogs chunk, v10 only has the filename"""
236 """return the header of the filelogs chunk, v10 only has the filename"""
237 l = self._chunklength()
237 l = self._chunklength()
238 if not l:
238 if not l:
239 return {}
239 return {}
240 fname = readexactly(self._stream, l)
240 fname = readexactly(self._stream, l)
241 return {'filename': fname}
241 return {'filename': fname}
242
242
243 def _deltaheader(self, headertuple, prevnode):
243 def _deltaheader(self, headertuple, prevnode):
244 node, p1, p2, cs = headertuple
244 node, p1, p2, cs = headertuple
245 if prevnode is None:
245 if prevnode is None:
246 deltabase = p1
246 deltabase = p1
247 else:
247 else:
248 deltabase = prevnode
248 deltabase = prevnode
249 return node, p1, p2, deltabase, cs
249 return node, p1, p2, deltabase, cs
250
250
251 def deltachunk(self, prevnode):
251 def deltachunk(self, prevnode):
252 l = self._chunklength()
252 l = self._chunklength()
253 if not l:
253 if not l:
254 return {}
254 return {}
255 headerdata = readexactly(self._stream, self.deltaheadersize)
255 headerdata = readexactly(self._stream, self.deltaheadersize)
256 header = struct.unpack(self.deltaheader, headerdata)
256 header = struct.unpack(self.deltaheader, headerdata)
257 delta = readexactly(self._stream, l - self.deltaheadersize)
257 delta = readexactly(self._stream, l - self.deltaheadersize)
258 node, p1, p2, deltabase, cs = self._deltaheader(header, prevnode)
258 node, p1, p2, deltabase, cs = self._deltaheader(header, prevnode)
259 return {'node': node, 'p1': p1, 'p2': p2, 'cs': cs,
259 return {'node': node, 'p1': p1, 'p2': p2, 'cs': cs,
260 'deltabase': deltabase, 'delta': delta}
260 'deltabase': deltabase, 'delta': delta}
261
261
262 def getchunks(self):
262 def getchunks(self):
263 """returns all the chunks contains in the bundle
263 """returns all the chunks contains in the bundle
264
264
265 Used when you need to forward the binary stream to a file or another
265 Used when you need to forward the binary stream to a file or another
266 network API. To do so, it parse the changegroup data, otherwise it will
266 network API. To do so, it parse the changegroup data, otherwise it will
267 block in case of sshrepo because it don't know the end of the stream.
267 block in case of sshrepo because it don't know the end of the stream.
268 """
268 """
269 # an empty chunkgroup is the end of the changegroup
269 # an empty chunkgroup is the end of the changegroup
270 # a changegroup has at least 2 chunkgroups (changelog and manifest).
270 # a changegroup has at least 2 chunkgroups (changelog and manifest).
271 # after that, an empty chunkgroup is the end of the changegroup
271 # after that, an empty chunkgroup is the end of the changegroup
272 empty = False
272 empty = False
273 count = 0
273 count = 0
274 while not empty or count <= 2:
274 while not empty or count <= 2:
275 empty = True
275 empty = True
276 count += 1
276 count += 1
277 while True:
277 while True:
278 chunk = getchunk(self)
278 chunk = getchunk(self)
279 if not chunk:
279 if not chunk:
280 break
280 break
281 empty = False
281 empty = False
282 yield chunkheader(len(chunk))
282 yield chunkheader(len(chunk))
283 pos = 0
283 pos = 0
284 while pos < len(chunk):
284 while pos < len(chunk):
285 next = pos + 2**20
285 next = pos + 2**20
286 yield chunk[pos:next]
286 yield chunk[pos:next]
287 pos = next
287 pos = next
288 yield closechunk()
288 yield closechunk()
289
289
290 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
291 # We know that we'll never have more manifests than we had
292 # changesets.
293 self.callback = prog(_('manifests'), numchanges)
294 # no need to check for empty manifest group here:
295 # if the result of the merge of 1 and 2 is the same in 3 and 4,
296 # no new manifest will be created and the manifest group will
297 # be empty during the pull
298 self.manifestheader()
299 repo.manifest.addgroup(self, revmap, trp)
300 repo.ui.progress(_('manifests'), None)
301
290 def apply(self, repo, srctype, url, emptyok=False,
302 def apply(self, repo, srctype, url, emptyok=False,
291 targetphase=phases.draft, expectedtotal=None):
303 targetphase=phases.draft, expectedtotal=None):
292 """Add the changegroup returned by source.read() to this repo.
304 """Add the changegroup returned by source.read() to this repo.
293 srctype is a string like 'push', 'pull', or 'unbundle'. url is
305 srctype is a string like 'push', 'pull', or 'unbundle'. url is
294 the URL of the repo where this changegroup is coming from.
306 the URL of the repo where this changegroup is coming from.
295
307
296 Return an integer summarizing the change to this repo:
308 Return an integer summarizing the change to this repo:
297 - nothing changed or no source: 0
309 - nothing changed or no source: 0
298 - more heads than before: 1+added heads (2..n)
310 - more heads than before: 1+added heads (2..n)
299 - fewer heads than before: -1-removed heads (-2..-n)
311 - fewer heads than before: -1-removed heads (-2..-n)
300 - number of heads stays the same: 1
312 - number of heads stays the same: 1
301 """
313 """
302 repo = repo.unfiltered()
314 repo = repo.unfiltered()
303 def csmap(x):
315 def csmap(x):
304 repo.ui.debug("add changeset %s\n" % short(x))
316 repo.ui.debug("add changeset %s\n" % short(x))
305 return len(cl)
317 return len(cl)
306
318
307 def revmap(x):
319 def revmap(x):
308 return cl.rev(x)
320 return cl.rev(x)
309
321
310 changesets = files = revisions = 0
322 changesets = files = revisions = 0
311
323
312 tr = repo.transaction("\n".join([srctype, util.hidepassword(url)]))
324 tr = repo.transaction("\n".join([srctype, util.hidepassword(url)]))
313 # The transaction could have been created before and already
325 # The transaction could have been created before and already
314 # carries source information. In this case we use the top
326 # carries source information. In this case we use the top
315 # level data. We overwrite the argument because we need to use
327 # level data. We overwrite the argument because we need to use
316 # the top level value (if they exist) in this function.
328 # the top level value (if they exist) in this function.
317 srctype = tr.hookargs.setdefault('source', srctype)
329 srctype = tr.hookargs.setdefault('source', srctype)
318 url = tr.hookargs.setdefault('url', url)
330 url = tr.hookargs.setdefault('url', url)
319
331
320 # write changelog data to temp files so concurrent readers will not see
332 # write changelog data to temp files so concurrent readers will not see
321 # inconsistent view
333 # inconsistent view
322 cl = repo.changelog
334 cl = repo.changelog
323 cl.delayupdate(tr)
335 cl.delayupdate(tr)
324 oldheads = cl.heads()
336 oldheads = cl.heads()
325 try:
337 try:
326 repo.hook('prechangegroup', throw=True, **tr.hookargs)
338 repo.hook('prechangegroup', throw=True, **tr.hookargs)
327
339
328 trp = weakref.proxy(tr)
340 trp = weakref.proxy(tr)
329 # pull off the changeset group
341 # pull off the changeset group
330 repo.ui.status(_("adding changesets\n"))
342 repo.ui.status(_("adding changesets\n"))
331 clstart = len(cl)
343 clstart = len(cl)
332 class prog(object):
344 class prog(object):
333 def __init__(self, step, total):
345 def __init__(self, step, total):
334 self._step = step
346 self._step = step
335 self._total = total
347 self._total = total
336 self._count = 1
348 self._count = 1
337 def __call__(self):
349 def __call__(self):
338 repo.ui.progress(self._step, self._count, unit=_('chunks'),
350 repo.ui.progress(self._step, self._count, unit=_('chunks'),
339 total=self._total)
351 total=self._total)
340 self._count += 1
352 self._count += 1
341 self.callback = prog(_('changesets'), expectedtotal)
353 self.callback = prog(_('changesets'), expectedtotal)
342
354
343 efiles = set()
355 efiles = set()
344 def onchangelog(cl, node):
356 def onchangelog(cl, node):
345 efiles.update(cl.read(node)[3])
357 efiles.update(cl.read(node)[3])
346
358
347 self.changelogheader()
359 self.changelogheader()
348 srccontent = cl.addgroup(self, csmap, trp,
360 srccontent = cl.addgroup(self, csmap, trp,
349 addrevisioncb=onchangelog)
361 addrevisioncb=onchangelog)
350 efiles = len(efiles)
362 efiles = len(efiles)
351
363
352 if not (srccontent or emptyok):
364 if not (srccontent or emptyok):
353 raise error.Abort(_("received changelog group is empty"))
365 raise error.Abort(_("received changelog group is empty"))
354 clend = len(cl)
366 clend = len(cl)
355 changesets = clend - clstart
367 changesets = clend - clstart
356 repo.ui.progress(_('changesets'), None)
368 repo.ui.progress(_('changesets'), None)
357
369
358 # pull off the manifest group
370 # pull off the manifest group
359 repo.ui.status(_("adding manifests\n"))
371 repo.ui.status(_("adding manifests\n"))
360 # manifests <= changesets
372 self._unpackmanifests(repo, revmap, trp, prog, changesets)
361 self.callback = prog(_('manifests'), changesets)
362 # no need to check for empty manifest group here:
363 # if the result of the merge of 1 and 2 is the same in 3 and 4,
364 # no new manifest will be created and the manifest group will
365 # be empty during the pull
366 self.manifestheader()
367 repo.manifest.addgroup(self, revmap, trp)
368 repo.ui.progress(_('manifests'), None)
369
373
370 needfiles = {}
374 needfiles = {}
371 if repo.ui.configbool('server', 'validate', default=False):
375 if repo.ui.configbool('server', 'validate', default=False):
372 # validate incoming csets have their manifests
376 # validate incoming csets have their manifests
373 for cset in xrange(clstart, clend):
377 for cset in xrange(clstart, clend):
374 mfnode = repo.changelog.read(repo.changelog.node(cset))[0]
378 mfnode = repo.changelog.read(repo.changelog.node(cset))[0]
375 mfest = repo.manifest.readdelta(mfnode)
379 mfest = repo.manifest.readdelta(mfnode)
376 # store file nodes we must see
380 # store file nodes we must see
377 for f, n in mfest.iteritems():
381 for f, n in mfest.iteritems():
378 needfiles.setdefault(f, set()).add(n)
382 needfiles.setdefault(f, set()).add(n)
379
383
380 # process the files
384 # process the files
381 repo.ui.status(_("adding file changes\n"))
385 repo.ui.status(_("adding file changes\n"))
382 self.callback = None
386 self.callback = None
383 pr = prog(_('files'), efiles)
387 pr = prog(_('files'), efiles)
384 newrevs, newfiles = _addchangegroupfiles(
388 newrevs, newfiles = _addchangegroupfiles(
385 repo, self, revmap, trp, pr, needfiles)
389 repo, self, revmap, trp, pr, needfiles)
386 revisions += newrevs
390 revisions += newrevs
387 files += newfiles
391 files += newfiles
388
392
389 dh = 0
393 dh = 0
390 if oldheads:
394 if oldheads:
391 heads = cl.heads()
395 heads = cl.heads()
392 dh = len(heads) - len(oldheads)
396 dh = len(heads) - len(oldheads)
393 for h in heads:
397 for h in heads:
394 if h not in oldheads and repo[h].closesbranch():
398 if h not in oldheads and repo[h].closesbranch():
395 dh -= 1
399 dh -= 1
396 htext = ""
400 htext = ""
397 if dh:
401 if dh:
398 htext = _(" (%+d heads)") % dh
402 htext = _(" (%+d heads)") % dh
399
403
400 repo.ui.status(_("added %d changesets"
404 repo.ui.status(_("added %d changesets"
401 " with %d changes to %d files%s\n")
405 " with %d changes to %d files%s\n")
402 % (changesets, revisions, files, htext))
406 % (changesets, revisions, files, htext))
403 repo.invalidatevolatilesets()
407 repo.invalidatevolatilesets()
404
408
405 if changesets > 0:
409 if changesets > 0:
406 p = lambda: tr.writepending() and repo.root or ""
410 p = lambda: tr.writepending() and repo.root or ""
407 if 'node' not in tr.hookargs:
411 if 'node' not in tr.hookargs:
408 tr.hookargs['node'] = hex(cl.node(clstart))
412 tr.hookargs['node'] = hex(cl.node(clstart))
409 hookargs = dict(tr.hookargs)
413 hookargs = dict(tr.hookargs)
410 else:
414 else:
411 hookargs = dict(tr.hookargs)
415 hookargs = dict(tr.hookargs)
412 hookargs['node'] = hex(cl.node(clstart))
416 hookargs['node'] = hex(cl.node(clstart))
413 repo.hook('pretxnchangegroup', throw=True, pending=p,
417 repo.hook('pretxnchangegroup', throw=True, pending=p,
414 **hookargs)
418 **hookargs)
415
419
416 added = [cl.node(r) for r in xrange(clstart, clend)]
420 added = [cl.node(r) for r in xrange(clstart, clend)]
417 publishing = repo.publishing()
421 publishing = repo.publishing()
418 if srctype in ('push', 'serve'):
422 if srctype in ('push', 'serve'):
419 # Old servers can not push the boundary themselves.
423 # Old servers can not push the boundary themselves.
420 # New servers won't push the boundary if changeset already
424 # New servers won't push the boundary if changeset already
421 # exists locally as secret
425 # exists locally as secret
422 #
426 #
423 # We should not use added here but the list of all change in
427 # We should not use added here but the list of all change in
424 # the bundle
428 # the bundle
425 if publishing:
429 if publishing:
426 phases.advanceboundary(repo, tr, phases.public, srccontent)
430 phases.advanceboundary(repo, tr, phases.public, srccontent)
427 else:
431 else:
428 # Those changesets have been pushed from the outside, their
432 # Those changesets have been pushed from the outside, their
429 # phases are going to be pushed alongside. Therefor
433 # phases are going to be pushed alongside. Therefor
430 # `targetphase` is ignored.
434 # `targetphase` is ignored.
431 phases.advanceboundary(repo, tr, phases.draft, srccontent)
435 phases.advanceboundary(repo, tr, phases.draft, srccontent)
432 phases.retractboundary(repo, tr, phases.draft, added)
436 phases.retractboundary(repo, tr, phases.draft, added)
433 elif srctype != 'strip':
437 elif srctype != 'strip':
434 # publishing only alter behavior during push
438 # publishing only alter behavior during push
435 #
439 #
436 # strip should not touch boundary at all
440 # strip should not touch boundary at all
437 phases.retractboundary(repo, tr, targetphase, added)
441 phases.retractboundary(repo, tr, targetphase, added)
438
442
439 if changesets > 0:
443 if changesets > 0:
440 if srctype != 'strip':
444 if srctype != 'strip':
441 # During strip, branchcache is invalid but coming call to
445 # During strip, branchcache is invalid but coming call to
442 # `destroyed` will repair it.
446 # `destroyed` will repair it.
443 # In other case we can safely update cache on disk.
447 # In other case we can safely update cache on disk.
444 branchmap.updatecache(repo.filtered('served'))
448 branchmap.updatecache(repo.filtered('served'))
445
449
446 def runhooks():
450 def runhooks():
447 # These hooks run when the lock releases, not when the
451 # These hooks run when the lock releases, not when the
448 # transaction closes. So it's possible for the changelog
452 # transaction closes. So it's possible for the changelog
449 # to have changed since we last saw it.
453 # to have changed since we last saw it.
450 if clstart >= len(repo):
454 if clstart >= len(repo):
451 return
455 return
452
456
453 # forcefully update the on-disk branch cache
457 # forcefully update the on-disk branch cache
454 repo.ui.debug("updating the branch cache\n")
458 repo.ui.debug("updating the branch cache\n")
455 repo.hook("changegroup", **hookargs)
459 repo.hook("changegroup", **hookargs)
456
460
457 for n in added:
461 for n in added:
458 args = hookargs.copy()
462 args = hookargs.copy()
459 args['node'] = hex(n)
463 args['node'] = hex(n)
460 repo.hook("incoming", **args)
464 repo.hook("incoming", **args)
461
465
462 newheads = [h for h in repo.heads() if h not in oldheads]
466 newheads = [h for h in repo.heads() if h not in oldheads]
463 repo.ui.log("incoming",
467 repo.ui.log("incoming",
464 "%s incoming changes - new heads: %s\n",
468 "%s incoming changes - new heads: %s\n",
465 len(added),
469 len(added),
466 ', '.join([hex(c[:6]) for c in newheads]))
470 ', '.join([hex(c[:6]) for c in newheads]))
467
471
468 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
472 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
469 lambda tr: repo._afterlock(runhooks))
473 lambda tr: repo._afterlock(runhooks))
470
474
471 tr.close()
475 tr.close()
472
476
473 finally:
477 finally:
474 tr.release()
478 tr.release()
475 repo.ui.flush()
479 repo.ui.flush()
476 # never return 0 here:
480 # never return 0 here:
477 if dh < 0:
481 if dh < 0:
478 return dh - 1
482 return dh - 1
479 else:
483 else:
480 return dh + 1
484 return dh + 1
481
485
482 class cg2unpacker(cg1unpacker):
486 class cg2unpacker(cg1unpacker):
483 """Unpacker for cg2 streams.
487 """Unpacker for cg2 streams.
484
488
485 cg2 streams add support for generaldelta, so the delta header
489 cg2 streams add support for generaldelta, so the delta header
486 format is slightly different. All other features about the data
490 format is slightly different. All other features about the data
487 remain the same.
491 remain the same.
488 """
492 """
489 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
493 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
490 deltaheadersize = struct.calcsize(deltaheader)
494 deltaheadersize = struct.calcsize(deltaheader)
491 version = '02'
495 version = '02'
492
496
493 def _deltaheader(self, headertuple, prevnode):
497 def _deltaheader(self, headertuple, prevnode):
494 node, p1, p2, deltabase, cs = headertuple
498 node, p1, p2, deltabase, cs = headertuple
495 return node, p1, p2, deltabase, cs
499 return node, p1, p2, deltabase, cs
496
500
497 class headerlessfixup(object):
501 class headerlessfixup(object):
498 def __init__(self, fh, h):
502 def __init__(self, fh, h):
499 self._h = h
503 self._h = h
500 self._fh = fh
504 self._fh = fh
501 def read(self, n):
505 def read(self, n):
502 if self._h:
506 if self._h:
503 d, self._h = self._h[:n], self._h[n:]
507 d, self._h = self._h[:n], self._h[n:]
504 if len(d) < n:
508 if len(d) < n:
505 d += readexactly(self._fh, n - len(d))
509 d += readexactly(self._fh, n - len(d))
506 return d
510 return d
507 return readexactly(self._fh, n)
511 return readexactly(self._fh, n)
508
512
509 class cg1packer(object):
513 class cg1packer(object):
510 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
514 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
511 version = '01'
515 version = '01'
512 def __init__(self, repo, bundlecaps=None):
516 def __init__(self, repo, bundlecaps=None):
513 """Given a source repo, construct a bundler.
517 """Given a source repo, construct a bundler.
514
518
515 bundlecaps is optional and can be used to specify the set of
519 bundlecaps is optional and can be used to specify the set of
516 capabilities which can be used to build the bundle.
520 capabilities which can be used to build the bundle.
517 """
521 """
518 # Set of capabilities we can use to build the bundle.
522 # Set of capabilities we can use to build the bundle.
519 if bundlecaps is None:
523 if bundlecaps is None:
520 bundlecaps = set()
524 bundlecaps = set()
521 self._bundlecaps = bundlecaps
525 self._bundlecaps = bundlecaps
522 # experimental config: bundle.reorder
526 # experimental config: bundle.reorder
523 reorder = repo.ui.config('bundle', 'reorder', 'auto')
527 reorder = repo.ui.config('bundle', 'reorder', 'auto')
524 if reorder == 'auto':
528 if reorder == 'auto':
525 reorder = None
529 reorder = None
526 else:
530 else:
527 reorder = util.parsebool(reorder)
531 reorder = util.parsebool(reorder)
528 self._repo = repo
532 self._repo = repo
529 self._reorder = reorder
533 self._reorder = reorder
530 self._progress = repo.ui.progress
534 self._progress = repo.ui.progress
531 if self._repo.ui.verbose and not self._repo.ui.debugflag:
535 if self._repo.ui.verbose and not self._repo.ui.debugflag:
532 self._verbosenote = self._repo.ui.note
536 self._verbosenote = self._repo.ui.note
533 else:
537 else:
534 self._verbosenote = lambda s: None
538 self._verbosenote = lambda s: None
535
539
536 def close(self):
540 def close(self):
537 return closechunk()
541 return closechunk()
538
542
539 def fileheader(self, fname):
543 def fileheader(self, fname):
540 return chunkheader(len(fname)) + fname
544 return chunkheader(len(fname)) + fname
541
545
542 def group(self, nodelist, revlog, lookup, units=None):
546 def group(self, nodelist, revlog, lookup, units=None):
543 """Calculate a delta group, yielding a sequence of changegroup chunks
547 """Calculate a delta group, yielding a sequence of changegroup chunks
544 (strings).
548 (strings).
545
549
546 Given a list of changeset revs, return a set of deltas and
550 Given a list of changeset revs, return a set of deltas and
547 metadata corresponding to nodes. The first delta is
551 metadata corresponding to nodes. The first delta is
548 first parent(nodelist[0]) -> nodelist[0], the receiver is
552 first parent(nodelist[0]) -> nodelist[0], the receiver is
549 guaranteed to have this parent as it has all history before
553 guaranteed to have this parent as it has all history before
550 these changesets. In the case firstparent is nullrev the
554 these changesets. In the case firstparent is nullrev the
551 changegroup starts with a full revision.
555 changegroup starts with a full revision.
552
556
553 If units is not None, progress detail will be generated, units specifies
557 If units is not None, progress detail will be generated, units specifies
554 the type of revlog that is touched (changelog, manifest, etc.).
558 the type of revlog that is touched (changelog, manifest, etc.).
555 """
559 """
556 # if we don't have any revisions touched by these changesets, bail
560 # if we don't have any revisions touched by these changesets, bail
557 if len(nodelist) == 0:
561 if len(nodelist) == 0:
558 yield self.close()
562 yield self.close()
559 return
563 return
560
564
561 # for generaldelta revlogs, we linearize the revs; this will both be
565 # for generaldelta revlogs, we linearize the revs; this will both be
562 # much quicker and generate a much smaller bundle
566 # much quicker and generate a much smaller bundle
563 if (revlog._generaldelta and self._reorder is None) or self._reorder:
567 if (revlog._generaldelta and self._reorder is None) or self._reorder:
564 dag = dagutil.revlogdag(revlog)
568 dag = dagutil.revlogdag(revlog)
565 revs = set(revlog.rev(n) for n in nodelist)
569 revs = set(revlog.rev(n) for n in nodelist)
566 revs = dag.linearize(revs)
570 revs = dag.linearize(revs)
567 else:
571 else:
568 revs = sorted([revlog.rev(n) for n in nodelist])
572 revs = sorted([revlog.rev(n) for n in nodelist])
569
573
570 # add the parent of the first rev
574 # add the parent of the first rev
571 p = revlog.parentrevs(revs[0])[0]
575 p = revlog.parentrevs(revs[0])[0]
572 revs.insert(0, p)
576 revs.insert(0, p)
573
577
574 # build deltas
578 # build deltas
575 total = len(revs) - 1
579 total = len(revs) - 1
576 msgbundling = _('bundling')
580 msgbundling = _('bundling')
577 for r in xrange(len(revs) - 1):
581 for r in xrange(len(revs) - 1):
578 if units is not None:
582 if units is not None:
579 self._progress(msgbundling, r + 1, unit=units, total=total)
583 self._progress(msgbundling, r + 1, unit=units, total=total)
580 prev, curr = revs[r], revs[r + 1]
584 prev, curr = revs[r], revs[r + 1]
581 linknode = lookup(revlog.node(curr))
585 linknode = lookup(revlog.node(curr))
582 for c in self.revchunk(revlog, curr, prev, linknode):
586 for c in self.revchunk(revlog, curr, prev, linknode):
583 yield c
587 yield c
584
588
585 if units is not None:
589 if units is not None:
586 self._progress(msgbundling, None)
590 self._progress(msgbundling, None)
587 yield self.close()
591 yield self.close()
588
592
589 # filter any nodes that claim to be part of the known set
593 # filter any nodes that claim to be part of the known set
590 def prune(self, revlog, missing, commonrevs):
594 def prune(self, revlog, missing, commonrevs):
591 rr, rl = revlog.rev, revlog.linkrev
595 rr, rl = revlog.rev, revlog.linkrev
592 return [n for n in missing if rl(rr(n)) not in commonrevs]
596 return [n for n in missing if rl(rr(n)) not in commonrevs]
593
597
594 def _packmanifests(self, mfnodes, lookuplinknode):
598 def _packmanifests(self, mfnodes, lookuplinknode):
595 """Pack flat manifests into a changegroup stream."""
599 """Pack flat manifests into a changegroup stream."""
596 ml = self._repo.manifest
600 ml = self._repo.manifest
597 size = 0
601 size = 0
598 for chunk in self.group(
602 for chunk in self.group(
599 mfnodes, ml, lookuplinknode, units=_('manifests')):
603 mfnodes, ml, lookuplinknode, units=_('manifests')):
600 size += len(chunk)
604 size += len(chunk)
601 yield chunk
605 yield chunk
602 self._verbosenote(_('%8.i (manifests)\n') % size)
606 self._verbosenote(_('%8.i (manifests)\n') % size)
603
607
604 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
608 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
605 '''yield a sequence of changegroup chunks (strings)'''
609 '''yield a sequence of changegroup chunks (strings)'''
606 repo = self._repo
610 repo = self._repo
607 cl = repo.changelog
611 cl = repo.changelog
608 ml = repo.manifest
612 ml = repo.manifest
609
613
610 clrevorder = {}
614 clrevorder = {}
611 mfs = {} # needed manifests
615 mfs = {} # needed manifests
612 fnodes = {} # needed file nodes
616 fnodes = {} # needed file nodes
613 changedfiles = set()
617 changedfiles = set()
614
618
615 # Callback for the changelog, used to collect changed files and manifest
619 # Callback for the changelog, used to collect changed files and manifest
616 # nodes.
620 # nodes.
617 # Returns the linkrev node (identity in the changelog case).
621 # Returns the linkrev node (identity in the changelog case).
618 def lookupcl(x):
622 def lookupcl(x):
619 c = cl.read(x)
623 c = cl.read(x)
620 clrevorder[x] = len(clrevorder)
624 clrevorder[x] = len(clrevorder)
621 changedfiles.update(c[3])
625 changedfiles.update(c[3])
622 # record the first changeset introducing this manifest version
626 # record the first changeset introducing this manifest version
623 mfs.setdefault(c[0], x)
627 mfs.setdefault(c[0], x)
624 return x
628 return x
625
629
626 self._verbosenote(_('uncompressed size of bundle content:\n'))
630 self._verbosenote(_('uncompressed size of bundle content:\n'))
627 size = 0
631 size = 0
628 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
632 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
629 size += len(chunk)
633 size += len(chunk)
630 yield chunk
634 yield chunk
631 self._verbosenote(_('%8.i (changelog)\n') % size)
635 self._verbosenote(_('%8.i (changelog)\n') % size)
632
636
633 # We need to make sure that the linkrev in the changegroup refers to
637 # We need to make sure that the linkrev in the changegroup refers to
634 # the first changeset that introduced the manifest or file revision.
638 # the first changeset that introduced the manifest or file revision.
635 # The fastpath is usually safer than the slowpath, because the filelogs
639 # The fastpath is usually safer than the slowpath, because the filelogs
636 # are walked in revlog order.
640 # are walked in revlog order.
637 #
641 #
638 # When taking the slowpath with reorder=None and the manifest revlog
642 # When taking the slowpath with reorder=None and the manifest revlog
639 # uses generaldelta, the manifest may be walked in the "wrong" order.
643 # uses generaldelta, the manifest may be walked in the "wrong" order.
640 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
644 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
641 # cc0ff93d0c0c).
645 # cc0ff93d0c0c).
642 #
646 #
643 # When taking the fastpath, we are only vulnerable to reordering
647 # When taking the fastpath, we are only vulnerable to reordering
644 # of the changelog itself. The changelog never uses generaldelta, so
648 # of the changelog itself. The changelog never uses generaldelta, so
645 # it is only reordered when reorder=True. To handle this case, we
649 # it is only reordered when reorder=True. To handle this case, we
646 # simply take the slowpath, which already has the 'clrevorder' logic.
650 # simply take the slowpath, which already has the 'clrevorder' logic.
647 # This was also fixed in cc0ff93d0c0c.
651 # This was also fixed in cc0ff93d0c0c.
648 fastpathlinkrev = fastpathlinkrev and not self._reorder
652 fastpathlinkrev = fastpathlinkrev and not self._reorder
649 # Callback for the manifest, used to collect linkrevs for filelog
653 # Callback for the manifest, used to collect linkrevs for filelog
650 # revisions.
654 # revisions.
651 # Returns the linkrev node (collected in lookupcl).
655 # Returns the linkrev node (collected in lookupcl).
652 def lookupmflinknode(x):
656 def lookupmflinknode(x):
653 clnode = mfs[x]
657 clnode = mfs[x]
654 if not fastpathlinkrev:
658 if not fastpathlinkrev:
655 mdata = ml.readfast(x)
659 mdata = ml.readfast(x)
656 for f, n in mdata.iteritems():
660 for f, n in mdata.iteritems():
657 if f in changedfiles:
661 if f in changedfiles:
658 # record the first changeset introducing this filelog
662 # record the first changeset introducing this filelog
659 # version
663 # version
660 fclnodes = fnodes.setdefault(f, {})
664 fclnodes = fnodes.setdefault(f, {})
661 fclnode = fclnodes.setdefault(n, clnode)
665 fclnode = fclnodes.setdefault(n, clnode)
662 if clrevorder[clnode] < clrevorder[fclnode]:
666 if clrevorder[clnode] < clrevorder[fclnode]:
663 fclnodes[n] = clnode
667 fclnodes[n] = clnode
664 return clnode
668 return clnode
665
669
666 mfnodes = self.prune(ml, mfs, commonrevs)
670 mfnodes = self.prune(ml, mfs, commonrevs)
667 for x in self._packmanifests(mfnodes, lookupmflinknode):
671 for x in self._packmanifests(mfnodes, lookupmflinknode):
668 yield x
672 yield x
669
673
670 mfs.clear()
674 mfs.clear()
671 clrevs = set(cl.rev(x) for x in clnodes)
675 clrevs = set(cl.rev(x) for x in clnodes)
672
676
673 def linknodes(filerevlog, fname):
677 def linknodes(filerevlog, fname):
674 if fastpathlinkrev:
678 if fastpathlinkrev:
675 llr = filerevlog.linkrev
679 llr = filerevlog.linkrev
676 def genfilenodes():
680 def genfilenodes():
677 for r in filerevlog:
681 for r in filerevlog:
678 linkrev = llr(r)
682 linkrev = llr(r)
679 if linkrev in clrevs:
683 if linkrev in clrevs:
680 yield filerevlog.node(r), cl.node(linkrev)
684 yield filerevlog.node(r), cl.node(linkrev)
681 return dict(genfilenodes())
685 return dict(genfilenodes())
682 return fnodes.get(fname, {})
686 return fnodes.get(fname, {})
683
687
684 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
688 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
685 source):
689 source):
686 yield chunk
690 yield chunk
687
691
688 yield self.close()
692 yield self.close()
689
693
690 if clnodes:
694 if clnodes:
691 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
695 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
692
696
693 # The 'source' parameter is useful for extensions
697 # The 'source' parameter is useful for extensions
694 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
698 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
695 repo = self._repo
699 repo = self._repo
696 progress = self._progress
700 progress = self._progress
697 msgbundling = _('bundling')
701 msgbundling = _('bundling')
698
702
699 total = len(changedfiles)
703 total = len(changedfiles)
700 # for progress output
704 # for progress output
701 msgfiles = _('files')
705 msgfiles = _('files')
702 for i, fname in enumerate(sorted(changedfiles)):
706 for i, fname in enumerate(sorted(changedfiles)):
703 filerevlog = repo.file(fname)
707 filerevlog = repo.file(fname)
704 if not filerevlog:
708 if not filerevlog:
705 raise error.Abort(_("empty or missing revlog for %s") % fname)
709 raise error.Abort(_("empty or missing revlog for %s") % fname)
706
710
707 linkrevnodes = linknodes(filerevlog, fname)
711 linkrevnodes = linknodes(filerevlog, fname)
708 # Lookup for filenodes, we collected the linkrev nodes above in the
712 # Lookup for filenodes, we collected the linkrev nodes above in the
709 # fastpath case and with lookupmf in the slowpath case.
713 # fastpath case and with lookupmf in the slowpath case.
710 def lookupfilelog(x):
714 def lookupfilelog(x):
711 return linkrevnodes[x]
715 return linkrevnodes[x]
712
716
713 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
717 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
714 if filenodes:
718 if filenodes:
715 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
719 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
716 total=total)
720 total=total)
717 h = self.fileheader(fname)
721 h = self.fileheader(fname)
718 size = len(h)
722 size = len(h)
719 yield h
723 yield h
720 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
724 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
721 size += len(chunk)
725 size += len(chunk)
722 yield chunk
726 yield chunk
723 self._verbosenote(_('%8.i %s\n') % (size, fname))
727 self._verbosenote(_('%8.i %s\n') % (size, fname))
724 progress(msgbundling, None)
728 progress(msgbundling, None)
725
729
726 def deltaparent(self, revlog, rev, p1, p2, prev):
730 def deltaparent(self, revlog, rev, p1, p2, prev):
727 return prev
731 return prev
728
732
729 def revchunk(self, revlog, rev, prev, linknode):
733 def revchunk(self, revlog, rev, prev, linknode):
730 node = revlog.node(rev)
734 node = revlog.node(rev)
731 p1, p2 = revlog.parentrevs(rev)
735 p1, p2 = revlog.parentrevs(rev)
732 base = self.deltaparent(revlog, rev, p1, p2, prev)
736 base = self.deltaparent(revlog, rev, p1, p2, prev)
733
737
734 prefix = ''
738 prefix = ''
735 if revlog.iscensored(base) or revlog.iscensored(rev):
739 if revlog.iscensored(base) or revlog.iscensored(rev):
736 try:
740 try:
737 delta = revlog.revision(node)
741 delta = revlog.revision(node)
738 except error.CensoredNodeError as e:
742 except error.CensoredNodeError as e:
739 delta = e.tombstone
743 delta = e.tombstone
740 if base == nullrev:
744 if base == nullrev:
741 prefix = mdiff.trivialdiffheader(len(delta))
745 prefix = mdiff.trivialdiffheader(len(delta))
742 else:
746 else:
743 baselen = revlog.rawsize(base)
747 baselen = revlog.rawsize(base)
744 prefix = mdiff.replacediffheader(baselen, len(delta))
748 prefix = mdiff.replacediffheader(baselen, len(delta))
745 elif base == nullrev:
749 elif base == nullrev:
746 delta = revlog.revision(node)
750 delta = revlog.revision(node)
747 prefix = mdiff.trivialdiffheader(len(delta))
751 prefix = mdiff.trivialdiffheader(len(delta))
748 else:
752 else:
749 delta = revlog.revdiff(base, rev)
753 delta = revlog.revdiff(base, rev)
750 p1n, p2n = revlog.parents(node)
754 p1n, p2n = revlog.parents(node)
751 basenode = revlog.node(base)
755 basenode = revlog.node(base)
752 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode)
756 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode)
753 meta += prefix
757 meta += prefix
754 l = len(meta) + len(delta)
758 l = len(meta) + len(delta)
755 yield chunkheader(l)
759 yield chunkheader(l)
756 yield meta
760 yield meta
757 yield delta
761 yield delta
758 def builddeltaheader(self, node, p1n, p2n, basenode, linknode):
762 def builddeltaheader(self, node, p1n, p2n, basenode, linknode):
759 # do nothing with basenode, it is implicitly the previous one in HG10
763 # do nothing with basenode, it is implicitly the previous one in HG10
760 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
764 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
761
765
762 class cg2packer(cg1packer):
766 class cg2packer(cg1packer):
763 version = '02'
767 version = '02'
764 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
768 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
765
769
766 def __init__(self, repo, bundlecaps=None):
770 def __init__(self, repo, bundlecaps=None):
767 super(cg2packer, self).__init__(repo, bundlecaps)
771 super(cg2packer, self).__init__(repo, bundlecaps)
768 if self._reorder is None:
772 if self._reorder is None:
769 # Since generaldelta is directly supported by cg2, reordering
773 # Since generaldelta is directly supported by cg2, reordering
770 # generally doesn't help, so we disable it by default (treating
774 # generally doesn't help, so we disable it by default (treating
771 # bundle.reorder=auto just like bundle.reorder=False).
775 # bundle.reorder=auto just like bundle.reorder=False).
772 self._reorder = False
776 self._reorder = False
773
777
774 def deltaparent(self, revlog, rev, p1, p2, prev):
778 def deltaparent(self, revlog, rev, p1, p2, prev):
775 dp = revlog.deltaparent(rev)
779 dp = revlog.deltaparent(rev)
776 # avoid storing full revisions; pick prev in those cases
780 # avoid storing full revisions; pick prev in those cases
777 # also pick prev when we can't be sure remote has dp
781 # also pick prev when we can't be sure remote has dp
778 if dp == nullrev or (dp != p1 and dp != p2 and dp != prev):
782 if dp == nullrev or (dp != p1 and dp != p2 and dp != prev):
779 return prev
783 return prev
780 return dp
784 return dp
781
785
782 def builddeltaheader(self, node, p1n, p2n, basenode, linknode):
786 def builddeltaheader(self, node, p1n, p2n, basenode, linknode):
783 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
787 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
784
788
785 packermap = {'01': (cg1packer, cg1unpacker),
789 packermap = {'01': (cg1packer, cg1unpacker),
786 # cg2 adds support for exchanging generaldelta
790 # cg2 adds support for exchanging generaldelta
787 '02': (cg2packer, cg2unpacker),
791 '02': (cg2packer, cg2unpacker),
788 }
792 }
789
793
790 def _changegroupinfo(repo, nodes, source):
794 def _changegroupinfo(repo, nodes, source):
791 if repo.ui.verbose or source == 'bundle':
795 if repo.ui.verbose or source == 'bundle':
792 repo.ui.status(_("%d changesets found\n") % len(nodes))
796 repo.ui.status(_("%d changesets found\n") % len(nodes))
793 if repo.ui.debugflag:
797 if repo.ui.debugflag:
794 repo.ui.debug("list of changesets:\n")
798 repo.ui.debug("list of changesets:\n")
795 for node in nodes:
799 for node in nodes:
796 repo.ui.debug("%s\n" % hex(node))
800 repo.ui.debug("%s\n" % hex(node))
797
801
798 def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
802 def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
799 repo = repo.unfiltered()
803 repo = repo.unfiltered()
800 commonrevs = outgoing.common
804 commonrevs = outgoing.common
801 csets = outgoing.missing
805 csets = outgoing.missing
802 heads = outgoing.missingheads
806 heads = outgoing.missingheads
803 # We go through the fast path if we get told to, or if all (unfiltered
807 # We go through the fast path if we get told to, or if all (unfiltered
804 # heads have been requested (since we then know there all linkrevs will
808 # heads have been requested (since we then know there all linkrevs will
805 # be pulled by the client).
809 # be pulled by the client).
806 heads.sort()
810 heads.sort()
807 fastpathlinkrev = fastpath or (
811 fastpathlinkrev = fastpath or (
808 repo.filtername is None and heads == sorted(repo.heads()))
812 repo.filtername is None and heads == sorted(repo.heads()))
809
813
810 repo.hook('preoutgoing', throw=True, source=source)
814 repo.hook('preoutgoing', throw=True, source=source)
811 _changegroupinfo(repo, csets, source)
815 _changegroupinfo(repo, csets, source)
812 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
816 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
813
817
814 def getsubset(repo, outgoing, bundler, source, fastpath=False):
818 def getsubset(repo, outgoing, bundler, source, fastpath=False):
815 gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
819 gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
816 return packermap[bundler.version][1](util.chunkbuffer(gengroup), None)
820 return packermap[bundler.version][1](util.chunkbuffer(gengroup), None)
817
821
818 def changegroupsubset(repo, roots, heads, source, version='01'):
822 def changegroupsubset(repo, roots, heads, source, version='01'):
819 """Compute a changegroup consisting of all the nodes that are
823 """Compute a changegroup consisting of all the nodes that are
820 descendants of any of the roots and ancestors of any of the heads.
824 descendants of any of the roots and ancestors of any of the heads.
821 Return a chunkbuffer object whose read() method will return
825 Return a chunkbuffer object whose read() method will return
822 successive changegroup chunks.
826 successive changegroup chunks.
823
827
824 It is fairly complex as determining which filenodes and which
828 It is fairly complex as determining which filenodes and which
825 manifest nodes need to be included for the changeset to be complete
829 manifest nodes need to be included for the changeset to be complete
826 is non-trivial.
830 is non-trivial.
827
831
828 Another wrinkle is doing the reverse, figuring out which changeset in
832 Another wrinkle is doing the reverse, figuring out which changeset in
829 the changegroup a particular filenode or manifestnode belongs to.
833 the changegroup a particular filenode or manifestnode belongs to.
830 """
834 """
831 cl = repo.changelog
835 cl = repo.changelog
832 if not roots:
836 if not roots:
833 roots = [nullid]
837 roots = [nullid]
834 discbases = []
838 discbases = []
835 for n in roots:
839 for n in roots:
836 discbases.extend([p for p in cl.parents(n) if p != nullid])
840 discbases.extend([p for p in cl.parents(n) if p != nullid])
837 # TODO: remove call to nodesbetween.
841 # TODO: remove call to nodesbetween.
838 csets, roots, heads = cl.nodesbetween(roots, heads)
842 csets, roots, heads = cl.nodesbetween(roots, heads)
839 included = set(csets)
843 included = set(csets)
840 discbases = [n for n in discbases if n not in included]
844 discbases = [n for n in discbases if n not in included]
841 outgoing = discovery.outgoing(cl, discbases, heads)
845 outgoing = discovery.outgoing(cl, discbases, heads)
842 bundler = packermap[version][0](repo)
846 bundler = packermap[version][0](repo)
843 return getsubset(repo, outgoing, bundler, source)
847 return getsubset(repo, outgoing, bundler, source)
844
848
845 def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
849 def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
846 version='01'):
850 version='01'):
847 """Like getbundle, but taking a discovery.outgoing as an argument.
851 """Like getbundle, but taking a discovery.outgoing as an argument.
848
852
849 This is only implemented for local repos and reuses potentially
853 This is only implemented for local repos and reuses potentially
850 precomputed sets in outgoing. Returns a raw changegroup generator."""
854 precomputed sets in outgoing. Returns a raw changegroup generator."""
851 if not outgoing.missing:
855 if not outgoing.missing:
852 return None
856 return None
853 bundler = packermap[version][0](repo, bundlecaps)
857 bundler = packermap[version][0](repo, bundlecaps)
854 return getsubsetraw(repo, outgoing, bundler, source)
858 return getsubsetraw(repo, outgoing, bundler, source)
855
859
856 def getlocalchangegroup(repo, source, outgoing, bundlecaps=None,
860 def getlocalchangegroup(repo, source, outgoing, bundlecaps=None,
857 version='01'):
861 version='01'):
858 """Like getbundle, but taking a discovery.outgoing as an argument.
862 """Like getbundle, but taking a discovery.outgoing as an argument.
859
863
860 This is only implemented for local repos and reuses potentially
864 This is only implemented for local repos and reuses potentially
861 precomputed sets in outgoing."""
865 precomputed sets in outgoing."""
862 if not outgoing.missing:
866 if not outgoing.missing:
863 return None
867 return None
864 bundler = packermap[version][0](repo, bundlecaps)
868 bundler = packermap[version][0](repo, bundlecaps)
865 return getsubset(repo, outgoing, bundler, source)
869 return getsubset(repo, outgoing, bundler, source)
866
870
867 def computeoutgoing(repo, heads, common):
871 def computeoutgoing(repo, heads, common):
868 """Computes which revs are outgoing given a set of common
872 """Computes which revs are outgoing given a set of common
869 and a set of heads.
873 and a set of heads.
870
874
871 This is a separate function so extensions can have access to
875 This is a separate function so extensions can have access to
872 the logic.
876 the logic.
873
877
874 Returns a discovery.outgoing object.
878 Returns a discovery.outgoing object.
875 """
879 """
876 cl = repo.changelog
880 cl = repo.changelog
877 if common:
881 if common:
878 hasnode = cl.hasnode
882 hasnode = cl.hasnode
879 common = [n for n in common if hasnode(n)]
883 common = [n for n in common if hasnode(n)]
880 else:
884 else:
881 common = [nullid]
885 common = [nullid]
882 if not heads:
886 if not heads:
883 heads = cl.heads()
887 heads = cl.heads()
884 return discovery.outgoing(cl, common, heads)
888 return discovery.outgoing(cl, common, heads)
885
889
886 def getchangegroup(repo, source, heads=None, common=None, bundlecaps=None,
890 def getchangegroup(repo, source, heads=None, common=None, bundlecaps=None,
887 version='01'):
891 version='01'):
888 """Like changegroupsubset, but returns the set difference between the
892 """Like changegroupsubset, but returns the set difference between the
889 ancestors of heads and the ancestors common.
893 ancestors of heads and the ancestors common.
890
894
891 If heads is None, use the local heads. If common is None, use [nullid].
895 If heads is None, use the local heads. If common is None, use [nullid].
892
896
893 The nodes in common might not all be known locally due to the way the
897 The nodes in common might not all be known locally due to the way the
894 current discovery protocol works.
898 current discovery protocol works.
895 """
899 """
896 outgoing = computeoutgoing(repo, heads, common)
900 outgoing = computeoutgoing(repo, heads, common)
897 return getlocalchangegroup(repo, source, outgoing, bundlecaps=bundlecaps,
901 return getlocalchangegroup(repo, source, outgoing, bundlecaps=bundlecaps,
898 version=version)
902 version=version)
899
903
900 def changegroup(repo, basenodes, source):
904 def changegroup(repo, basenodes, source):
901 # to avoid a race we use changegroupsubset() (issue1320)
905 # to avoid a race we use changegroupsubset() (issue1320)
902 return changegroupsubset(repo, basenodes, repo.heads(), source)
906 return changegroupsubset(repo, basenodes, repo.heads(), source)
903
907
904 def _addchangegroupfiles(repo, source, revmap, trp, pr, needfiles):
908 def _addchangegroupfiles(repo, source, revmap, trp, pr, needfiles):
905 revisions = 0
909 revisions = 0
906 files = 0
910 files = 0
907 while True:
911 while True:
908 chunkdata = source.filelogheader()
912 chunkdata = source.filelogheader()
909 if not chunkdata:
913 if not chunkdata:
910 break
914 break
911 f = chunkdata["filename"]
915 f = chunkdata["filename"]
912 repo.ui.debug("adding %s revisions\n" % f)
916 repo.ui.debug("adding %s revisions\n" % f)
913 pr()
917 pr()
914 fl = repo.file(f)
918 fl = repo.file(f)
915 o = len(fl)
919 o = len(fl)
916 try:
920 try:
917 if not fl.addgroup(source, revmap, trp):
921 if not fl.addgroup(source, revmap, trp):
918 raise error.Abort(_("received file revlog group is empty"))
922 raise error.Abort(_("received file revlog group is empty"))
919 except error.CensoredBaseError as e:
923 except error.CensoredBaseError as e:
920 raise error.Abort(_("received delta base is censored: %s") % e)
924 raise error.Abort(_("received delta base is censored: %s") % e)
921 revisions += len(fl) - o
925 revisions += len(fl) - o
922 files += 1
926 files += 1
923 if f in needfiles:
927 if f in needfiles:
924 needs = needfiles[f]
928 needs = needfiles[f]
925 for new in xrange(o, len(fl)):
929 for new in xrange(o, len(fl)):
926 n = fl.node(new)
930 n = fl.node(new)
927 if n in needs:
931 if n in needs:
928 needs.remove(n)
932 needs.remove(n)
929 else:
933 else:
930 raise error.Abort(
934 raise error.Abort(
931 _("received spurious file revlog entry"))
935 _("received spurious file revlog entry"))
932 if not needs:
936 if not needs:
933 del needfiles[f]
937 del needfiles[f]
934 repo.ui.progress(_('files'), None)
938 repo.ui.progress(_('files'), None)
935
939
936 for f, needs in needfiles.iteritems():
940 for f, needs in needfiles.iteritems():
937 fl = repo.file(f)
941 fl = repo.file(f)
938 for n in needs:
942 for n in needs:
939 try:
943 try:
940 fl.rev(n)
944 fl.rev(n)
941 except error.LookupError:
945 except error.LookupError:
942 raise error.Abort(
946 raise error.Abort(
943 _('missing file data for %s:%s - run hg verify') %
947 _('missing file data for %s:%s - run hg verify') %
944 (f, hex(n)))
948 (f, hex(n)))
945
949
946 return revisions, files
950 return revisions, files
General Comments 0
You need to be logged in to leave comments. Login now