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