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