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