##// END OF EJS Templates
changegroup: move manifest packing into a separate function...
Augie Fackler -
r26711:0ef0aec5 default
parent child Browse files
Show More
@@ -1,940 +1,946 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 _packmanifests(self, mfnodes, lookuplinknode):
595 """Pack flat manifests into a changegroup stream."""
596 ml = self._repo.manifest
597 size = 0
598 for chunk in self.group(
599 mfnodes, ml, lookuplinknode, units=_('manifests')):
600 size += len(chunk)
601 yield chunk
602 self._verbosenote(_('%8.i (manifests)\n') % size)
603
594 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
604 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
595 '''yield a sequence of changegroup chunks (strings)'''
605 '''yield a sequence of changegroup chunks (strings)'''
596 repo = self._repo
606 repo = self._repo
597 cl = repo.changelog
607 cl = repo.changelog
598 ml = repo.manifest
608 ml = repo.manifest
599
609
600 clrevorder = {}
610 clrevorder = {}
601 mfs = {} # needed manifests
611 mfs = {} # needed manifests
602 fnodes = {} # needed file nodes
612 fnodes = {} # needed file nodes
603 changedfiles = set()
613 changedfiles = set()
604
614
605 # Callback for the changelog, used to collect changed files and manifest
615 # Callback for the changelog, used to collect changed files and manifest
606 # nodes.
616 # nodes.
607 # Returns the linkrev node (identity in the changelog case).
617 # Returns the linkrev node (identity in the changelog case).
608 def lookupcl(x):
618 def lookupcl(x):
609 c = cl.read(x)
619 c = cl.read(x)
610 clrevorder[x] = len(clrevorder)
620 clrevorder[x] = len(clrevorder)
611 changedfiles.update(c[3])
621 changedfiles.update(c[3])
612 # record the first changeset introducing this manifest version
622 # record the first changeset introducing this manifest version
613 mfs.setdefault(c[0], x)
623 mfs.setdefault(c[0], x)
614 return x
624 return x
615
625
616 self._verbosenote(_('uncompressed size of bundle content:\n'))
626 self._verbosenote(_('uncompressed size of bundle content:\n'))
617 size = 0
627 size = 0
618 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
628 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
619 size += len(chunk)
629 size += len(chunk)
620 yield chunk
630 yield chunk
621 self._verbosenote(_('%8.i (changelog)\n') % size)
631 self._verbosenote(_('%8.i (changelog)\n') % size)
622
632
623 # We need to make sure that the linkrev in the changegroup refers to
633 # We need to make sure that the linkrev in the changegroup refers to
624 # the first changeset that introduced the manifest or file revision.
634 # the first changeset that introduced the manifest or file revision.
625 # The fastpath is usually safer than the slowpath, because the filelogs
635 # The fastpath is usually safer than the slowpath, because the filelogs
626 # are walked in revlog order.
636 # are walked in revlog order.
627 #
637 #
628 # When taking the slowpath with reorder=None and the manifest revlog
638 # When taking the slowpath with reorder=None and the manifest revlog
629 # uses generaldelta, the manifest may be walked in the "wrong" order.
639 # uses generaldelta, the manifest may be walked in the "wrong" order.
630 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
640 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
631 # cc0ff93d0c0c).
641 # cc0ff93d0c0c).
632 #
642 #
633 # When taking the fastpath, we are only vulnerable to reordering
643 # When taking the fastpath, we are only vulnerable to reordering
634 # of the changelog itself. The changelog never uses generaldelta, so
644 # of the changelog itself. The changelog never uses generaldelta, so
635 # it is only reordered when reorder=True. To handle this case, we
645 # it is only reordered when reorder=True. To handle this case, we
636 # simply take the slowpath, which already has the 'clrevorder' logic.
646 # simply take the slowpath, which already has the 'clrevorder' logic.
637 # This was also fixed in cc0ff93d0c0c.
647 # This was also fixed in cc0ff93d0c0c.
638 fastpathlinkrev = fastpathlinkrev and not self._reorder
648 fastpathlinkrev = fastpathlinkrev and not self._reorder
639 # Callback for the manifest, used to collect linkrevs for filelog
649 # Callback for the manifest, used to collect linkrevs for filelog
640 # revisions.
650 # revisions.
641 # Returns the linkrev node (collected in lookupcl).
651 # Returns the linkrev node (collected in lookupcl).
642 def lookupmflinknode(x):
652 def lookupmflinknode(x):
643 clnode = mfs[x]
653 clnode = mfs[x]
644 if not fastpathlinkrev:
654 if not fastpathlinkrev:
645 mdata = ml.readfast(x)
655 mdata = ml.readfast(x)
646 for f, n in mdata.iteritems():
656 for f, n in mdata.iteritems():
647 if f in changedfiles:
657 if f in changedfiles:
648 # record the first changeset introducing this filelog
658 # record the first changeset introducing this filelog
649 # version
659 # version
650 fclnodes = fnodes.setdefault(f, {})
660 fclnodes = fnodes.setdefault(f, {})
651 fclnode = fclnodes.setdefault(n, clnode)
661 fclnode = fclnodes.setdefault(n, clnode)
652 if clrevorder[clnode] < clrevorder[fclnode]:
662 if clrevorder[clnode] < clrevorder[fclnode]:
653 fclnodes[n] = clnode
663 fclnodes[n] = clnode
654 return clnode
664 return clnode
655
665
656 mfnodes = self.prune(ml, mfs, commonrevs)
666 mfnodes = self.prune(ml, mfs, commonrevs)
657 size = 0
667 for x in self._packmanifests(mfnodes, lookupmflinknode):
658 for chunk in self.group(
668 yield x
659 mfnodes, ml, lookupmflinknode, units=_('manifests')):
660 size += len(chunk)
661 yield chunk
662 self._verbosenote(_('%8.i (manifests)\n') % size)
663
669
664 mfs.clear()
670 mfs.clear()
665 clrevs = set(cl.rev(x) for x in clnodes)
671 clrevs = set(cl.rev(x) for x in clnodes)
666
672
667 def linknodes(filerevlog, fname):
673 def linknodes(filerevlog, fname):
668 if fastpathlinkrev:
674 if fastpathlinkrev:
669 llr = filerevlog.linkrev
675 llr = filerevlog.linkrev
670 def genfilenodes():
676 def genfilenodes():
671 for r in filerevlog:
677 for r in filerevlog:
672 linkrev = llr(r)
678 linkrev = llr(r)
673 if linkrev in clrevs:
679 if linkrev in clrevs:
674 yield filerevlog.node(r), cl.node(linkrev)
680 yield filerevlog.node(r), cl.node(linkrev)
675 return dict(genfilenodes())
681 return dict(genfilenodes())
676 return fnodes.get(fname, {})
682 return fnodes.get(fname, {})
677
683
678 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
684 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
679 source):
685 source):
680 yield chunk
686 yield chunk
681
687
682 yield self.close()
688 yield self.close()
683
689
684 if clnodes:
690 if clnodes:
685 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
691 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
686
692
687 # The 'source' parameter is useful for extensions
693 # The 'source' parameter is useful for extensions
688 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
694 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
689 repo = self._repo
695 repo = self._repo
690 progress = self._progress
696 progress = self._progress
691 msgbundling = _('bundling')
697 msgbundling = _('bundling')
692
698
693 total = len(changedfiles)
699 total = len(changedfiles)
694 # for progress output
700 # for progress output
695 msgfiles = _('files')
701 msgfiles = _('files')
696 for i, fname in enumerate(sorted(changedfiles)):
702 for i, fname in enumerate(sorted(changedfiles)):
697 filerevlog = repo.file(fname)
703 filerevlog = repo.file(fname)
698 if not filerevlog:
704 if not filerevlog:
699 raise error.Abort(_("empty or missing revlog for %s") % fname)
705 raise error.Abort(_("empty or missing revlog for %s") % fname)
700
706
701 linkrevnodes = linknodes(filerevlog, fname)
707 linkrevnodes = linknodes(filerevlog, fname)
702 # Lookup for filenodes, we collected the linkrev nodes above in the
708 # Lookup for filenodes, we collected the linkrev nodes above in the
703 # fastpath case and with lookupmf in the slowpath case.
709 # fastpath case and with lookupmf in the slowpath case.
704 def lookupfilelog(x):
710 def lookupfilelog(x):
705 return linkrevnodes[x]
711 return linkrevnodes[x]
706
712
707 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
713 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
708 if filenodes:
714 if filenodes:
709 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
715 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
710 total=total)
716 total=total)
711 h = self.fileheader(fname)
717 h = self.fileheader(fname)
712 size = len(h)
718 size = len(h)
713 yield h
719 yield h
714 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
720 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
715 size += len(chunk)
721 size += len(chunk)
716 yield chunk
722 yield chunk
717 self._verbosenote(_('%8.i %s\n') % (size, fname))
723 self._verbosenote(_('%8.i %s\n') % (size, fname))
718 progress(msgbundling, None)
724 progress(msgbundling, None)
719
725
720 def deltaparent(self, revlog, rev, p1, p2, prev):
726 def deltaparent(self, revlog, rev, p1, p2, prev):
721 return prev
727 return prev
722
728
723 def revchunk(self, revlog, rev, prev, linknode):
729 def revchunk(self, revlog, rev, prev, linknode):
724 node = revlog.node(rev)
730 node = revlog.node(rev)
725 p1, p2 = revlog.parentrevs(rev)
731 p1, p2 = revlog.parentrevs(rev)
726 base = self.deltaparent(revlog, rev, p1, p2, prev)
732 base = self.deltaparent(revlog, rev, p1, p2, prev)
727
733
728 prefix = ''
734 prefix = ''
729 if revlog.iscensored(base) or revlog.iscensored(rev):
735 if revlog.iscensored(base) or revlog.iscensored(rev):
730 try:
736 try:
731 delta = revlog.revision(node)
737 delta = revlog.revision(node)
732 except error.CensoredNodeError as e:
738 except error.CensoredNodeError as e:
733 delta = e.tombstone
739 delta = e.tombstone
734 if base == nullrev:
740 if base == nullrev:
735 prefix = mdiff.trivialdiffheader(len(delta))
741 prefix = mdiff.trivialdiffheader(len(delta))
736 else:
742 else:
737 baselen = revlog.rawsize(base)
743 baselen = revlog.rawsize(base)
738 prefix = mdiff.replacediffheader(baselen, len(delta))
744 prefix = mdiff.replacediffheader(baselen, len(delta))
739 elif base == nullrev:
745 elif base == nullrev:
740 delta = revlog.revision(node)
746 delta = revlog.revision(node)
741 prefix = mdiff.trivialdiffheader(len(delta))
747 prefix = mdiff.trivialdiffheader(len(delta))
742 else:
748 else:
743 delta = revlog.revdiff(base, rev)
749 delta = revlog.revdiff(base, rev)
744 p1n, p2n = revlog.parents(node)
750 p1n, p2n = revlog.parents(node)
745 basenode = revlog.node(base)
751 basenode = revlog.node(base)
746 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode)
752 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode)
747 meta += prefix
753 meta += prefix
748 l = len(meta) + len(delta)
754 l = len(meta) + len(delta)
749 yield chunkheader(l)
755 yield chunkheader(l)
750 yield meta
756 yield meta
751 yield delta
757 yield delta
752 def builddeltaheader(self, node, p1n, p2n, basenode, linknode):
758 def builddeltaheader(self, node, p1n, p2n, basenode, linknode):
753 # do nothing with basenode, it is implicitly the previous one in HG10
759 # do nothing with basenode, it is implicitly the previous one in HG10
754 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
760 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
755
761
756 class cg2packer(cg1packer):
762 class cg2packer(cg1packer):
757 version = '02'
763 version = '02'
758 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
764 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
759
765
760 def __init__(self, repo, bundlecaps=None):
766 def __init__(self, repo, bundlecaps=None):
761 super(cg2packer, self).__init__(repo, bundlecaps)
767 super(cg2packer, self).__init__(repo, bundlecaps)
762 if self._reorder is None:
768 if self._reorder is None:
763 # Since generaldelta is directly supported by cg2, reordering
769 # Since generaldelta is directly supported by cg2, reordering
764 # generally doesn't help, so we disable it by default (treating
770 # generally doesn't help, so we disable it by default (treating
765 # bundle.reorder=auto just like bundle.reorder=False).
771 # bundle.reorder=auto just like bundle.reorder=False).
766 self._reorder = False
772 self._reorder = False
767
773
768 def deltaparent(self, revlog, rev, p1, p2, prev):
774 def deltaparent(self, revlog, rev, p1, p2, prev):
769 dp = revlog.deltaparent(rev)
775 dp = revlog.deltaparent(rev)
770 # avoid storing full revisions; pick prev in those cases
776 # avoid storing full revisions; pick prev in those cases
771 # also pick prev when we can't be sure remote has dp
777 # also pick prev when we can't be sure remote has dp
772 if dp == nullrev or (dp != p1 and dp != p2 and dp != prev):
778 if dp == nullrev or (dp != p1 and dp != p2 and dp != prev):
773 return prev
779 return prev
774 return dp
780 return dp
775
781
776 def builddeltaheader(self, node, p1n, p2n, basenode, linknode):
782 def builddeltaheader(self, node, p1n, p2n, basenode, linknode):
777 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
783 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
778
784
779 packermap = {'01': (cg1packer, cg1unpacker),
785 packermap = {'01': (cg1packer, cg1unpacker),
780 # cg2 adds support for exchanging generaldelta
786 # cg2 adds support for exchanging generaldelta
781 '02': (cg2packer, cg2unpacker),
787 '02': (cg2packer, cg2unpacker),
782 }
788 }
783
789
784 def _changegroupinfo(repo, nodes, source):
790 def _changegroupinfo(repo, nodes, source):
785 if repo.ui.verbose or source == 'bundle':
791 if repo.ui.verbose or source == 'bundle':
786 repo.ui.status(_("%d changesets found\n") % len(nodes))
792 repo.ui.status(_("%d changesets found\n") % len(nodes))
787 if repo.ui.debugflag:
793 if repo.ui.debugflag:
788 repo.ui.debug("list of changesets:\n")
794 repo.ui.debug("list of changesets:\n")
789 for node in nodes:
795 for node in nodes:
790 repo.ui.debug("%s\n" % hex(node))
796 repo.ui.debug("%s\n" % hex(node))
791
797
792 def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
798 def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
793 repo = repo.unfiltered()
799 repo = repo.unfiltered()
794 commonrevs = outgoing.common
800 commonrevs = outgoing.common
795 csets = outgoing.missing
801 csets = outgoing.missing
796 heads = outgoing.missingheads
802 heads = outgoing.missingheads
797 # We go through the fast path if we get told to, or if all (unfiltered
803 # We go through the fast path if we get told to, or if all (unfiltered
798 # heads have been requested (since we then know there all linkrevs will
804 # heads have been requested (since we then know there all linkrevs will
799 # be pulled by the client).
805 # be pulled by the client).
800 heads.sort()
806 heads.sort()
801 fastpathlinkrev = fastpath or (
807 fastpathlinkrev = fastpath or (
802 repo.filtername is None and heads == sorted(repo.heads()))
808 repo.filtername is None and heads == sorted(repo.heads()))
803
809
804 repo.hook('preoutgoing', throw=True, source=source)
810 repo.hook('preoutgoing', throw=True, source=source)
805 _changegroupinfo(repo, csets, source)
811 _changegroupinfo(repo, csets, source)
806 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
812 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
807
813
808 def getsubset(repo, outgoing, bundler, source, fastpath=False):
814 def getsubset(repo, outgoing, bundler, source, fastpath=False):
809 gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
815 gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
810 return packermap[bundler.version][1](util.chunkbuffer(gengroup), None)
816 return packermap[bundler.version][1](util.chunkbuffer(gengroup), None)
811
817
812 def changegroupsubset(repo, roots, heads, source, version='01'):
818 def changegroupsubset(repo, roots, heads, source, version='01'):
813 """Compute a changegroup consisting of all the nodes that are
819 """Compute a changegroup consisting of all the nodes that are
814 descendants of any of the roots and ancestors of any of the heads.
820 descendants of any of the roots and ancestors of any of the heads.
815 Return a chunkbuffer object whose read() method will return
821 Return a chunkbuffer object whose read() method will return
816 successive changegroup chunks.
822 successive changegroup chunks.
817
823
818 It is fairly complex as determining which filenodes and which
824 It is fairly complex as determining which filenodes and which
819 manifest nodes need to be included for the changeset to be complete
825 manifest nodes need to be included for the changeset to be complete
820 is non-trivial.
826 is non-trivial.
821
827
822 Another wrinkle is doing the reverse, figuring out which changeset in
828 Another wrinkle is doing the reverse, figuring out which changeset in
823 the changegroup a particular filenode or manifestnode belongs to.
829 the changegroup a particular filenode or manifestnode belongs to.
824 """
830 """
825 cl = repo.changelog
831 cl = repo.changelog
826 if not roots:
832 if not roots:
827 roots = [nullid]
833 roots = [nullid]
828 discbases = []
834 discbases = []
829 for n in roots:
835 for n in roots:
830 discbases.extend([p for p in cl.parents(n) if p != nullid])
836 discbases.extend([p for p in cl.parents(n) if p != nullid])
831 # TODO: remove call to nodesbetween.
837 # TODO: remove call to nodesbetween.
832 csets, roots, heads = cl.nodesbetween(roots, heads)
838 csets, roots, heads = cl.nodesbetween(roots, heads)
833 included = set(csets)
839 included = set(csets)
834 discbases = [n for n in discbases if n not in included]
840 discbases = [n for n in discbases if n not in included]
835 outgoing = discovery.outgoing(cl, discbases, heads)
841 outgoing = discovery.outgoing(cl, discbases, heads)
836 bundler = packermap[version][0](repo)
842 bundler = packermap[version][0](repo)
837 return getsubset(repo, outgoing, bundler, source)
843 return getsubset(repo, outgoing, bundler, source)
838
844
839 def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
845 def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
840 version='01'):
846 version='01'):
841 """Like getbundle, but taking a discovery.outgoing as an argument.
847 """Like getbundle, but taking a discovery.outgoing as an argument.
842
848
843 This is only implemented for local repos and reuses potentially
849 This is only implemented for local repos and reuses potentially
844 precomputed sets in outgoing. Returns a raw changegroup generator."""
850 precomputed sets in outgoing. Returns a raw changegroup generator."""
845 if not outgoing.missing:
851 if not outgoing.missing:
846 return None
852 return None
847 bundler = packermap[version][0](repo, bundlecaps)
853 bundler = packermap[version][0](repo, bundlecaps)
848 return getsubsetraw(repo, outgoing, bundler, source)
854 return getsubsetraw(repo, outgoing, bundler, source)
849
855
850 def getlocalchangegroup(repo, source, outgoing, bundlecaps=None,
856 def getlocalchangegroup(repo, source, outgoing, bundlecaps=None,
851 version='01'):
857 version='01'):
852 """Like getbundle, but taking a discovery.outgoing as an argument.
858 """Like getbundle, but taking a discovery.outgoing as an argument.
853
859
854 This is only implemented for local repos and reuses potentially
860 This is only implemented for local repos and reuses potentially
855 precomputed sets in outgoing."""
861 precomputed sets in outgoing."""
856 if not outgoing.missing:
862 if not outgoing.missing:
857 return None
863 return None
858 bundler = packermap[version][0](repo, bundlecaps)
864 bundler = packermap[version][0](repo, bundlecaps)
859 return getsubset(repo, outgoing, bundler, source)
865 return getsubset(repo, outgoing, bundler, source)
860
866
861 def computeoutgoing(repo, heads, common):
867 def computeoutgoing(repo, heads, common):
862 """Computes which revs are outgoing given a set of common
868 """Computes which revs are outgoing given a set of common
863 and a set of heads.
869 and a set of heads.
864
870
865 This is a separate function so extensions can have access to
871 This is a separate function so extensions can have access to
866 the logic.
872 the logic.
867
873
868 Returns a discovery.outgoing object.
874 Returns a discovery.outgoing object.
869 """
875 """
870 cl = repo.changelog
876 cl = repo.changelog
871 if common:
877 if common:
872 hasnode = cl.hasnode
878 hasnode = cl.hasnode
873 common = [n for n in common if hasnode(n)]
879 common = [n for n in common if hasnode(n)]
874 else:
880 else:
875 common = [nullid]
881 common = [nullid]
876 if not heads:
882 if not heads:
877 heads = cl.heads()
883 heads = cl.heads()
878 return discovery.outgoing(cl, common, heads)
884 return discovery.outgoing(cl, common, heads)
879
885
880 def getchangegroup(repo, source, heads=None, common=None, bundlecaps=None,
886 def getchangegroup(repo, source, heads=None, common=None, bundlecaps=None,
881 version='01'):
887 version='01'):
882 """Like changegroupsubset, but returns the set difference between the
888 """Like changegroupsubset, but returns the set difference between the
883 ancestors of heads and the ancestors common.
889 ancestors of heads and the ancestors common.
884
890
885 If heads is None, use the local heads. If common is None, use [nullid].
891 If heads is None, use the local heads. If common is None, use [nullid].
886
892
887 The nodes in common might not all be known locally due to the way the
893 The nodes in common might not all be known locally due to the way the
888 current discovery protocol works.
894 current discovery protocol works.
889 """
895 """
890 outgoing = computeoutgoing(repo, heads, common)
896 outgoing = computeoutgoing(repo, heads, common)
891 return getlocalchangegroup(repo, source, outgoing, bundlecaps=bundlecaps,
897 return getlocalchangegroup(repo, source, outgoing, bundlecaps=bundlecaps,
892 version=version)
898 version=version)
893
899
894 def changegroup(repo, basenodes, source):
900 def changegroup(repo, basenodes, source):
895 # to avoid a race we use changegroupsubset() (issue1320)
901 # to avoid a race we use changegroupsubset() (issue1320)
896 return changegroupsubset(repo, basenodes, repo.heads(), source)
902 return changegroupsubset(repo, basenodes, repo.heads(), source)
897
903
898 def _addchangegroupfiles(repo, source, revmap, trp, pr, needfiles):
904 def _addchangegroupfiles(repo, source, revmap, trp, pr, needfiles):
899 revisions = 0
905 revisions = 0
900 files = 0
906 files = 0
901 while True:
907 while True:
902 chunkdata = source.filelogheader()
908 chunkdata = source.filelogheader()
903 if not chunkdata:
909 if not chunkdata:
904 break
910 break
905 f = chunkdata["filename"]
911 f = chunkdata["filename"]
906 repo.ui.debug("adding %s revisions\n" % f)
912 repo.ui.debug("adding %s revisions\n" % f)
907 pr()
913 pr()
908 fl = repo.file(f)
914 fl = repo.file(f)
909 o = len(fl)
915 o = len(fl)
910 try:
916 try:
911 if not fl.addgroup(source, revmap, trp):
917 if not fl.addgroup(source, revmap, trp):
912 raise error.Abort(_("received file revlog group is empty"))
918 raise error.Abort(_("received file revlog group is empty"))
913 except error.CensoredBaseError as e:
919 except error.CensoredBaseError as e:
914 raise error.Abort(_("received delta base is censored: %s") % e)
920 raise error.Abort(_("received delta base is censored: %s") % e)
915 revisions += len(fl) - o
921 revisions += len(fl) - o
916 files += 1
922 files += 1
917 if f in needfiles:
923 if f in needfiles:
918 needs = needfiles[f]
924 needs = needfiles[f]
919 for new in xrange(o, len(fl)):
925 for new in xrange(o, len(fl)):
920 n = fl.node(new)
926 n = fl.node(new)
921 if n in needs:
927 if n in needs:
922 needs.remove(n)
928 needs.remove(n)
923 else:
929 else:
924 raise error.Abort(
930 raise error.Abort(
925 _("received spurious file revlog entry"))
931 _("received spurious file revlog entry"))
926 if not needs:
932 if not needs:
927 del needfiles[f]
933 del needfiles[f]
928 repo.ui.progress(_('files'), None)
934 repo.ui.progress(_('files'), None)
929
935
930 for f, needs in needfiles.iteritems():
936 for f, needs in needfiles.iteritems():
931 fl = repo.file(f)
937 fl = repo.file(f)
932 for n in needs:
938 for n in needs:
933 try:
939 try:
934 fl.rev(n)
940 fl.rev(n)
935 except error.LookupError:
941 except error.LookupError:
936 raise error.Abort(
942 raise error.Abort(
937 _('missing file data for %s:%s - run hg verify') %
943 _('missing file data for %s:%s - run hg verify') %
938 (f, hex(n)))
944 (f, hex(n)))
939
945
940 return revisions, files
946 return revisions, files
General Comments 0
You need to be logged in to leave comments. Login now