##// END OF EJS Templates
changegroup: pass state into _revisiondeltanarrow...
Gregory Szorc -
r39039:58377518 default
parent child Browse files
Show More
@@ -1,1421 +1,1423 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 weakref
12 import weakref
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 hex,
16 hex,
17 nullid,
17 nullid,
18 nullrev,
18 nullrev,
19 short,
19 short,
20 )
20 )
21
21
22 from .thirdparty import (
22 from .thirdparty import (
23 attr,
23 attr,
24 )
24 )
25
25
26 from . import (
26 from . import (
27 dagutil,
27 dagutil,
28 error,
28 error,
29 manifest,
29 manifest,
30 match as matchmod,
30 match as matchmod,
31 mdiff,
31 mdiff,
32 phases,
32 phases,
33 pycompat,
33 pycompat,
34 repository,
34 repository,
35 revlog,
35 revlog,
36 util,
36 util,
37 )
37 )
38
38
39 from .utils import (
39 from .utils import (
40 stringutil,
40 stringutil,
41 )
41 )
42
42
43 _CHANGEGROUPV1_DELTA_HEADER = struct.Struct("20s20s20s20s")
43 _CHANGEGROUPV1_DELTA_HEADER = struct.Struct("20s20s20s20s")
44 _CHANGEGROUPV2_DELTA_HEADER = struct.Struct("20s20s20s20s20s")
44 _CHANGEGROUPV2_DELTA_HEADER = struct.Struct("20s20s20s20s20s")
45 _CHANGEGROUPV3_DELTA_HEADER = struct.Struct(">20s20s20s20s20sH")
45 _CHANGEGROUPV3_DELTA_HEADER = struct.Struct(">20s20s20s20s20sH")
46
46
47 LFS_REQUIREMENT = 'lfs'
47 LFS_REQUIREMENT = 'lfs'
48
48
49 readexactly = util.readexactly
49 readexactly = util.readexactly
50
50
51 def getchunk(stream):
51 def getchunk(stream):
52 """return the next chunk from stream as a string"""
52 """return the next chunk from stream as a string"""
53 d = readexactly(stream, 4)
53 d = readexactly(stream, 4)
54 l = struct.unpack(">l", d)[0]
54 l = struct.unpack(">l", d)[0]
55 if l <= 4:
55 if l <= 4:
56 if l:
56 if l:
57 raise error.Abort(_("invalid chunk length %d") % l)
57 raise error.Abort(_("invalid chunk length %d") % l)
58 return ""
58 return ""
59 return readexactly(stream, l - 4)
59 return readexactly(stream, l - 4)
60
60
61 def chunkheader(length):
61 def chunkheader(length):
62 """return a changegroup chunk header (string)"""
62 """return a changegroup chunk header (string)"""
63 return struct.pack(">l", length + 4)
63 return struct.pack(">l", length + 4)
64
64
65 def closechunk():
65 def closechunk():
66 """return a changegroup chunk header (string) for a zero-length chunk"""
66 """return a changegroup chunk header (string) for a zero-length chunk"""
67 return struct.pack(">l", 0)
67 return struct.pack(">l", 0)
68
68
69 def _fileheader(path):
69 def _fileheader(path):
70 """Obtain a changegroup chunk header for a named path."""
70 """Obtain a changegroup chunk header for a named path."""
71 return chunkheader(len(path)) + path
71 return chunkheader(len(path)) + path
72
72
73 def writechunks(ui, chunks, filename, vfs=None):
73 def writechunks(ui, chunks, filename, vfs=None):
74 """Write chunks to a file and return its filename.
74 """Write chunks to a file and return its filename.
75
75
76 The stream is assumed to be a bundle file.
76 The stream is assumed to be a bundle file.
77 Existing files will not be overwritten.
77 Existing files will not be overwritten.
78 If no filename is specified, a temporary file is created.
78 If no filename is specified, a temporary file is created.
79 """
79 """
80 fh = None
80 fh = None
81 cleanup = None
81 cleanup = None
82 try:
82 try:
83 if filename:
83 if filename:
84 if vfs:
84 if vfs:
85 fh = vfs.open(filename, "wb")
85 fh = vfs.open(filename, "wb")
86 else:
86 else:
87 # Increase default buffer size because default is usually
87 # Increase default buffer size because default is usually
88 # small (4k is common on Linux).
88 # small (4k is common on Linux).
89 fh = open(filename, "wb", 131072)
89 fh = open(filename, "wb", 131072)
90 else:
90 else:
91 fd, filename = pycompat.mkstemp(prefix="hg-bundle-", suffix=".hg")
91 fd, filename = pycompat.mkstemp(prefix="hg-bundle-", suffix=".hg")
92 fh = os.fdopen(fd, r"wb")
92 fh = os.fdopen(fd, r"wb")
93 cleanup = filename
93 cleanup = filename
94 for c in chunks:
94 for c in chunks:
95 fh.write(c)
95 fh.write(c)
96 cleanup = None
96 cleanup = None
97 return filename
97 return filename
98 finally:
98 finally:
99 if fh is not None:
99 if fh is not None:
100 fh.close()
100 fh.close()
101 if cleanup is not None:
101 if cleanup is not None:
102 if filename and vfs:
102 if filename and vfs:
103 vfs.unlink(cleanup)
103 vfs.unlink(cleanup)
104 else:
104 else:
105 os.unlink(cleanup)
105 os.unlink(cleanup)
106
106
107 class cg1unpacker(object):
107 class cg1unpacker(object):
108 """Unpacker for cg1 changegroup streams.
108 """Unpacker for cg1 changegroup streams.
109
109
110 A changegroup unpacker handles the framing of the revision data in
110 A changegroup unpacker handles the framing of the revision data in
111 the wire format. Most consumers will want to use the apply()
111 the wire format. Most consumers will want to use the apply()
112 method to add the changes from the changegroup to a repository.
112 method to add the changes from the changegroup to a repository.
113
113
114 If you're forwarding a changegroup unmodified to another consumer,
114 If you're forwarding a changegroup unmodified to another consumer,
115 use getchunks(), which returns an iterator of changegroup
115 use getchunks(), which returns an iterator of changegroup
116 chunks. This is mostly useful for cases where you need to know the
116 chunks. This is mostly useful for cases where you need to know the
117 data stream has ended by observing the end of the changegroup.
117 data stream has ended by observing the end of the changegroup.
118
118
119 deltachunk() is useful only if you're applying delta data. Most
119 deltachunk() is useful only if you're applying delta data. Most
120 consumers should prefer apply() instead.
120 consumers should prefer apply() instead.
121
121
122 A few other public methods exist. Those are used only for
122 A few other public methods exist. Those are used only for
123 bundlerepo and some debug commands - their use is discouraged.
123 bundlerepo and some debug commands - their use is discouraged.
124 """
124 """
125 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
125 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
126 deltaheadersize = deltaheader.size
126 deltaheadersize = deltaheader.size
127 version = '01'
127 version = '01'
128 _grouplistcount = 1 # One list of files after the manifests
128 _grouplistcount = 1 # One list of files after the manifests
129
129
130 def __init__(self, fh, alg, extras=None):
130 def __init__(self, fh, alg, extras=None):
131 if alg is None:
131 if alg is None:
132 alg = 'UN'
132 alg = 'UN'
133 if alg not in util.compengines.supportedbundletypes:
133 if alg not in util.compengines.supportedbundletypes:
134 raise error.Abort(_('unknown stream compression type: %s')
134 raise error.Abort(_('unknown stream compression type: %s')
135 % alg)
135 % alg)
136 if alg == 'BZ':
136 if alg == 'BZ':
137 alg = '_truncatedBZ'
137 alg = '_truncatedBZ'
138
138
139 compengine = util.compengines.forbundletype(alg)
139 compengine = util.compengines.forbundletype(alg)
140 self._stream = compengine.decompressorreader(fh)
140 self._stream = compengine.decompressorreader(fh)
141 self._type = alg
141 self._type = alg
142 self.extras = extras or {}
142 self.extras = extras or {}
143 self.callback = None
143 self.callback = None
144
144
145 # These methods (compressed, read, seek, tell) all appear to only
145 # These methods (compressed, read, seek, tell) all appear to only
146 # be used by bundlerepo, but it's a little hard to tell.
146 # be used by bundlerepo, but it's a little hard to tell.
147 def compressed(self):
147 def compressed(self):
148 return self._type is not None and self._type != 'UN'
148 return self._type is not None and self._type != 'UN'
149 def read(self, l):
149 def read(self, l):
150 return self._stream.read(l)
150 return self._stream.read(l)
151 def seek(self, pos):
151 def seek(self, pos):
152 return self._stream.seek(pos)
152 return self._stream.seek(pos)
153 def tell(self):
153 def tell(self):
154 return self._stream.tell()
154 return self._stream.tell()
155 def close(self):
155 def close(self):
156 return self._stream.close()
156 return self._stream.close()
157
157
158 def _chunklength(self):
158 def _chunklength(self):
159 d = readexactly(self._stream, 4)
159 d = readexactly(self._stream, 4)
160 l = struct.unpack(">l", d)[0]
160 l = struct.unpack(">l", d)[0]
161 if l <= 4:
161 if l <= 4:
162 if l:
162 if l:
163 raise error.Abort(_("invalid chunk length %d") % l)
163 raise error.Abort(_("invalid chunk length %d") % l)
164 return 0
164 return 0
165 if self.callback:
165 if self.callback:
166 self.callback()
166 self.callback()
167 return l - 4
167 return l - 4
168
168
169 def changelogheader(self):
169 def changelogheader(self):
170 """v10 does not have a changelog header chunk"""
170 """v10 does not have a changelog header chunk"""
171 return {}
171 return {}
172
172
173 def manifestheader(self):
173 def manifestheader(self):
174 """v10 does not have a manifest header chunk"""
174 """v10 does not have a manifest header chunk"""
175 return {}
175 return {}
176
176
177 def filelogheader(self):
177 def filelogheader(self):
178 """return the header of the filelogs chunk, v10 only has the filename"""
178 """return the header of the filelogs chunk, v10 only has the filename"""
179 l = self._chunklength()
179 l = self._chunklength()
180 if not l:
180 if not l:
181 return {}
181 return {}
182 fname = readexactly(self._stream, l)
182 fname = readexactly(self._stream, l)
183 return {'filename': fname}
183 return {'filename': fname}
184
184
185 def _deltaheader(self, headertuple, prevnode):
185 def _deltaheader(self, headertuple, prevnode):
186 node, p1, p2, cs = headertuple
186 node, p1, p2, cs = headertuple
187 if prevnode is None:
187 if prevnode is None:
188 deltabase = p1
188 deltabase = p1
189 else:
189 else:
190 deltabase = prevnode
190 deltabase = prevnode
191 flags = 0
191 flags = 0
192 return node, p1, p2, deltabase, cs, flags
192 return node, p1, p2, deltabase, cs, flags
193
193
194 def deltachunk(self, prevnode):
194 def deltachunk(self, prevnode):
195 l = self._chunklength()
195 l = self._chunklength()
196 if not l:
196 if not l:
197 return {}
197 return {}
198 headerdata = readexactly(self._stream, self.deltaheadersize)
198 headerdata = readexactly(self._stream, self.deltaheadersize)
199 header = self.deltaheader.unpack(headerdata)
199 header = self.deltaheader.unpack(headerdata)
200 delta = readexactly(self._stream, l - self.deltaheadersize)
200 delta = readexactly(self._stream, l - self.deltaheadersize)
201 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
201 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
202 return (node, p1, p2, cs, deltabase, delta, flags)
202 return (node, p1, p2, cs, deltabase, delta, flags)
203
203
204 def getchunks(self):
204 def getchunks(self):
205 """returns all the chunks contains in the bundle
205 """returns all the chunks contains in the bundle
206
206
207 Used when you need to forward the binary stream to a file or another
207 Used when you need to forward the binary stream to a file or another
208 network API. To do so, it parse the changegroup data, otherwise it will
208 network API. To do so, it parse the changegroup data, otherwise it will
209 block in case of sshrepo because it don't know the end of the stream.
209 block in case of sshrepo because it don't know the end of the stream.
210 """
210 """
211 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
211 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
212 # and a list of filelogs. For changegroup 3, we expect 4 parts:
212 # and a list of filelogs. For changegroup 3, we expect 4 parts:
213 # changelog, manifestlog, a list of tree manifestlogs, and a list of
213 # changelog, manifestlog, a list of tree manifestlogs, and a list of
214 # filelogs.
214 # filelogs.
215 #
215 #
216 # Changelog and manifestlog parts are terminated with empty chunks. The
216 # Changelog and manifestlog parts are terminated with empty chunks. The
217 # tree and file parts are a list of entry sections. Each entry section
217 # tree and file parts are a list of entry sections. Each entry section
218 # is a series of chunks terminating in an empty chunk. The list of these
218 # is a series of chunks terminating in an empty chunk. The list of these
219 # entry sections is terminated in yet another empty chunk, so we know
219 # entry sections is terminated in yet another empty chunk, so we know
220 # we've reached the end of the tree/file list when we reach an empty
220 # we've reached the end of the tree/file list when we reach an empty
221 # chunk that was proceeded by no non-empty chunks.
221 # chunk that was proceeded by no non-empty chunks.
222
222
223 parts = 0
223 parts = 0
224 while parts < 2 + self._grouplistcount:
224 while parts < 2 + self._grouplistcount:
225 noentries = True
225 noentries = True
226 while True:
226 while True:
227 chunk = getchunk(self)
227 chunk = getchunk(self)
228 if not chunk:
228 if not chunk:
229 # The first two empty chunks represent the end of the
229 # The first two empty chunks represent the end of the
230 # changelog and the manifestlog portions. The remaining
230 # changelog and the manifestlog portions. The remaining
231 # empty chunks represent either A) the end of individual
231 # empty chunks represent either A) the end of individual
232 # tree or file entries in the file list, or B) the end of
232 # tree or file entries in the file list, or B) the end of
233 # the entire list. It's the end of the entire list if there
233 # the entire list. It's the end of the entire list if there
234 # were no entries (i.e. noentries is True).
234 # were no entries (i.e. noentries is True).
235 if parts < 2:
235 if parts < 2:
236 parts += 1
236 parts += 1
237 elif noentries:
237 elif noentries:
238 parts += 1
238 parts += 1
239 break
239 break
240 noentries = False
240 noentries = False
241 yield chunkheader(len(chunk))
241 yield chunkheader(len(chunk))
242 pos = 0
242 pos = 0
243 while pos < len(chunk):
243 while pos < len(chunk):
244 next = pos + 2**20
244 next = pos + 2**20
245 yield chunk[pos:next]
245 yield chunk[pos:next]
246 pos = next
246 pos = next
247 yield closechunk()
247 yield closechunk()
248
248
249 def _unpackmanifests(self, repo, revmap, trp, prog):
249 def _unpackmanifests(self, repo, revmap, trp, prog):
250 self.callback = prog.increment
250 self.callback = prog.increment
251 # no need to check for empty manifest group here:
251 # no need to check for empty manifest group here:
252 # if the result of the merge of 1 and 2 is the same in 3 and 4,
252 # if the result of the merge of 1 and 2 is the same in 3 and 4,
253 # no new manifest will be created and the manifest group will
253 # no new manifest will be created and the manifest group will
254 # be empty during the pull
254 # be empty during the pull
255 self.manifestheader()
255 self.manifestheader()
256 deltas = self.deltaiter()
256 deltas = self.deltaiter()
257 repo.manifestlog.addgroup(deltas, revmap, trp)
257 repo.manifestlog.addgroup(deltas, revmap, trp)
258 prog.complete()
258 prog.complete()
259 self.callback = None
259 self.callback = None
260
260
261 def apply(self, repo, tr, srctype, url, targetphase=phases.draft,
261 def apply(self, repo, tr, srctype, url, targetphase=phases.draft,
262 expectedtotal=None):
262 expectedtotal=None):
263 """Add the changegroup returned by source.read() to this repo.
263 """Add the changegroup returned by source.read() to this repo.
264 srctype is a string like 'push', 'pull', or 'unbundle'. url is
264 srctype is a string like 'push', 'pull', or 'unbundle'. url is
265 the URL of the repo where this changegroup is coming from.
265 the URL of the repo where this changegroup is coming from.
266
266
267 Return an integer summarizing the change to this repo:
267 Return an integer summarizing the change to this repo:
268 - nothing changed or no source: 0
268 - nothing changed or no source: 0
269 - more heads than before: 1+added heads (2..n)
269 - more heads than before: 1+added heads (2..n)
270 - fewer heads than before: -1-removed heads (-2..-n)
270 - fewer heads than before: -1-removed heads (-2..-n)
271 - number of heads stays the same: 1
271 - number of heads stays the same: 1
272 """
272 """
273 repo = repo.unfiltered()
273 repo = repo.unfiltered()
274 def csmap(x):
274 def csmap(x):
275 repo.ui.debug("add changeset %s\n" % short(x))
275 repo.ui.debug("add changeset %s\n" % short(x))
276 return len(cl)
276 return len(cl)
277
277
278 def revmap(x):
278 def revmap(x):
279 return cl.rev(x)
279 return cl.rev(x)
280
280
281 changesets = files = revisions = 0
281 changesets = files = revisions = 0
282
282
283 try:
283 try:
284 # The transaction may already carry source information. In this
284 # The transaction may already carry source information. In this
285 # case we use the top level data. We overwrite the argument
285 # case we use the top level data. We overwrite the argument
286 # because we need to use the top level value (if they exist)
286 # because we need to use the top level value (if they exist)
287 # in this function.
287 # in this function.
288 srctype = tr.hookargs.setdefault('source', srctype)
288 srctype = tr.hookargs.setdefault('source', srctype)
289 url = tr.hookargs.setdefault('url', url)
289 url = tr.hookargs.setdefault('url', url)
290 repo.hook('prechangegroup',
290 repo.hook('prechangegroup',
291 throw=True, **pycompat.strkwargs(tr.hookargs))
291 throw=True, **pycompat.strkwargs(tr.hookargs))
292
292
293 # write changelog data to temp files so concurrent readers
293 # write changelog data to temp files so concurrent readers
294 # will not see an inconsistent view
294 # will not see an inconsistent view
295 cl = repo.changelog
295 cl = repo.changelog
296 cl.delayupdate(tr)
296 cl.delayupdate(tr)
297 oldheads = set(cl.heads())
297 oldheads = set(cl.heads())
298
298
299 trp = weakref.proxy(tr)
299 trp = weakref.proxy(tr)
300 # pull off the changeset group
300 # pull off the changeset group
301 repo.ui.status(_("adding changesets\n"))
301 repo.ui.status(_("adding changesets\n"))
302 clstart = len(cl)
302 clstart = len(cl)
303 progress = repo.ui.makeprogress(_('changesets'), unit=_('chunks'),
303 progress = repo.ui.makeprogress(_('changesets'), unit=_('chunks'),
304 total=expectedtotal)
304 total=expectedtotal)
305 self.callback = progress.increment
305 self.callback = progress.increment
306
306
307 efiles = set()
307 efiles = set()
308 def onchangelog(cl, node):
308 def onchangelog(cl, node):
309 efiles.update(cl.readfiles(node))
309 efiles.update(cl.readfiles(node))
310
310
311 self.changelogheader()
311 self.changelogheader()
312 deltas = self.deltaiter()
312 deltas = self.deltaiter()
313 cgnodes = cl.addgroup(deltas, csmap, trp, addrevisioncb=onchangelog)
313 cgnodes = cl.addgroup(deltas, csmap, trp, addrevisioncb=onchangelog)
314 efiles = len(efiles)
314 efiles = len(efiles)
315
315
316 if not cgnodes:
316 if not cgnodes:
317 repo.ui.develwarn('applied empty changegroup',
317 repo.ui.develwarn('applied empty changegroup',
318 config='warn-empty-changegroup')
318 config='warn-empty-changegroup')
319 clend = len(cl)
319 clend = len(cl)
320 changesets = clend - clstart
320 changesets = clend - clstart
321 progress.complete()
321 progress.complete()
322 self.callback = None
322 self.callback = None
323
323
324 # pull off the manifest group
324 # pull off the manifest group
325 repo.ui.status(_("adding manifests\n"))
325 repo.ui.status(_("adding manifests\n"))
326 # We know that we'll never have more manifests than we had
326 # We know that we'll never have more manifests than we had
327 # changesets.
327 # changesets.
328 progress = repo.ui.makeprogress(_('manifests'), unit=_('chunks'),
328 progress = repo.ui.makeprogress(_('manifests'), unit=_('chunks'),
329 total=changesets)
329 total=changesets)
330 self._unpackmanifests(repo, revmap, trp, progress)
330 self._unpackmanifests(repo, revmap, trp, progress)
331
331
332 needfiles = {}
332 needfiles = {}
333 if repo.ui.configbool('server', 'validate'):
333 if repo.ui.configbool('server', 'validate'):
334 cl = repo.changelog
334 cl = repo.changelog
335 ml = repo.manifestlog
335 ml = repo.manifestlog
336 # validate incoming csets have their manifests
336 # validate incoming csets have their manifests
337 for cset in pycompat.xrange(clstart, clend):
337 for cset in pycompat.xrange(clstart, clend):
338 mfnode = cl.changelogrevision(cset).manifest
338 mfnode = cl.changelogrevision(cset).manifest
339 mfest = ml[mfnode].readdelta()
339 mfest = ml[mfnode].readdelta()
340 # store file cgnodes we must see
340 # store file cgnodes we must see
341 for f, n in mfest.iteritems():
341 for f, n in mfest.iteritems():
342 needfiles.setdefault(f, set()).add(n)
342 needfiles.setdefault(f, set()).add(n)
343
343
344 # process the files
344 # process the files
345 repo.ui.status(_("adding file changes\n"))
345 repo.ui.status(_("adding file changes\n"))
346 newrevs, newfiles = _addchangegroupfiles(
346 newrevs, newfiles = _addchangegroupfiles(
347 repo, self, revmap, trp, efiles, needfiles)
347 repo, self, revmap, trp, efiles, needfiles)
348 revisions += newrevs
348 revisions += newrevs
349 files += newfiles
349 files += newfiles
350
350
351 deltaheads = 0
351 deltaheads = 0
352 if oldheads:
352 if oldheads:
353 heads = cl.heads()
353 heads = cl.heads()
354 deltaheads = len(heads) - len(oldheads)
354 deltaheads = len(heads) - len(oldheads)
355 for h in heads:
355 for h in heads:
356 if h not in oldheads and repo[h].closesbranch():
356 if h not in oldheads and repo[h].closesbranch():
357 deltaheads -= 1
357 deltaheads -= 1
358 htext = ""
358 htext = ""
359 if deltaheads:
359 if deltaheads:
360 htext = _(" (%+d heads)") % deltaheads
360 htext = _(" (%+d heads)") % deltaheads
361
361
362 repo.ui.status(_("added %d changesets"
362 repo.ui.status(_("added %d changesets"
363 " with %d changes to %d files%s\n")
363 " with %d changes to %d files%s\n")
364 % (changesets, revisions, files, htext))
364 % (changesets, revisions, files, htext))
365 repo.invalidatevolatilesets()
365 repo.invalidatevolatilesets()
366
366
367 if changesets > 0:
367 if changesets > 0:
368 if 'node' not in tr.hookargs:
368 if 'node' not in tr.hookargs:
369 tr.hookargs['node'] = hex(cl.node(clstart))
369 tr.hookargs['node'] = hex(cl.node(clstart))
370 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
370 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
371 hookargs = dict(tr.hookargs)
371 hookargs = dict(tr.hookargs)
372 else:
372 else:
373 hookargs = dict(tr.hookargs)
373 hookargs = dict(tr.hookargs)
374 hookargs['node'] = hex(cl.node(clstart))
374 hookargs['node'] = hex(cl.node(clstart))
375 hookargs['node_last'] = hex(cl.node(clend - 1))
375 hookargs['node_last'] = hex(cl.node(clend - 1))
376 repo.hook('pretxnchangegroup',
376 repo.hook('pretxnchangegroup',
377 throw=True, **pycompat.strkwargs(hookargs))
377 throw=True, **pycompat.strkwargs(hookargs))
378
378
379 added = [cl.node(r) for r in pycompat.xrange(clstart, clend)]
379 added = [cl.node(r) for r in pycompat.xrange(clstart, clend)]
380 phaseall = None
380 phaseall = None
381 if srctype in ('push', 'serve'):
381 if srctype in ('push', 'serve'):
382 # Old servers can not push the boundary themselves.
382 # Old servers can not push the boundary themselves.
383 # New servers won't push the boundary if changeset already
383 # New servers won't push the boundary if changeset already
384 # exists locally as secret
384 # exists locally as secret
385 #
385 #
386 # We should not use added here but the list of all change in
386 # We should not use added here but the list of all change in
387 # the bundle
387 # the bundle
388 if repo.publishing():
388 if repo.publishing():
389 targetphase = phaseall = phases.public
389 targetphase = phaseall = phases.public
390 else:
390 else:
391 # closer target phase computation
391 # closer target phase computation
392
392
393 # Those changesets have been pushed from the
393 # Those changesets have been pushed from the
394 # outside, their phases are going to be pushed
394 # outside, their phases are going to be pushed
395 # alongside. Therefor `targetphase` is
395 # alongside. Therefor `targetphase` is
396 # ignored.
396 # ignored.
397 targetphase = phaseall = phases.draft
397 targetphase = phaseall = phases.draft
398 if added:
398 if added:
399 phases.registernew(repo, tr, targetphase, added)
399 phases.registernew(repo, tr, targetphase, added)
400 if phaseall is not None:
400 if phaseall is not None:
401 phases.advanceboundary(repo, tr, phaseall, cgnodes)
401 phases.advanceboundary(repo, tr, phaseall, cgnodes)
402
402
403 if changesets > 0:
403 if changesets > 0:
404
404
405 def runhooks():
405 def runhooks():
406 # These hooks run when the lock releases, not when the
406 # These hooks run when the lock releases, not when the
407 # transaction closes. So it's possible for the changelog
407 # transaction closes. So it's possible for the changelog
408 # to have changed since we last saw it.
408 # to have changed since we last saw it.
409 if clstart >= len(repo):
409 if clstart >= len(repo):
410 return
410 return
411
411
412 repo.hook("changegroup", **pycompat.strkwargs(hookargs))
412 repo.hook("changegroup", **pycompat.strkwargs(hookargs))
413
413
414 for n in added:
414 for n in added:
415 args = hookargs.copy()
415 args = hookargs.copy()
416 args['node'] = hex(n)
416 args['node'] = hex(n)
417 del args['node_last']
417 del args['node_last']
418 repo.hook("incoming", **pycompat.strkwargs(args))
418 repo.hook("incoming", **pycompat.strkwargs(args))
419
419
420 newheads = [h for h in repo.heads()
420 newheads = [h for h in repo.heads()
421 if h not in oldheads]
421 if h not in oldheads]
422 repo.ui.log("incoming",
422 repo.ui.log("incoming",
423 "%d incoming changes - new heads: %s\n",
423 "%d incoming changes - new heads: %s\n",
424 len(added),
424 len(added),
425 ', '.join([hex(c[:6]) for c in newheads]))
425 ', '.join([hex(c[:6]) for c in newheads]))
426
426
427 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
427 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
428 lambda tr: repo._afterlock(runhooks))
428 lambda tr: repo._afterlock(runhooks))
429 finally:
429 finally:
430 repo.ui.flush()
430 repo.ui.flush()
431 # never return 0 here:
431 # never return 0 here:
432 if deltaheads < 0:
432 if deltaheads < 0:
433 ret = deltaheads - 1
433 ret = deltaheads - 1
434 else:
434 else:
435 ret = deltaheads + 1
435 ret = deltaheads + 1
436 return ret
436 return ret
437
437
438 def deltaiter(self):
438 def deltaiter(self):
439 """
439 """
440 returns an iterator of the deltas in this changegroup
440 returns an iterator of the deltas in this changegroup
441
441
442 Useful for passing to the underlying storage system to be stored.
442 Useful for passing to the underlying storage system to be stored.
443 """
443 """
444 chain = None
444 chain = None
445 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
445 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
446 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags)
446 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags)
447 yield chunkdata
447 yield chunkdata
448 chain = chunkdata[0]
448 chain = chunkdata[0]
449
449
450 class cg2unpacker(cg1unpacker):
450 class cg2unpacker(cg1unpacker):
451 """Unpacker for cg2 streams.
451 """Unpacker for cg2 streams.
452
452
453 cg2 streams add support for generaldelta, so the delta header
453 cg2 streams add support for generaldelta, so the delta header
454 format is slightly different. All other features about the data
454 format is slightly different. All other features about the data
455 remain the same.
455 remain the same.
456 """
456 """
457 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
457 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
458 deltaheadersize = deltaheader.size
458 deltaheadersize = deltaheader.size
459 version = '02'
459 version = '02'
460
460
461 def _deltaheader(self, headertuple, prevnode):
461 def _deltaheader(self, headertuple, prevnode):
462 node, p1, p2, deltabase, cs = headertuple
462 node, p1, p2, deltabase, cs = headertuple
463 flags = 0
463 flags = 0
464 return node, p1, p2, deltabase, cs, flags
464 return node, p1, p2, deltabase, cs, flags
465
465
466 class cg3unpacker(cg2unpacker):
466 class cg3unpacker(cg2unpacker):
467 """Unpacker for cg3 streams.
467 """Unpacker for cg3 streams.
468
468
469 cg3 streams add support for exchanging treemanifests and revlog
469 cg3 streams add support for exchanging treemanifests and revlog
470 flags. It adds the revlog flags to the delta header and an empty chunk
470 flags. It adds the revlog flags to the delta header and an empty chunk
471 separating manifests and files.
471 separating manifests and files.
472 """
472 """
473 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
473 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
474 deltaheadersize = deltaheader.size
474 deltaheadersize = deltaheader.size
475 version = '03'
475 version = '03'
476 _grouplistcount = 2 # One list of manifests and one list of files
476 _grouplistcount = 2 # One list of manifests and one list of files
477
477
478 def _deltaheader(self, headertuple, prevnode):
478 def _deltaheader(self, headertuple, prevnode):
479 node, p1, p2, deltabase, cs, flags = headertuple
479 node, p1, p2, deltabase, cs, flags = headertuple
480 return node, p1, p2, deltabase, cs, flags
480 return node, p1, p2, deltabase, cs, flags
481
481
482 def _unpackmanifests(self, repo, revmap, trp, prog):
482 def _unpackmanifests(self, repo, revmap, trp, prog):
483 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog)
483 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog)
484 for chunkdata in iter(self.filelogheader, {}):
484 for chunkdata in iter(self.filelogheader, {}):
485 # If we get here, there are directory manifests in the changegroup
485 # If we get here, there are directory manifests in the changegroup
486 d = chunkdata["filename"]
486 d = chunkdata["filename"]
487 repo.ui.debug("adding %s revisions\n" % d)
487 repo.ui.debug("adding %s revisions\n" % d)
488 dirlog = repo.manifestlog._revlog.dirlog(d)
488 dirlog = repo.manifestlog._revlog.dirlog(d)
489 deltas = self.deltaiter()
489 deltas = self.deltaiter()
490 if not dirlog.addgroup(deltas, revmap, trp):
490 if not dirlog.addgroup(deltas, revmap, trp):
491 raise error.Abort(_("received dir revlog group is empty"))
491 raise error.Abort(_("received dir revlog group is empty"))
492
492
493 class headerlessfixup(object):
493 class headerlessfixup(object):
494 def __init__(self, fh, h):
494 def __init__(self, fh, h):
495 self._h = h
495 self._h = h
496 self._fh = fh
496 self._fh = fh
497 def read(self, n):
497 def read(self, n):
498 if self._h:
498 if self._h:
499 d, self._h = self._h[:n], self._h[n:]
499 d, self._h = self._h[:n], self._h[n:]
500 if len(d) < n:
500 if len(d) < n:
501 d += readexactly(self._fh, n - len(d))
501 d += readexactly(self._fh, n - len(d))
502 return d
502 return d
503 return readexactly(self._fh, n)
503 return readexactly(self._fh, n)
504
504
505 @attr.s(slots=True, frozen=True)
505 @attr.s(slots=True, frozen=True)
506 class revisiondelta(object):
506 class revisiondelta(object):
507 """Describes a delta entry in a changegroup.
507 """Describes a delta entry in a changegroup.
508
508
509 Captured data is sufficient to serialize the delta into multiple
509 Captured data is sufficient to serialize the delta into multiple
510 formats.
510 formats.
511 """
511 """
512 # 20 byte node of this revision.
512 # 20 byte node of this revision.
513 node = attr.ib()
513 node = attr.ib()
514 # 20 byte nodes of parent revisions.
514 # 20 byte nodes of parent revisions.
515 p1node = attr.ib()
515 p1node = attr.ib()
516 p2node = attr.ib()
516 p2node = attr.ib()
517 # 20 byte node of node this delta is against.
517 # 20 byte node of node this delta is against.
518 basenode = attr.ib()
518 basenode = attr.ib()
519 # 20 byte node of changeset revision this delta is associated with.
519 # 20 byte node of changeset revision this delta is associated with.
520 linknode = attr.ib()
520 linknode = attr.ib()
521 # 2 bytes of flags to apply to revision data.
521 # 2 bytes of flags to apply to revision data.
522 flags = attr.ib()
522 flags = attr.ib()
523 # Iterable of chunks holding raw delta data.
523 # Iterable of chunks holding raw delta data.
524 deltachunks = attr.ib()
524 deltachunks = attr.ib()
525
525
526 def _sortnodesnormal(store, nodes, reorder):
526 def _sortnodesnormal(store, nodes, reorder):
527 """Sort nodes for changegroup generation and turn into revnums."""
527 """Sort nodes for changegroup generation and turn into revnums."""
528 # for generaldelta revlogs, we linearize the revs; this will both be
528 # for generaldelta revlogs, we linearize the revs; this will both be
529 # much quicker and generate a much smaller bundle
529 # much quicker and generate a much smaller bundle
530 if (store._generaldelta and reorder is None) or reorder:
530 if (store._generaldelta and reorder is None) or reorder:
531 dag = dagutil.revlogdag(store)
531 dag = dagutil.revlogdag(store)
532 return dag.linearize(set(store.rev(n) for n in nodes))
532 return dag.linearize(set(store.rev(n) for n in nodes))
533 else:
533 else:
534 return sorted([store.rev(n) for n in nodes])
534 return sorted([store.rev(n) for n in nodes])
535
535
536 def _sortnodesellipsis(store, nodes, cl, lookup):
536 def _sortnodesellipsis(store, nodes, cl, lookup):
537 """Sort nodes for changegroup generation and turn into revnums."""
537 """Sort nodes for changegroup generation and turn into revnums."""
538 # Ellipses serving mode.
538 # Ellipses serving mode.
539 #
539 #
540 # In a perfect world, we'd generate better ellipsis-ified graphs
540 # In a perfect world, we'd generate better ellipsis-ified graphs
541 # for non-changelog revlogs. In practice, we haven't started doing
541 # for non-changelog revlogs. In practice, we haven't started doing
542 # that yet, so the resulting DAGs for the manifestlog and filelogs
542 # that yet, so the resulting DAGs for the manifestlog and filelogs
543 # are actually full of bogus parentage on all the ellipsis
543 # are actually full of bogus parentage on all the ellipsis
544 # nodes. This has the side effect that, while the contents are
544 # nodes. This has the side effect that, while the contents are
545 # correct, the individual DAGs might be completely out of whack in
545 # correct, the individual DAGs might be completely out of whack in
546 # a case like 882681bc3166 and its ancestors (back about 10
546 # a case like 882681bc3166 and its ancestors (back about 10
547 # revisions or so) in the main hg repo.
547 # revisions or so) in the main hg repo.
548 #
548 #
549 # The one invariant we *know* holds is that the new (potentially
549 # The one invariant we *know* holds is that the new (potentially
550 # bogus) DAG shape will be valid if we order the nodes in the
550 # bogus) DAG shape will be valid if we order the nodes in the
551 # order that they're introduced in dramatis personae by the
551 # order that they're introduced in dramatis personae by the
552 # changelog, so what we do is we sort the non-changelog histories
552 # changelog, so what we do is we sort the non-changelog histories
553 # by the order in which they are used by the changelog.
553 # by the order in which they are used by the changelog.
554 key = lambda n: cl.rev(lookup(n))
554 key = lambda n: cl.rev(lookup(n))
555 return [store.rev(n) for n in sorted(nodes, key=key)]
555 return [store.rev(n) for n in sorted(nodes, key=key)]
556
556
557 def _revisiondeltanormal(store, rev, prev, linknode, deltaparentfn):
557 def _revisiondeltanormal(store, rev, prev, linknode, deltaparentfn):
558 """Construct a revision delta for non-ellipses changegroup generation."""
558 """Construct a revision delta for non-ellipses changegroup generation."""
559 node = store.node(rev)
559 node = store.node(rev)
560 p1, p2 = store.parentrevs(rev)
560 p1, p2 = store.parentrevs(rev)
561 base = deltaparentfn(store, rev, p1, p2, prev)
561 base = deltaparentfn(store, rev, p1, p2, prev)
562
562
563 prefix = ''
563 prefix = ''
564 if store.iscensored(base) or store.iscensored(rev):
564 if store.iscensored(base) or store.iscensored(rev):
565 try:
565 try:
566 delta = store.revision(node, raw=True)
566 delta = store.revision(node, raw=True)
567 except error.CensoredNodeError as e:
567 except error.CensoredNodeError as e:
568 delta = e.tombstone
568 delta = e.tombstone
569 if base == nullrev:
569 if base == nullrev:
570 prefix = mdiff.trivialdiffheader(len(delta))
570 prefix = mdiff.trivialdiffheader(len(delta))
571 else:
571 else:
572 baselen = store.rawsize(base)
572 baselen = store.rawsize(base)
573 prefix = mdiff.replacediffheader(baselen, len(delta))
573 prefix = mdiff.replacediffheader(baselen, len(delta))
574 elif base == nullrev:
574 elif base == nullrev:
575 delta = store.revision(node, raw=True)
575 delta = store.revision(node, raw=True)
576 prefix = mdiff.trivialdiffheader(len(delta))
576 prefix = mdiff.trivialdiffheader(len(delta))
577 else:
577 else:
578 delta = store.revdiff(base, rev)
578 delta = store.revdiff(base, rev)
579 p1n, p2n = store.parents(node)
579 p1n, p2n = store.parents(node)
580
580
581 return revisiondelta(
581 return revisiondelta(
582 node=node,
582 node=node,
583 p1node=p1n,
583 p1node=p1n,
584 p2node=p2n,
584 p2node=p2n,
585 basenode=store.node(base),
585 basenode=store.node(base),
586 linknode=linknode,
586 linknode=linknode,
587 flags=store.flags(rev),
587 flags=store.flags(rev),
588 deltachunks=(prefix, delta),
588 deltachunks=(prefix, delta),
589 )
589 )
590
590
591 class cgpacker(object):
591 class cgpacker(object):
592 def __init__(self, repo, filematcher, version, allowreorder,
592 def __init__(self, repo, filematcher, version, allowreorder,
593 deltaparentfn, builddeltaheader, manifestsend,
593 deltaparentfn, builddeltaheader, manifestsend,
594 bundlecaps=None, ellipses=False,
594 bundlecaps=None, ellipses=False,
595 shallow=False, ellipsisroots=None, fullnodes=None):
595 shallow=False, ellipsisroots=None, fullnodes=None):
596 """Given a source repo, construct a bundler.
596 """Given a source repo, construct a bundler.
597
597
598 filematcher is a matcher that matches on files to include in the
598 filematcher is a matcher that matches on files to include in the
599 changegroup. Used to facilitate sparse changegroups.
599 changegroup. Used to facilitate sparse changegroups.
600
600
601 allowreorder controls whether reordering of revisions is allowed.
601 allowreorder controls whether reordering of revisions is allowed.
602 This value is used when ``bundle.reorder`` is ``auto`` or isn't
602 This value is used when ``bundle.reorder`` is ``auto`` or isn't
603 set.
603 set.
604
604
605 deltaparentfn is a callable that resolves the delta parent for
605 deltaparentfn is a callable that resolves the delta parent for
606 a specific revision.
606 a specific revision.
607
607
608 builddeltaheader is a callable that constructs the header for a group
608 builddeltaheader is a callable that constructs the header for a group
609 delta.
609 delta.
610
610
611 manifestsend is a chunk to send after manifests have been fully emitted.
611 manifestsend is a chunk to send after manifests have been fully emitted.
612
612
613 ellipses indicates whether ellipsis serving mode is enabled.
613 ellipses indicates whether ellipsis serving mode is enabled.
614
614
615 bundlecaps is optional and can be used to specify the set of
615 bundlecaps is optional and can be used to specify the set of
616 capabilities which can be used to build the bundle. While bundlecaps is
616 capabilities which can be used to build the bundle. While bundlecaps is
617 unused in core Mercurial, extensions rely on this feature to communicate
617 unused in core Mercurial, extensions rely on this feature to communicate
618 capabilities to customize the changegroup packer.
618 capabilities to customize the changegroup packer.
619
619
620 shallow indicates whether shallow data might be sent. The packer may
620 shallow indicates whether shallow data might be sent. The packer may
621 need to pack file contents not introduced by the changes being packed.
621 need to pack file contents not introduced by the changes being packed.
622
622
623 fullnodes is the set of changelog nodes which should not be ellipsis
623 fullnodes is the set of changelog nodes which should not be ellipsis
624 nodes. We store this rather than the set of nodes that should be
624 nodes. We store this rather than the set of nodes that should be
625 ellipsis because for very large histories we expect this to be
625 ellipsis because for very large histories we expect this to be
626 significantly smaller.
626 significantly smaller.
627 """
627 """
628 assert filematcher
628 assert filematcher
629 self._filematcher = filematcher
629 self._filematcher = filematcher
630
630
631 self.version = version
631 self.version = version
632 self._deltaparentfn = deltaparentfn
632 self._deltaparentfn = deltaparentfn
633 self._builddeltaheader = builddeltaheader
633 self._builddeltaheader = builddeltaheader
634 self._manifestsend = manifestsend
634 self._manifestsend = manifestsend
635 self._ellipses = ellipses
635 self._ellipses = ellipses
636
636
637 # Set of capabilities we can use to build the bundle.
637 # Set of capabilities we can use to build the bundle.
638 if bundlecaps is None:
638 if bundlecaps is None:
639 bundlecaps = set()
639 bundlecaps = set()
640 self._bundlecaps = bundlecaps
640 self._bundlecaps = bundlecaps
641 self._isshallow = shallow
641 self._isshallow = shallow
642 self._fullclnodes = fullnodes
642 self._fullclnodes = fullnodes
643
643
644 # Maps ellipsis revs to their roots at the changelog level.
644 # Maps ellipsis revs to their roots at the changelog level.
645 self._precomputedellipsis = ellipsisroots
645 self._precomputedellipsis = ellipsisroots
646
646
647 # experimental config: bundle.reorder
647 # experimental config: bundle.reorder
648 reorder = repo.ui.config('bundle', 'reorder')
648 reorder = repo.ui.config('bundle', 'reorder')
649 if reorder == 'auto':
649 if reorder == 'auto':
650 self._reorder = allowreorder
650 self._reorder = allowreorder
651 else:
651 else:
652 self._reorder = stringutil.parsebool(reorder)
652 self._reorder = stringutil.parsebool(reorder)
653
653
654 self._repo = repo
654 self._repo = repo
655
655
656 if self._repo.ui.verbose and not self._repo.ui.debugflag:
656 if self._repo.ui.verbose and not self._repo.ui.debugflag:
657 self._verbosenote = self._repo.ui.note
657 self._verbosenote = self._repo.ui.note
658 else:
658 else:
659 self._verbosenote = lambda s: None
659 self._verbosenote = lambda s: None
660
660
661 def group(self, revs, store, ischangelog, lookup, units=None,
661 def group(self, revs, store, ischangelog, lookup, units=None,
662 clrevtolocalrev=None):
662 clrevtolocalrev=None):
663 """Calculate a delta group, yielding a sequence of changegroup chunks
663 """Calculate a delta group, yielding a sequence of changegroup chunks
664 (strings).
664 (strings).
665
665
666 Given a list of changeset revs, return a set of deltas and
666 Given a list of changeset revs, return a set of deltas and
667 metadata corresponding to nodes. The first delta is
667 metadata corresponding to nodes. The first delta is
668 first parent(nodelist[0]) -> nodelist[0], the receiver is
668 first parent(nodelist[0]) -> nodelist[0], the receiver is
669 guaranteed to have this parent as it has all history before
669 guaranteed to have this parent as it has all history before
670 these changesets. In the case firstparent is nullrev the
670 these changesets. In the case firstparent is nullrev the
671 changegroup starts with a full revision.
671 changegroup starts with a full revision.
672
672
673 If units is not None, progress detail will be generated, units specifies
673 If units is not None, progress detail will be generated, units specifies
674 the type of revlog that is touched (changelog, manifest, etc.).
674 the type of revlog that is touched (changelog, manifest, etc.).
675 """
675 """
676 # if we don't have any revisions touched by these changesets, bail
676 # if we don't have any revisions touched by these changesets, bail
677 if len(revs) == 0:
677 if len(revs) == 0:
678 yield closechunk()
678 yield closechunk()
679 return
679 return
680
680
681 cl = self._repo.changelog
681 cl = self._repo.changelog
682
682
683 # add the parent of the first rev
683 # add the parent of the first rev
684 p = store.parentrevs(revs[0])[0]
684 p = store.parentrevs(revs[0])[0]
685 revs.insert(0, p)
685 revs.insert(0, p)
686
686
687 # build deltas
687 # build deltas
688 progress = None
688 progress = None
689 if units is not None:
689 if units is not None:
690 progress = self._repo.ui.makeprogress(_('bundling'), unit=units,
690 progress = self._repo.ui.makeprogress(_('bundling'), unit=units,
691 total=(len(revs) - 1))
691 total=(len(revs) - 1))
692 for r in pycompat.xrange(len(revs) - 1):
692 for r in pycompat.xrange(len(revs) - 1):
693 if progress:
693 if progress:
694 progress.update(r + 1)
694 progress.update(r + 1)
695 prev, curr = revs[r], revs[r + 1]
695 prev, curr = revs[r], revs[r + 1]
696 linknode = lookup(store.node(curr))
696 linknode = lookup(store.node(curr))
697
697
698 if self._ellipses:
698 if self._ellipses:
699 linkrev = cl.rev(linknode)
699 linkrev = cl.rev(linknode)
700 clrevtolocalrev[linkrev] = curr
700 clrevtolocalrev[linkrev] = curr
701
701
702 # This is a node to send in full, because the changeset it
702 # This is a node to send in full, because the changeset it
703 # corresponds to was a full changeset.
703 # corresponds to was a full changeset.
704 if linknode in self._fullclnodes:
704 if linknode in self._fullclnodes:
705 delta = _revisiondeltanormal(store, curr, prev, linknode,
705 delta = _revisiondeltanormal(store, curr, prev, linknode,
706 self._deltaparentfn)
706 self._deltaparentfn)
707 elif linkrev not in self._precomputedellipsis:
707 elif linkrev not in self._precomputedellipsis:
708 delta = None
708 delta = None
709 else:
709 else:
710 delta = self._revisiondeltanarrow(store, ischangelog,
710 delta = self._revisiondeltanarrow(
711 curr, linkrev, linknode,
711 cl, store, ischangelog, curr, linkrev, linknode,
712 clrevtolocalrev)
712 clrevtolocalrev, self._fullclnodes,
713 self._precomputedellipsis)
713 else:
714 else:
714 delta = _revisiondeltanormal(store, curr, prev, linknode,
715 delta = _revisiondeltanormal(store, curr, prev, linknode,
715 self._deltaparentfn)
716 self._deltaparentfn)
716
717
717 if not delta:
718 if not delta:
718 continue
719 continue
719
720
720 meta = self._builddeltaheader(delta)
721 meta = self._builddeltaheader(delta)
721 l = len(meta) + sum(len(x) for x in delta.deltachunks)
722 l = len(meta) + sum(len(x) for x in delta.deltachunks)
722 yield chunkheader(l)
723 yield chunkheader(l)
723 yield meta
724 yield meta
724 for x in delta.deltachunks:
725 for x in delta.deltachunks:
725 yield x
726 yield x
726
727
727 if progress:
728 if progress:
728 progress.complete()
729 progress.complete()
729
730
730 yield closechunk()
731 yield closechunk()
731
732
732 # filter any nodes that claim to be part of the known set
733 # filter any nodes that claim to be part of the known set
733 def _prune(self, store, missing, commonrevs):
734 def _prune(self, store, missing, commonrevs):
734 # TODO this violates storage abstraction for manifests.
735 # TODO this violates storage abstraction for manifests.
735 if isinstance(store, manifest.manifestrevlog):
736 if isinstance(store, manifest.manifestrevlog):
736 if not self._filematcher.visitdir(store._dir[:-1] or '.'):
737 if not self._filematcher.visitdir(store._dir[:-1] or '.'):
737 return []
738 return []
738
739
739 rr, rl = store.rev, store.linkrev
740 rr, rl = store.rev, store.linkrev
740 return [n for n in missing if rl(rr(n)) not in commonrevs]
741 return [n for n in missing if rl(rr(n)) not in commonrevs]
741
742
742 def _packmanifests(self, dir, dirlog, revs, lookuplinknode,
743 def _packmanifests(self, dir, dirlog, revs, lookuplinknode,
743 clrevtolocalrev):
744 clrevtolocalrev):
744 """Pack manifests into a changegroup stream.
745 """Pack manifests into a changegroup stream.
745
746
746 Encodes the directory name in the output so multiple manifests
747 Encodes the directory name in the output so multiple manifests
747 can be sent. Multiple manifests is not supported by cg1 and cg2.
748 can be sent. Multiple manifests is not supported by cg1 and cg2.
748 """
749 """
749 if dir:
750 if dir:
750 assert self.version == b'03'
751 assert self.version == b'03'
751 yield _fileheader(dir)
752 yield _fileheader(dir)
752
753
753 for chunk in self.group(revs, dirlog, False, lookuplinknode,
754 for chunk in self.group(revs, dirlog, False, lookuplinknode,
754 units=_('manifests'),
755 units=_('manifests'),
755 clrevtolocalrev=clrevtolocalrev):
756 clrevtolocalrev=clrevtolocalrev):
756 yield chunk
757 yield chunk
757
758
758 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
759 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
759 """Yield a sequence of changegroup byte chunks."""
760 """Yield a sequence of changegroup byte chunks."""
760
761
761 repo = self._repo
762 repo = self._repo
762 cl = repo.changelog
763 cl = repo.changelog
763
764
764 self._verbosenote(_('uncompressed size of bundle content:\n'))
765 self._verbosenote(_('uncompressed size of bundle content:\n'))
765 size = 0
766 size = 0
766
767
767 clstate, chunks = self._generatechangelog(cl, clnodes)
768 clstate, chunks = self._generatechangelog(cl, clnodes)
768 for chunk in chunks:
769 for chunk in chunks:
769 size += len(chunk)
770 size += len(chunk)
770 yield chunk
771 yield chunk
771
772
772 self._verbosenote(_('%8.i (changelog)\n') % size)
773 self._verbosenote(_('%8.i (changelog)\n') % size)
773
774
774 clrevorder = clstate['clrevorder']
775 clrevorder = clstate['clrevorder']
775 mfs = clstate['mfs']
776 mfs = clstate['mfs']
776 changedfiles = clstate['changedfiles']
777 changedfiles = clstate['changedfiles']
777
778
778 # We need to make sure that the linkrev in the changegroup refers to
779 # We need to make sure that the linkrev in the changegroup refers to
779 # the first changeset that introduced the manifest or file revision.
780 # the first changeset that introduced the manifest or file revision.
780 # The fastpath is usually safer than the slowpath, because the filelogs
781 # The fastpath is usually safer than the slowpath, because the filelogs
781 # are walked in revlog order.
782 # are walked in revlog order.
782 #
783 #
783 # When taking the slowpath with reorder=None and the manifest revlog
784 # When taking the slowpath with reorder=None and the manifest revlog
784 # uses generaldelta, the manifest may be walked in the "wrong" order.
785 # uses generaldelta, the manifest may be walked in the "wrong" order.
785 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
786 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
786 # cc0ff93d0c0c).
787 # cc0ff93d0c0c).
787 #
788 #
788 # When taking the fastpath, we are only vulnerable to reordering
789 # When taking the fastpath, we are only vulnerable to reordering
789 # of the changelog itself. The changelog never uses generaldelta, so
790 # of the changelog itself. The changelog never uses generaldelta, so
790 # it is only reordered when reorder=True. To handle this case, we
791 # it is only reordered when reorder=True. To handle this case, we
791 # simply take the slowpath, which already has the 'clrevorder' logic.
792 # simply take the slowpath, which already has the 'clrevorder' logic.
792 # This was also fixed in cc0ff93d0c0c.
793 # This was also fixed in cc0ff93d0c0c.
793 fastpathlinkrev = fastpathlinkrev and not self._reorder
794 fastpathlinkrev = fastpathlinkrev and not self._reorder
794 # Treemanifests don't work correctly with fastpathlinkrev
795 # Treemanifests don't work correctly with fastpathlinkrev
795 # either, because we don't discover which directory nodes to
796 # either, because we don't discover which directory nodes to
796 # send along with files. This could probably be fixed.
797 # send along with files. This could probably be fixed.
797 fastpathlinkrev = fastpathlinkrev and (
798 fastpathlinkrev = fastpathlinkrev and (
798 'treemanifest' not in repo.requirements)
799 'treemanifest' not in repo.requirements)
799
800
800 fnodes = {} # needed file nodes
801 fnodes = {} # needed file nodes
801
802
802 for chunk in self.generatemanifests(commonrevs, clrevorder,
803 for chunk in self.generatemanifests(commonrevs, clrevorder,
803 fastpathlinkrev, mfs, fnodes, source,
804 fastpathlinkrev, mfs, fnodes, source,
804 clstate['clrevtomanifestrev']):
805 clstate['clrevtomanifestrev']):
805 yield chunk
806 yield chunk
806
807
807 mfdicts = None
808 mfdicts = None
808 if self._ellipses and self._isshallow:
809 if self._ellipses and self._isshallow:
809 mfdicts = [(self._repo.manifestlog[n].read(), lr)
810 mfdicts = [(self._repo.manifestlog[n].read(), lr)
810 for (n, lr) in mfs.iteritems()]
811 for (n, lr) in mfs.iteritems()]
811
812
812 mfs.clear()
813 mfs.clear()
813 clrevs = set(cl.rev(x) for x in clnodes)
814 clrevs = set(cl.rev(x) for x in clnodes)
814
815
815 for chunk in self.generatefiles(changedfiles, commonrevs,
816 for chunk in self.generatefiles(changedfiles, commonrevs,
816 source, mfdicts, fastpathlinkrev,
817 source, mfdicts, fastpathlinkrev,
817 fnodes, clrevs):
818 fnodes, clrevs):
818 yield chunk
819 yield chunk
819
820
820 yield closechunk()
821 yield closechunk()
821
822
822 if clnodes:
823 if clnodes:
823 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
824 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
824
825
825 def _generatechangelog(self, cl, nodes):
826 def _generatechangelog(self, cl, nodes):
826 """Generate data for changelog chunks.
827 """Generate data for changelog chunks.
827
828
828 Returns a 2-tuple of a dict containing state and an iterable of
829 Returns a 2-tuple of a dict containing state and an iterable of
829 byte chunks. The state will not be fully populated until the
830 byte chunks. The state will not be fully populated until the
830 chunk stream has been fully consumed.
831 chunk stream has been fully consumed.
831 """
832 """
832 clrevorder = {}
833 clrevorder = {}
833 mfs = {} # needed manifests
834 mfs = {} # needed manifests
834 mfl = self._repo.manifestlog
835 mfl = self._repo.manifestlog
835 # TODO violates storage abstraction.
836 # TODO violates storage abstraction.
836 mfrevlog = mfl._revlog
837 mfrevlog = mfl._revlog
837 changedfiles = set()
838 changedfiles = set()
838 clrevtomanifestrev = {}
839 clrevtomanifestrev = {}
839
840
840 # Callback for the changelog, used to collect changed files and
841 # Callback for the changelog, used to collect changed files and
841 # manifest nodes.
842 # manifest nodes.
842 # Returns the linkrev node (identity in the changelog case).
843 # Returns the linkrev node (identity in the changelog case).
843 def lookupcl(x):
844 def lookupcl(x):
844 c = cl.read(x)
845 c = cl.read(x)
845 clrevorder[x] = len(clrevorder)
846 clrevorder[x] = len(clrevorder)
846
847
847 if self._ellipses:
848 if self._ellipses:
848 # Only update mfs if x is going to be sent. Otherwise we
849 # Only update mfs if x is going to be sent. Otherwise we
849 # end up with bogus linkrevs specified for manifests and
850 # end up with bogus linkrevs specified for manifests and
850 # we skip some manifest nodes that we should otherwise
851 # we skip some manifest nodes that we should otherwise
851 # have sent.
852 # have sent.
852 if (x in self._fullclnodes
853 if (x in self._fullclnodes
853 or cl.rev(x) in self._precomputedellipsis):
854 or cl.rev(x) in self._precomputedellipsis):
854 n = c[0]
855 n = c[0]
855 # Record the first changeset introducing this manifest
856 # Record the first changeset introducing this manifest
856 # version.
857 # version.
857 mfs.setdefault(n, x)
858 mfs.setdefault(n, x)
858 # Set this narrow-specific dict so we have the lowest
859 # Set this narrow-specific dict so we have the lowest
859 # manifest revnum to look up for this cl revnum. (Part of
860 # manifest revnum to look up for this cl revnum. (Part of
860 # mapping changelog ellipsis parents to manifest ellipsis
861 # mapping changelog ellipsis parents to manifest ellipsis
861 # parents)
862 # parents)
862 clrevtomanifestrev.setdefault(cl.rev(x), mfrevlog.rev(n))
863 clrevtomanifestrev.setdefault(cl.rev(x), mfrevlog.rev(n))
863 # We can't trust the changed files list in the changeset if the
864 # We can't trust the changed files list in the changeset if the
864 # client requested a shallow clone.
865 # client requested a shallow clone.
865 if self._isshallow:
866 if self._isshallow:
866 changedfiles.update(mfl[c[0]].read().keys())
867 changedfiles.update(mfl[c[0]].read().keys())
867 else:
868 else:
868 changedfiles.update(c[3])
869 changedfiles.update(c[3])
869 else:
870 else:
870
871
871 n = c[0]
872 n = c[0]
872 # record the first changeset introducing this manifest version
873 # record the first changeset introducing this manifest version
873 mfs.setdefault(n, x)
874 mfs.setdefault(n, x)
874 # Record a complete list of potentially-changed files in
875 # Record a complete list of potentially-changed files in
875 # this manifest.
876 # this manifest.
876 changedfiles.update(c[3])
877 changedfiles.update(c[3])
877
878
878 return x
879 return x
879
880
880 # Changelog doesn't benefit from reordering revisions. So send out
881 # Changelog doesn't benefit from reordering revisions. So send out
881 # revisions in store order.
882 # revisions in store order.
882 revs = sorted(cl.rev(n) for n in nodes)
883 revs = sorted(cl.rev(n) for n in nodes)
883
884
884 state = {
885 state = {
885 'clrevorder': clrevorder,
886 'clrevorder': clrevorder,
886 'mfs': mfs,
887 'mfs': mfs,
887 'changedfiles': changedfiles,
888 'changedfiles': changedfiles,
888 'clrevtomanifestrev': clrevtomanifestrev,
889 'clrevtomanifestrev': clrevtomanifestrev,
889 }
890 }
890
891
891 gen = self.group(revs, cl, True, lookupcl, units=_('changesets'),
892 gen = self.group(revs, cl, True, lookupcl, units=_('changesets'),
892 clrevtolocalrev={})
893 clrevtolocalrev={})
893
894
894 return state, gen
895 return state, gen
895
896
896 def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev, mfs,
897 def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev, mfs,
897 fnodes, source, clrevtolocalrev):
898 fnodes, source, clrevtolocalrev):
898 """Returns an iterator of changegroup chunks containing manifests.
899 """Returns an iterator of changegroup chunks containing manifests.
899
900
900 `source` is unused here, but is used by extensions like remotefilelog to
901 `source` is unused here, but is used by extensions like remotefilelog to
901 change what is sent based in pulls vs pushes, etc.
902 change what is sent based in pulls vs pushes, etc.
902 """
903 """
903 repo = self._repo
904 repo = self._repo
904 cl = repo.changelog
905 cl = repo.changelog
905 mfl = repo.manifestlog
906 mfl = repo.manifestlog
906 dirlog = mfl._revlog.dirlog
907 dirlog = mfl._revlog.dirlog
907 tmfnodes = {'': mfs}
908 tmfnodes = {'': mfs}
908
909
909 # Callback for the manifest, used to collect linkrevs for filelog
910 # Callback for the manifest, used to collect linkrevs for filelog
910 # revisions.
911 # revisions.
911 # Returns the linkrev node (collected in lookupcl).
912 # Returns the linkrev node (collected in lookupcl).
912 def makelookupmflinknode(dir, nodes):
913 def makelookupmflinknode(dir, nodes):
913 if fastpathlinkrev:
914 if fastpathlinkrev:
914 assert not dir
915 assert not dir
915 return mfs.__getitem__
916 return mfs.__getitem__
916
917
917 def lookupmflinknode(x):
918 def lookupmflinknode(x):
918 """Callback for looking up the linknode for manifests.
919 """Callback for looking up the linknode for manifests.
919
920
920 Returns the linkrev node for the specified manifest.
921 Returns the linkrev node for the specified manifest.
921
922
922 SIDE EFFECT:
923 SIDE EFFECT:
923
924
924 1) fclnodes gets populated with the list of relevant
925 1) fclnodes gets populated with the list of relevant
925 file nodes if we're not using fastpathlinkrev
926 file nodes if we're not using fastpathlinkrev
926 2) When treemanifests are in use, collects treemanifest nodes
927 2) When treemanifests are in use, collects treemanifest nodes
927 to send
928 to send
928
929
929 Note that this means manifests must be completely sent to
930 Note that this means manifests must be completely sent to
930 the client before you can trust the list of files and
931 the client before you can trust the list of files and
931 treemanifests to send.
932 treemanifests to send.
932 """
933 """
933 clnode = nodes[x]
934 clnode = nodes[x]
934 mdata = mfl.get(dir, x).readfast(shallow=True)
935 mdata = mfl.get(dir, x).readfast(shallow=True)
935 for p, n, fl in mdata.iterentries():
936 for p, n, fl in mdata.iterentries():
936 if fl == 't': # subdirectory manifest
937 if fl == 't': # subdirectory manifest
937 subdir = dir + p + '/'
938 subdir = dir + p + '/'
938 tmfclnodes = tmfnodes.setdefault(subdir, {})
939 tmfclnodes = tmfnodes.setdefault(subdir, {})
939 tmfclnode = tmfclnodes.setdefault(n, clnode)
940 tmfclnode = tmfclnodes.setdefault(n, clnode)
940 if clrevorder[clnode] < clrevorder[tmfclnode]:
941 if clrevorder[clnode] < clrevorder[tmfclnode]:
941 tmfclnodes[n] = clnode
942 tmfclnodes[n] = clnode
942 else:
943 else:
943 f = dir + p
944 f = dir + p
944 fclnodes = fnodes.setdefault(f, {})
945 fclnodes = fnodes.setdefault(f, {})
945 fclnode = fclnodes.setdefault(n, clnode)
946 fclnode = fclnodes.setdefault(n, clnode)
946 if clrevorder[clnode] < clrevorder[fclnode]:
947 if clrevorder[clnode] < clrevorder[fclnode]:
947 fclnodes[n] = clnode
948 fclnodes[n] = clnode
948 return clnode
949 return clnode
949 return lookupmflinknode
950 return lookupmflinknode
950
951
951 size = 0
952 size = 0
952 while tmfnodes:
953 while tmfnodes:
953 dir, nodes = tmfnodes.popitem()
954 dir, nodes = tmfnodes.popitem()
954 store = dirlog(dir)
955 store = dirlog(dir)
955 prunednodes = self._prune(store, nodes, commonrevs)
956 prunednodes = self._prune(store, nodes, commonrevs)
956 if not dir or prunednodes:
957 if not dir or prunednodes:
957 lookupfn = makelookupmflinknode(dir, nodes)
958 lookupfn = makelookupmflinknode(dir, nodes)
958
959
959 if self._ellipses:
960 if self._ellipses:
960 revs = _sortnodesellipsis(store, prunednodes, cl,
961 revs = _sortnodesellipsis(store, prunednodes, cl,
961 lookupfn)
962 lookupfn)
962 else:
963 else:
963 revs = _sortnodesnormal(store, prunednodes,
964 revs = _sortnodesnormal(store, prunednodes,
964 self._reorder)
965 self._reorder)
965
966
966 for x in self._packmanifests(dir, store, revs, lookupfn,
967 for x in self._packmanifests(dir, store, revs, lookupfn,
967 clrevtolocalrev):
968 clrevtolocalrev):
968 size += len(x)
969 size += len(x)
969 yield x
970 yield x
970 self._verbosenote(_('%8.i (manifests)\n') % size)
971 self._verbosenote(_('%8.i (manifests)\n') % size)
971 yield self._manifestsend
972 yield self._manifestsend
972
973
973 # The 'source' parameter is useful for extensions
974 # The 'source' parameter is useful for extensions
974 def generatefiles(self, changedfiles, commonrevs, source,
975 def generatefiles(self, changedfiles, commonrevs, source,
975 mfdicts, fastpathlinkrev, fnodes, clrevs):
976 mfdicts, fastpathlinkrev, fnodes, clrevs):
976 changedfiles = list(filter(self._filematcher, changedfiles))
977 changedfiles = list(filter(self._filematcher, changedfiles))
977
978
978 if not fastpathlinkrev:
979 if not fastpathlinkrev:
979 def normallinknodes(unused, fname):
980 def normallinknodes(unused, fname):
980 return fnodes.get(fname, {})
981 return fnodes.get(fname, {})
981 else:
982 else:
982 cln = self._repo.changelog.node
983 cln = self._repo.changelog.node
983
984
984 def normallinknodes(store, fname):
985 def normallinknodes(store, fname):
985 flinkrev = store.linkrev
986 flinkrev = store.linkrev
986 fnode = store.node
987 fnode = store.node
987 revs = ((r, flinkrev(r)) for r in store)
988 revs = ((r, flinkrev(r)) for r in store)
988 return dict((fnode(r), cln(lr))
989 return dict((fnode(r), cln(lr))
989 for r, lr in revs if lr in clrevs)
990 for r, lr in revs if lr in clrevs)
990
991
991 clrevtolocalrev = {}
992 clrevtolocalrev = {}
992
993
993 if self._isshallow:
994 if self._isshallow:
994 # In a shallow clone, the linknodes callback needs to also include
995 # In a shallow clone, the linknodes callback needs to also include
995 # those file nodes that are in the manifests we sent but weren't
996 # those file nodes that are in the manifests we sent but weren't
996 # introduced by those manifests.
997 # introduced by those manifests.
997 commonctxs = [self._repo[c] for c in commonrevs]
998 commonctxs = [self._repo[c] for c in commonrevs]
998 clrev = self._repo.changelog.rev
999 clrev = self._repo.changelog.rev
999
1000
1000 # Defining this function has a side-effect of overriding the
1001 # Defining this function has a side-effect of overriding the
1001 # function of the same name that was passed in as an argument.
1002 # function of the same name that was passed in as an argument.
1002 # TODO have caller pass in appropriate function.
1003 # TODO have caller pass in appropriate function.
1003 def linknodes(flog, fname):
1004 def linknodes(flog, fname):
1004 for c in commonctxs:
1005 for c in commonctxs:
1005 try:
1006 try:
1006 fnode = c.filenode(fname)
1007 fnode = c.filenode(fname)
1007 clrevtolocalrev[c.rev()] = flog.rev(fnode)
1008 clrevtolocalrev[c.rev()] = flog.rev(fnode)
1008 except error.ManifestLookupError:
1009 except error.ManifestLookupError:
1009 pass
1010 pass
1010 links = normallinknodes(flog, fname)
1011 links = normallinknodes(flog, fname)
1011 if len(links) != len(mfdicts):
1012 if len(links) != len(mfdicts):
1012 for mf, lr in mfdicts:
1013 for mf, lr in mfdicts:
1013 fnode = mf.get(fname, None)
1014 fnode = mf.get(fname, None)
1014 if fnode in links:
1015 if fnode in links:
1015 links[fnode] = min(links[fnode], lr, key=clrev)
1016 links[fnode] = min(links[fnode], lr, key=clrev)
1016 elif fnode:
1017 elif fnode:
1017 links[fnode] = lr
1018 links[fnode] = lr
1018 return links
1019 return links
1019 else:
1020 else:
1020 linknodes = normallinknodes
1021 linknodes = normallinknodes
1021
1022
1022 repo = self._repo
1023 repo = self._repo
1023 cl = repo.changelog
1024 cl = repo.changelog
1024 progress = repo.ui.makeprogress(_('bundling'), unit=_('files'),
1025 progress = repo.ui.makeprogress(_('bundling'), unit=_('files'),
1025 total=len(changedfiles))
1026 total=len(changedfiles))
1026 for i, fname in enumerate(sorted(changedfiles)):
1027 for i, fname in enumerate(sorted(changedfiles)):
1027 filerevlog = repo.file(fname)
1028 filerevlog = repo.file(fname)
1028 if not filerevlog:
1029 if not filerevlog:
1029 raise error.Abort(_("empty or missing file data for %s") %
1030 raise error.Abort(_("empty or missing file data for %s") %
1030 fname)
1031 fname)
1031
1032
1032 clrevtolocalrev.clear()
1033 clrevtolocalrev.clear()
1033
1034
1034 linkrevnodes = linknodes(filerevlog, fname)
1035 linkrevnodes = linknodes(filerevlog, fname)
1035 # Lookup for filenodes, we collected the linkrev nodes above in the
1036 # Lookup for filenodes, we collected the linkrev nodes above in the
1036 # fastpath case and with lookupmf in the slowpath case.
1037 # fastpath case and with lookupmf in the slowpath case.
1037 def lookupfilelog(x):
1038 def lookupfilelog(x):
1038 return linkrevnodes[x]
1039 return linkrevnodes[x]
1039
1040
1040 filenodes = self._prune(filerevlog, linkrevnodes, commonrevs)
1041 filenodes = self._prune(filerevlog, linkrevnodes, commonrevs)
1041 if filenodes:
1042 if filenodes:
1042 if self._ellipses:
1043 if self._ellipses:
1043 revs = _sortnodesellipsis(filerevlog, filenodes,
1044 revs = _sortnodesellipsis(filerevlog, filenodes,
1044 cl, lookupfilelog)
1045 cl, lookupfilelog)
1045 else:
1046 else:
1046 revs = _sortnodesnormal(filerevlog, filenodes,
1047 revs = _sortnodesnormal(filerevlog, filenodes,
1047 self._reorder)
1048 self._reorder)
1048
1049
1049 progress.update(i + 1, item=fname)
1050 progress.update(i + 1, item=fname)
1050 h = _fileheader(fname)
1051 h = _fileheader(fname)
1051 size = len(h)
1052 size = len(h)
1052 yield h
1053 yield h
1053 for chunk in self.group(revs, filerevlog, False, lookupfilelog,
1054 for chunk in self.group(revs, filerevlog, False, lookupfilelog,
1054 clrevtolocalrev=clrevtolocalrev):
1055 clrevtolocalrev=clrevtolocalrev):
1055 size += len(chunk)
1056 size += len(chunk)
1056 yield chunk
1057 yield chunk
1057 self._verbosenote(_('%8.i %s\n') % (size, fname))
1058 self._verbosenote(_('%8.i %s\n') % (size, fname))
1058 progress.complete()
1059 progress.complete()
1059
1060
1060 def _revisiondeltanarrow(self, store, ischangelog, rev, linkrev, linknode,
1061 def _revisiondeltanarrow(self, cl, store, ischangelog, rev, linkrev,
1061 clrevtolocalrev):
1062 linknode, clrevtolocalrev, fullclnodes,
1062 linkparents = self._precomputedellipsis[linkrev]
1063 precomputedellipsis):
1064 linkparents = precomputedellipsis[linkrev]
1063 def local(clrev):
1065 def local(clrev):
1064 """Turn a changelog revnum into a local revnum.
1066 """Turn a changelog revnum into a local revnum.
1065
1067
1066 The ellipsis dag is stored as revnums on the changelog,
1068 The ellipsis dag is stored as revnums on the changelog,
1067 but when we're producing ellipsis entries for
1069 but when we're producing ellipsis entries for
1068 non-changelog revlogs, we need to turn those numbers into
1070 non-changelog revlogs, we need to turn those numbers into
1069 something local. This does that for us, and during the
1071 something local. This does that for us, and during the
1070 changelog sending phase will also expand the stored
1072 changelog sending phase will also expand the stored
1071 mappings as needed.
1073 mappings as needed.
1072 """
1074 """
1073 if clrev == nullrev:
1075 if clrev == nullrev:
1074 return nullrev
1076 return nullrev
1075
1077
1076 if ischangelog:
1078 if ischangelog:
1077 return clrev
1079 return clrev
1078
1080
1079 # Walk the ellipsis-ized changelog breadth-first looking for a
1081 # Walk the ellipsis-ized changelog breadth-first looking for a
1080 # change that has been linked from the current revlog.
1082 # change that has been linked from the current revlog.
1081 #
1083 #
1082 # For a flat manifest revlog only a single step should be necessary
1084 # For a flat manifest revlog only a single step should be necessary
1083 # as all relevant changelog entries are relevant to the flat
1085 # as all relevant changelog entries are relevant to the flat
1084 # manifest.
1086 # manifest.
1085 #
1087 #
1086 # For a filelog or tree manifest dirlog however not every changelog
1088 # For a filelog or tree manifest dirlog however not every changelog
1087 # entry will have been relevant, so we need to skip some changelog
1089 # entry will have been relevant, so we need to skip some changelog
1088 # nodes even after ellipsis-izing.
1090 # nodes even after ellipsis-izing.
1089 walk = [clrev]
1091 walk = [clrev]
1090 while walk:
1092 while walk:
1091 p = walk[0]
1093 p = walk[0]
1092 walk = walk[1:]
1094 walk = walk[1:]
1093 if p in clrevtolocalrev:
1095 if p in clrevtolocalrev:
1094 return clrevtolocalrev[p]
1096 return clrevtolocalrev[p]
1095 elif p in self._fullclnodes:
1097 elif p in fullclnodes:
1096 walk.extend([pp for pp in self._repo.changelog.parentrevs(p)
1098 walk.extend([pp for pp in cl.parentrevs(p)
1097 if pp != nullrev])
1099 if pp != nullrev])
1098 elif p in self._precomputedellipsis:
1100 elif p in precomputedellipsis:
1099 walk.extend([pp for pp in self._precomputedellipsis[p]
1101 walk.extend([pp for pp in precomputedellipsis[p]
1100 if pp != nullrev])
1102 if pp != nullrev])
1101 else:
1103 else:
1102 # In this case, we've got an ellipsis with parents
1104 # In this case, we've got an ellipsis with parents
1103 # outside the current bundle (likely an
1105 # outside the current bundle (likely an
1104 # incremental pull). We "know" that we can use the
1106 # incremental pull). We "know" that we can use the
1105 # value of this same revlog at whatever revision
1107 # value of this same revlog at whatever revision
1106 # is pointed to by linknode. "Know" is in scare
1108 # is pointed to by linknode. "Know" is in scare
1107 # quotes because I haven't done enough examination
1109 # quotes because I haven't done enough examination
1108 # of edge cases to convince myself this is really
1110 # of edge cases to convince myself this is really
1109 # a fact - it works for all the (admittedly
1111 # a fact - it works for all the (admittedly
1110 # thorough) cases in our testsuite, but I would be
1112 # thorough) cases in our testsuite, but I would be
1111 # somewhat unsurprised to find a case in the wild
1113 # somewhat unsurprised to find a case in the wild
1112 # where this breaks down a bit. That said, I don't
1114 # where this breaks down a bit. That said, I don't
1113 # know if it would hurt anything.
1115 # know if it would hurt anything.
1114 for i in pycompat.xrange(rev, 0, -1):
1116 for i in pycompat.xrange(rev, 0, -1):
1115 if store.linkrev(i) == clrev:
1117 if store.linkrev(i) == clrev:
1116 return i
1118 return i
1117 # We failed to resolve a parent for this node, so
1119 # We failed to resolve a parent for this node, so
1118 # we crash the changegroup construction.
1120 # we crash the changegroup construction.
1119 raise error.Abort(
1121 raise error.Abort(
1120 'unable to resolve parent while packing %r %r'
1122 'unable to resolve parent while packing %r %r'
1121 ' for changeset %r' % (store.indexfile, rev, clrev))
1123 ' for changeset %r' % (store.indexfile, rev, clrev))
1122
1124
1123 return nullrev
1125 return nullrev
1124
1126
1125 if not linkparents or (
1127 if not linkparents or (
1126 store.parentrevs(rev) == (nullrev, nullrev)):
1128 store.parentrevs(rev) == (nullrev, nullrev)):
1127 p1, p2 = nullrev, nullrev
1129 p1, p2 = nullrev, nullrev
1128 elif len(linkparents) == 1:
1130 elif len(linkparents) == 1:
1129 p1, = sorted(local(p) for p in linkparents)
1131 p1, = sorted(local(p) for p in linkparents)
1130 p2 = nullrev
1132 p2 = nullrev
1131 else:
1133 else:
1132 p1, p2 = sorted(local(p) for p in linkparents)
1134 p1, p2 = sorted(local(p) for p in linkparents)
1133
1135
1134 n = store.node(rev)
1136 n = store.node(rev)
1135 p1n, p2n = store.node(p1), store.node(p2)
1137 p1n, p2n = store.node(p1), store.node(p2)
1136 flags = store.flags(rev)
1138 flags = store.flags(rev)
1137 flags |= revlog.REVIDX_ELLIPSIS
1139 flags |= revlog.REVIDX_ELLIPSIS
1138
1140
1139 # TODO: try and actually send deltas for ellipsis data blocks
1141 # TODO: try and actually send deltas for ellipsis data blocks
1140 data = store.revision(n)
1142 data = store.revision(n)
1141 diffheader = mdiff.trivialdiffheader(len(data))
1143 diffheader = mdiff.trivialdiffheader(len(data))
1142
1144
1143 return revisiondelta(
1145 return revisiondelta(
1144 node=n,
1146 node=n,
1145 p1node=p1n,
1147 p1node=p1n,
1146 p2node=p2n,
1148 p2node=p2n,
1147 basenode=nullid,
1149 basenode=nullid,
1148 linknode=linknode,
1150 linknode=linknode,
1149 flags=flags,
1151 flags=flags,
1150 deltachunks=(diffheader, data),
1152 deltachunks=(diffheader, data),
1151 )
1153 )
1152
1154
1153 def _deltaparentprev(store, rev, p1, p2, prev):
1155 def _deltaparentprev(store, rev, p1, p2, prev):
1154 """Resolve a delta parent to the previous revision.
1156 """Resolve a delta parent to the previous revision.
1155
1157
1156 Used for version 1 changegroups, which don't support generaldelta.
1158 Used for version 1 changegroups, which don't support generaldelta.
1157 """
1159 """
1158 return prev
1160 return prev
1159
1161
1160 def _deltaparentgeneraldelta(store, rev, p1, p2, prev):
1162 def _deltaparentgeneraldelta(store, rev, p1, p2, prev):
1161 """Resolve a delta parent when general deltas are supported."""
1163 """Resolve a delta parent when general deltas are supported."""
1162 dp = store.deltaparent(rev)
1164 dp = store.deltaparent(rev)
1163 if dp == nullrev and store.storedeltachains:
1165 if dp == nullrev and store.storedeltachains:
1164 # Avoid sending full revisions when delta parent is null. Pick prev
1166 # Avoid sending full revisions when delta parent is null. Pick prev
1165 # in that case. It's tempting to pick p1 in this case, as p1 will
1167 # in that case. It's tempting to pick p1 in this case, as p1 will
1166 # be smaller in the common case. However, computing a delta against
1168 # be smaller in the common case. However, computing a delta against
1167 # p1 may require resolving the raw text of p1, which could be
1169 # p1 may require resolving the raw text of p1, which could be
1168 # expensive. The revlog caches should have prev cached, meaning
1170 # expensive. The revlog caches should have prev cached, meaning
1169 # less CPU for changegroup generation. There is likely room to add
1171 # less CPU for changegroup generation. There is likely room to add
1170 # a flag and/or config option to control this behavior.
1172 # a flag and/or config option to control this behavior.
1171 base = prev
1173 base = prev
1172 elif dp == nullrev:
1174 elif dp == nullrev:
1173 # revlog is configured to use full snapshot for a reason,
1175 # revlog is configured to use full snapshot for a reason,
1174 # stick to full snapshot.
1176 # stick to full snapshot.
1175 base = nullrev
1177 base = nullrev
1176 elif dp not in (p1, p2, prev):
1178 elif dp not in (p1, p2, prev):
1177 # Pick prev when we can't be sure remote has the base revision.
1179 # Pick prev when we can't be sure remote has the base revision.
1178 return prev
1180 return prev
1179 else:
1181 else:
1180 base = dp
1182 base = dp
1181
1183
1182 if base != nullrev and not store.candelta(base, rev):
1184 if base != nullrev and not store.candelta(base, rev):
1183 base = nullrev
1185 base = nullrev
1184
1186
1185 return base
1187 return base
1186
1188
1187 def _deltaparentellipses(store, rev, p1, p2, prev):
1189 def _deltaparentellipses(store, rev, p1, p2, prev):
1188 """Resolve a delta parent when in ellipses mode."""
1190 """Resolve a delta parent when in ellipses mode."""
1189 # TODO: send better deltas when in narrow mode.
1191 # TODO: send better deltas when in narrow mode.
1190 #
1192 #
1191 # changegroup.group() loops over revisions to send,
1193 # changegroup.group() loops over revisions to send,
1192 # including revisions we'll skip. What this means is that
1194 # including revisions we'll skip. What this means is that
1193 # `prev` will be a potentially useless delta base for all
1195 # `prev` will be a potentially useless delta base for all
1194 # ellipsis nodes, as the client likely won't have it. In
1196 # ellipsis nodes, as the client likely won't have it. In
1195 # the future we should do bookkeeping about which nodes
1197 # the future we should do bookkeeping about which nodes
1196 # have been sent to the client, and try to be
1198 # have been sent to the client, and try to be
1197 # significantly smarter about delta bases. This is
1199 # significantly smarter about delta bases. This is
1198 # slightly tricky because this same code has to work for
1200 # slightly tricky because this same code has to work for
1199 # all revlogs, and we don't have the linkrev/linknode here.
1201 # all revlogs, and we don't have the linkrev/linknode here.
1200 return p1
1202 return p1
1201
1203
1202 def _makecg1packer(repo, filematcher, bundlecaps, ellipses=False,
1204 def _makecg1packer(repo, filematcher, bundlecaps, ellipses=False,
1203 shallow=False, ellipsisroots=None, fullnodes=None):
1205 shallow=False, ellipsisroots=None, fullnodes=None):
1204 builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack(
1206 builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack(
1205 d.node, d.p1node, d.p2node, d.linknode)
1207 d.node, d.p1node, d.p2node, d.linknode)
1206
1208
1207 return cgpacker(repo, filematcher, b'01',
1209 return cgpacker(repo, filematcher, b'01',
1208 deltaparentfn=_deltaparentprev,
1210 deltaparentfn=_deltaparentprev,
1209 allowreorder=None,
1211 allowreorder=None,
1210 builddeltaheader=builddeltaheader,
1212 builddeltaheader=builddeltaheader,
1211 manifestsend=b'',
1213 manifestsend=b'',
1212 bundlecaps=bundlecaps,
1214 bundlecaps=bundlecaps,
1213 ellipses=ellipses,
1215 ellipses=ellipses,
1214 shallow=shallow,
1216 shallow=shallow,
1215 ellipsisroots=ellipsisroots,
1217 ellipsisroots=ellipsisroots,
1216 fullnodes=fullnodes)
1218 fullnodes=fullnodes)
1217
1219
1218 def _makecg2packer(repo, filematcher, bundlecaps, ellipses=False,
1220 def _makecg2packer(repo, filematcher, bundlecaps, ellipses=False,
1219 shallow=False, ellipsisroots=None, fullnodes=None):
1221 shallow=False, ellipsisroots=None, fullnodes=None):
1220 builddeltaheader = lambda d: _CHANGEGROUPV2_DELTA_HEADER.pack(
1222 builddeltaheader = lambda d: _CHANGEGROUPV2_DELTA_HEADER.pack(
1221 d.node, d.p1node, d.p2node, d.basenode, d.linknode)
1223 d.node, d.p1node, d.p2node, d.basenode, d.linknode)
1222
1224
1223 # Since generaldelta is directly supported by cg2, reordering
1225 # Since generaldelta is directly supported by cg2, reordering
1224 # generally doesn't help, so we disable it by default (treating
1226 # generally doesn't help, so we disable it by default (treating
1225 # bundle.reorder=auto just like bundle.reorder=False).
1227 # bundle.reorder=auto just like bundle.reorder=False).
1226 return cgpacker(repo, filematcher, b'02',
1228 return cgpacker(repo, filematcher, b'02',
1227 deltaparentfn=_deltaparentgeneraldelta,
1229 deltaparentfn=_deltaparentgeneraldelta,
1228 allowreorder=False,
1230 allowreorder=False,
1229 builddeltaheader=builddeltaheader,
1231 builddeltaheader=builddeltaheader,
1230 manifestsend=b'',
1232 manifestsend=b'',
1231 bundlecaps=bundlecaps,
1233 bundlecaps=bundlecaps,
1232 ellipses=ellipses,
1234 ellipses=ellipses,
1233 shallow=shallow,
1235 shallow=shallow,
1234 ellipsisroots=ellipsisroots,
1236 ellipsisroots=ellipsisroots,
1235 fullnodes=fullnodes)
1237 fullnodes=fullnodes)
1236
1238
1237 def _makecg3packer(repo, filematcher, bundlecaps, ellipses=False,
1239 def _makecg3packer(repo, filematcher, bundlecaps, ellipses=False,
1238 shallow=False, ellipsisroots=None, fullnodes=None):
1240 shallow=False, ellipsisroots=None, fullnodes=None):
1239 builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
1241 builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
1240 d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags)
1242 d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags)
1241
1243
1242 deltaparentfn = (_deltaparentellipses if ellipses
1244 deltaparentfn = (_deltaparentellipses if ellipses
1243 else _deltaparentgeneraldelta)
1245 else _deltaparentgeneraldelta)
1244
1246
1245 return cgpacker(repo, filematcher, b'03',
1247 return cgpacker(repo, filematcher, b'03',
1246 deltaparentfn=deltaparentfn,
1248 deltaparentfn=deltaparentfn,
1247 allowreorder=False,
1249 allowreorder=False,
1248 builddeltaheader=builddeltaheader,
1250 builddeltaheader=builddeltaheader,
1249 manifestsend=closechunk(),
1251 manifestsend=closechunk(),
1250 bundlecaps=bundlecaps,
1252 bundlecaps=bundlecaps,
1251 ellipses=ellipses,
1253 ellipses=ellipses,
1252 shallow=shallow,
1254 shallow=shallow,
1253 ellipsisroots=ellipsisroots,
1255 ellipsisroots=ellipsisroots,
1254 fullnodes=fullnodes)
1256 fullnodes=fullnodes)
1255
1257
1256 _packermap = {'01': (_makecg1packer, cg1unpacker),
1258 _packermap = {'01': (_makecg1packer, cg1unpacker),
1257 # cg2 adds support for exchanging generaldelta
1259 # cg2 adds support for exchanging generaldelta
1258 '02': (_makecg2packer, cg2unpacker),
1260 '02': (_makecg2packer, cg2unpacker),
1259 # cg3 adds support for exchanging revlog flags and treemanifests
1261 # cg3 adds support for exchanging revlog flags and treemanifests
1260 '03': (_makecg3packer, cg3unpacker),
1262 '03': (_makecg3packer, cg3unpacker),
1261 }
1263 }
1262
1264
1263 def allsupportedversions(repo):
1265 def allsupportedversions(repo):
1264 versions = set(_packermap.keys())
1266 versions = set(_packermap.keys())
1265 if not (repo.ui.configbool('experimental', 'changegroup3') or
1267 if not (repo.ui.configbool('experimental', 'changegroup3') or
1266 repo.ui.configbool('experimental', 'treemanifest') or
1268 repo.ui.configbool('experimental', 'treemanifest') or
1267 'treemanifest' in repo.requirements):
1269 'treemanifest' in repo.requirements):
1268 versions.discard('03')
1270 versions.discard('03')
1269 return versions
1271 return versions
1270
1272
1271 # Changegroup versions that can be applied to the repo
1273 # Changegroup versions that can be applied to the repo
1272 def supportedincomingversions(repo):
1274 def supportedincomingversions(repo):
1273 return allsupportedversions(repo)
1275 return allsupportedversions(repo)
1274
1276
1275 # Changegroup versions that can be created from the repo
1277 # Changegroup versions that can be created from the repo
1276 def supportedoutgoingversions(repo):
1278 def supportedoutgoingversions(repo):
1277 versions = allsupportedversions(repo)
1279 versions = allsupportedversions(repo)
1278 if 'treemanifest' in repo.requirements:
1280 if 'treemanifest' in repo.requirements:
1279 # Versions 01 and 02 support only flat manifests and it's just too
1281 # Versions 01 and 02 support only flat manifests and it's just too
1280 # expensive to convert between the flat manifest and tree manifest on
1282 # expensive to convert between the flat manifest and tree manifest on
1281 # the fly. Since tree manifests are hashed differently, all of history
1283 # the fly. Since tree manifests are hashed differently, all of history
1282 # would have to be converted. Instead, we simply don't even pretend to
1284 # would have to be converted. Instead, we simply don't even pretend to
1283 # support versions 01 and 02.
1285 # support versions 01 and 02.
1284 versions.discard('01')
1286 versions.discard('01')
1285 versions.discard('02')
1287 versions.discard('02')
1286 if repository.NARROW_REQUIREMENT in repo.requirements:
1288 if repository.NARROW_REQUIREMENT in repo.requirements:
1287 # Versions 01 and 02 don't support revlog flags, and we need to
1289 # Versions 01 and 02 don't support revlog flags, and we need to
1288 # support that for stripping and unbundling to work.
1290 # support that for stripping and unbundling to work.
1289 versions.discard('01')
1291 versions.discard('01')
1290 versions.discard('02')
1292 versions.discard('02')
1291 if LFS_REQUIREMENT in repo.requirements:
1293 if LFS_REQUIREMENT in repo.requirements:
1292 # Versions 01 and 02 don't support revlog flags, and we need to
1294 # Versions 01 and 02 don't support revlog flags, and we need to
1293 # mark LFS entries with REVIDX_EXTSTORED.
1295 # mark LFS entries with REVIDX_EXTSTORED.
1294 versions.discard('01')
1296 versions.discard('01')
1295 versions.discard('02')
1297 versions.discard('02')
1296
1298
1297 return versions
1299 return versions
1298
1300
1299 def localversion(repo):
1301 def localversion(repo):
1300 # Finds the best version to use for bundles that are meant to be used
1302 # Finds the best version to use for bundles that are meant to be used
1301 # locally, such as those from strip and shelve, and temporary bundles.
1303 # locally, such as those from strip and shelve, and temporary bundles.
1302 return max(supportedoutgoingversions(repo))
1304 return max(supportedoutgoingversions(repo))
1303
1305
1304 def safeversion(repo):
1306 def safeversion(repo):
1305 # Finds the smallest version that it's safe to assume clients of the repo
1307 # Finds the smallest version that it's safe to assume clients of the repo
1306 # will support. For example, all hg versions that support generaldelta also
1308 # will support. For example, all hg versions that support generaldelta also
1307 # support changegroup 02.
1309 # support changegroup 02.
1308 versions = supportedoutgoingversions(repo)
1310 versions = supportedoutgoingversions(repo)
1309 if 'generaldelta' in repo.requirements:
1311 if 'generaldelta' in repo.requirements:
1310 versions.discard('01')
1312 versions.discard('01')
1311 assert versions
1313 assert versions
1312 return min(versions)
1314 return min(versions)
1313
1315
1314 def getbundler(version, repo, bundlecaps=None, filematcher=None,
1316 def getbundler(version, repo, bundlecaps=None, filematcher=None,
1315 ellipses=False, shallow=False, ellipsisroots=None,
1317 ellipses=False, shallow=False, ellipsisroots=None,
1316 fullnodes=None):
1318 fullnodes=None):
1317 assert version in supportedoutgoingversions(repo)
1319 assert version in supportedoutgoingversions(repo)
1318
1320
1319 if filematcher is None:
1321 if filematcher is None:
1320 filematcher = matchmod.alwaysmatcher(repo.root, '')
1322 filematcher = matchmod.alwaysmatcher(repo.root, '')
1321
1323
1322 if version == '01' and not filematcher.always():
1324 if version == '01' and not filematcher.always():
1323 raise error.ProgrammingError('version 01 changegroups do not support '
1325 raise error.ProgrammingError('version 01 changegroups do not support '
1324 'sparse file matchers')
1326 'sparse file matchers')
1325
1327
1326 if ellipses and version in (b'01', b'02'):
1328 if ellipses and version in (b'01', b'02'):
1327 raise error.Abort(
1329 raise error.Abort(
1328 _('ellipsis nodes require at least cg3 on client and server, '
1330 _('ellipsis nodes require at least cg3 on client and server, '
1329 'but negotiated version %s') % version)
1331 'but negotiated version %s') % version)
1330
1332
1331 # Requested files could include files not in the local store. So
1333 # Requested files could include files not in the local store. So
1332 # filter those out.
1334 # filter those out.
1333 filematcher = matchmod.intersectmatchers(repo.narrowmatch(),
1335 filematcher = matchmod.intersectmatchers(repo.narrowmatch(),
1334 filematcher)
1336 filematcher)
1335
1337
1336 fn = _packermap[version][0]
1338 fn = _packermap[version][0]
1337 return fn(repo, filematcher, bundlecaps, ellipses=ellipses,
1339 return fn(repo, filematcher, bundlecaps, ellipses=ellipses,
1338 shallow=shallow, ellipsisroots=ellipsisroots,
1340 shallow=shallow, ellipsisroots=ellipsisroots,
1339 fullnodes=fullnodes)
1341 fullnodes=fullnodes)
1340
1342
1341 def getunbundler(version, fh, alg, extras=None):
1343 def getunbundler(version, fh, alg, extras=None):
1342 return _packermap[version][1](fh, alg, extras=extras)
1344 return _packermap[version][1](fh, alg, extras=extras)
1343
1345
1344 def _changegroupinfo(repo, nodes, source):
1346 def _changegroupinfo(repo, nodes, source):
1345 if repo.ui.verbose or source == 'bundle':
1347 if repo.ui.verbose or source == 'bundle':
1346 repo.ui.status(_("%d changesets found\n") % len(nodes))
1348 repo.ui.status(_("%d changesets found\n") % len(nodes))
1347 if repo.ui.debugflag:
1349 if repo.ui.debugflag:
1348 repo.ui.debug("list of changesets:\n")
1350 repo.ui.debug("list of changesets:\n")
1349 for node in nodes:
1351 for node in nodes:
1350 repo.ui.debug("%s\n" % hex(node))
1352 repo.ui.debug("%s\n" % hex(node))
1351
1353
1352 def makechangegroup(repo, outgoing, version, source, fastpath=False,
1354 def makechangegroup(repo, outgoing, version, source, fastpath=False,
1353 bundlecaps=None):
1355 bundlecaps=None):
1354 cgstream = makestream(repo, outgoing, version, source,
1356 cgstream = makestream(repo, outgoing, version, source,
1355 fastpath=fastpath, bundlecaps=bundlecaps)
1357 fastpath=fastpath, bundlecaps=bundlecaps)
1356 return getunbundler(version, util.chunkbuffer(cgstream), None,
1358 return getunbundler(version, util.chunkbuffer(cgstream), None,
1357 {'clcount': len(outgoing.missing) })
1359 {'clcount': len(outgoing.missing) })
1358
1360
1359 def makestream(repo, outgoing, version, source, fastpath=False,
1361 def makestream(repo, outgoing, version, source, fastpath=False,
1360 bundlecaps=None, filematcher=None):
1362 bundlecaps=None, filematcher=None):
1361 bundler = getbundler(version, repo, bundlecaps=bundlecaps,
1363 bundler = getbundler(version, repo, bundlecaps=bundlecaps,
1362 filematcher=filematcher)
1364 filematcher=filematcher)
1363
1365
1364 repo = repo.unfiltered()
1366 repo = repo.unfiltered()
1365 commonrevs = outgoing.common
1367 commonrevs = outgoing.common
1366 csets = outgoing.missing
1368 csets = outgoing.missing
1367 heads = outgoing.missingheads
1369 heads = outgoing.missingheads
1368 # We go through the fast path if we get told to, or if all (unfiltered
1370 # We go through the fast path if we get told to, or if all (unfiltered
1369 # heads have been requested (since we then know there all linkrevs will
1371 # heads have been requested (since we then know there all linkrevs will
1370 # be pulled by the client).
1372 # be pulled by the client).
1371 heads.sort()
1373 heads.sort()
1372 fastpathlinkrev = fastpath or (
1374 fastpathlinkrev = fastpath or (
1373 repo.filtername is None and heads == sorted(repo.heads()))
1375 repo.filtername is None and heads == sorted(repo.heads()))
1374
1376
1375 repo.hook('preoutgoing', throw=True, source=source)
1377 repo.hook('preoutgoing', throw=True, source=source)
1376 _changegroupinfo(repo, csets, source)
1378 _changegroupinfo(repo, csets, source)
1377 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1379 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1378
1380
1379 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
1381 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
1380 revisions = 0
1382 revisions = 0
1381 files = 0
1383 files = 0
1382 progress = repo.ui.makeprogress(_('files'), unit=_('files'),
1384 progress = repo.ui.makeprogress(_('files'), unit=_('files'),
1383 total=expectedfiles)
1385 total=expectedfiles)
1384 for chunkdata in iter(source.filelogheader, {}):
1386 for chunkdata in iter(source.filelogheader, {}):
1385 files += 1
1387 files += 1
1386 f = chunkdata["filename"]
1388 f = chunkdata["filename"]
1387 repo.ui.debug("adding %s revisions\n" % f)
1389 repo.ui.debug("adding %s revisions\n" % f)
1388 progress.increment()
1390 progress.increment()
1389 fl = repo.file(f)
1391 fl = repo.file(f)
1390 o = len(fl)
1392 o = len(fl)
1391 try:
1393 try:
1392 deltas = source.deltaiter()
1394 deltas = source.deltaiter()
1393 if not fl.addgroup(deltas, revmap, trp):
1395 if not fl.addgroup(deltas, revmap, trp):
1394 raise error.Abort(_("received file revlog group is empty"))
1396 raise error.Abort(_("received file revlog group is empty"))
1395 except error.CensoredBaseError as e:
1397 except error.CensoredBaseError as e:
1396 raise error.Abort(_("received delta base is censored: %s") % e)
1398 raise error.Abort(_("received delta base is censored: %s") % e)
1397 revisions += len(fl) - o
1399 revisions += len(fl) - o
1398 if f in needfiles:
1400 if f in needfiles:
1399 needs = needfiles[f]
1401 needs = needfiles[f]
1400 for new in pycompat.xrange(o, len(fl)):
1402 for new in pycompat.xrange(o, len(fl)):
1401 n = fl.node(new)
1403 n = fl.node(new)
1402 if n in needs:
1404 if n in needs:
1403 needs.remove(n)
1405 needs.remove(n)
1404 else:
1406 else:
1405 raise error.Abort(
1407 raise error.Abort(
1406 _("received spurious file revlog entry"))
1408 _("received spurious file revlog entry"))
1407 if not needs:
1409 if not needs:
1408 del needfiles[f]
1410 del needfiles[f]
1409 progress.complete()
1411 progress.complete()
1410
1412
1411 for f, needs in needfiles.iteritems():
1413 for f, needs in needfiles.iteritems():
1412 fl = repo.file(f)
1414 fl = repo.file(f)
1413 for n in needs:
1415 for n in needs:
1414 try:
1416 try:
1415 fl.rev(n)
1417 fl.rev(n)
1416 except error.LookupError:
1418 except error.LookupError:
1417 raise error.Abort(
1419 raise error.Abort(
1418 _('missing file data for %s:%s - run hg verify') %
1420 _('missing file data for %s:%s - run hg verify') %
1419 (f, hex(n)))
1421 (f, hex(n)))
1420
1422
1421 return revisions, files
1423 return revisions, files
General Comments 0
You need to be logged in to leave comments. Login now