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