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