##// END OF EJS Templates
revlog: use revlog.display_id in narrow error message...
marmoute -
r47927:89e11a6d default
parent child Browse files
Show More
@@ -1,1952 +1,1952 b''
1 # changegroup.py - Mercurial changegroup manipulation functions
1 # changegroup.py - Mercurial changegroup manipulation functions
2 #
2 #
3 # Copyright 2006 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2006 Olivia Mackall <olivia@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 nullrev,
17 nullrev,
18 short,
18 short,
19 )
19 )
20 from .pycompat import open
20 from .pycompat import open
21
21
22 from . import (
22 from . import (
23 error,
23 error,
24 match as matchmod,
24 match as matchmod,
25 mdiff,
25 mdiff,
26 phases,
26 phases,
27 pycompat,
27 pycompat,
28 requirements,
28 requirements,
29 scmutil,
29 scmutil,
30 util,
30 util,
31 )
31 )
32
32
33 from .interfaces import repository
33 from .interfaces import repository
34 from .revlogutils import sidedata as sidedatamod
34 from .revlogutils import sidedata as sidedatamod
35 from .revlogutils import constants as revlog_constants
35 from .revlogutils import constants as revlog_constants
36 from .utils import storageutil
36 from .utils import storageutil
37
37
38 _CHANGEGROUPV1_DELTA_HEADER = struct.Struct(b"20s20s20s20s")
38 _CHANGEGROUPV1_DELTA_HEADER = struct.Struct(b"20s20s20s20s")
39 _CHANGEGROUPV2_DELTA_HEADER = struct.Struct(b"20s20s20s20s20s")
39 _CHANGEGROUPV2_DELTA_HEADER = struct.Struct(b"20s20s20s20s20s")
40 _CHANGEGROUPV3_DELTA_HEADER = struct.Struct(b">20s20s20s20s20sH")
40 _CHANGEGROUPV3_DELTA_HEADER = struct.Struct(b">20s20s20s20s20sH")
41 _CHANGEGROUPV4_DELTA_HEADER = struct.Struct(b">B20s20s20s20s20sH")
41 _CHANGEGROUPV4_DELTA_HEADER = struct.Struct(b">B20s20s20s20s20sH")
42
42
43 LFS_REQUIREMENT = b'lfs'
43 LFS_REQUIREMENT = b'lfs'
44
44
45 readexactly = util.readexactly
45 readexactly = util.readexactly
46
46
47
47
48 def getchunk(stream):
48 def getchunk(stream):
49 """return the next chunk from stream as a string"""
49 """return the next chunk from stream as a string"""
50 d = readexactly(stream, 4)
50 d = readexactly(stream, 4)
51 l = struct.unpack(b">l", d)[0]
51 l = struct.unpack(b">l", d)[0]
52 if l <= 4:
52 if l <= 4:
53 if l:
53 if l:
54 raise error.Abort(_(b"invalid chunk length %d") % l)
54 raise error.Abort(_(b"invalid chunk length %d") % l)
55 return b""
55 return b""
56 return readexactly(stream, l - 4)
56 return readexactly(stream, l - 4)
57
57
58
58
59 def chunkheader(length):
59 def chunkheader(length):
60 """return a changegroup chunk header (string)"""
60 """return a changegroup chunk header (string)"""
61 return struct.pack(b">l", length + 4)
61 return struct.pack(b">l", length + 4)
62
62
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(b">l", 0)
66 return struct.pack(b">l", 0)
67
67
68
68
69 def _fileheader(path):
69 def _fileheader(path):
70 """Obtain a changegroup chunk header for a named path."""
70 """Obtain a changegroup chunk header for a named path."""
71 return chunkheader(len(path)) + path
71 return chunkheader(len(path)) + path
72
72
73
73
74 def writechunks(ui, chunks, filename, vfs=None):
74 def writechunks(ui, chunks, filename, vfs=None):
75 """Write chunks to a file and return its filename.
75 """Write chunks to a file and return its filename.
76
76
77 The stream is assumed to be a bundle file.
77 The stream is assumed to be a bundle file.
78 Existing files will not be overwritten.
78 Existing files will not be overwritten.
79 If no filename is specified, a temporary file is created.
79 If no filename is specified, a temporary file is created.
80 """
80 """
81 fh = None
81 fh = None
82 cleanup = None
82 cleanup = None
83 try:
83 try:
84 if filename:
84 if filename:
85 if vfs:
85 if vfs:
86 fh = vfs.open(filename, b"wb")
86 fh = vfs.open(filename, b"wb")
87 else:
87 else:
88 # Increase default buffer size because default is usually
88 # Increase default buffer size because default is usually
89 # small (4k is common on Linux).
89 # small (4k is common on Linux).
90 fh = open(filename, b"wb", 131072)
90 fh = open(filename, b"wb", 131072)
91 else:
91 else:
92 fd, filename = pycompat.mkstemp(prefix=b"hg-bundle-", suffix=b".hg")
92 fd, filename = pycompat.mkstemp(prefix=b"hg-bundle-", suffix=b".hg")
93 fh = os.fdopen(fd, "wb")
93 fh = os.fdopen(fd, "wb")
94 cleanup = filename
94 cleanup = filename
95 for c in chunks:
95 for c in chunks:
96 fh.write(c)
96 fh.write(c)
97 cleanup = None
97 cleanup = None
98 return filename
98 return filename
99 finally:
99 finally:
100 if fh is not None:
100 if fh is not None:
101 fh.close()
101 fh.close()
102 if cleanup is not None:
102 if cleanup is not None:
103 if filename and vfs:
103 if filename and vfs:
104 vfs.unlink(cleanup)
104 vfs.unlink(cleanup)
105 else:
105 else:
106 os.unlink(cleanup)
106 os.unlink(cleanup)
107
107
108
108
109 class cg1unpacker(object):
109 class cg1unpacker(object):
110 """Unpacker for cg1 changegroup streams.
110 """Unpacker for cg1 changegroup streams.
111
111
112 A changegroup unpacker handles the framing of the revision data in
112 A changegroup unpacker handles the framing of the revision data in
113 the wire format. Most consumers will want to use the apply()
113 the wire format. Most consumers will want to use the apply()
114 method to add the changes from the changegroup to a repository.
114 method to add the changes from the changegroup to a repository.
115
115
116 If you're forwarding a changegroup unmodified to another consumer,
116 If you're forwarding a changegroup unmodified to another consumer,
117 use getchunks(), which returns an iterator of changegroup
117 use getchunks(), which returns an iterator of changegroup
118 chunks. This is mostly useful for cases where you need to know the
118 chunks. This is mostly useful for cases where you need to know the
119 data stream has ended by observing the end of the changegroup.
119 data stream has ended by observing the end of the changegroup.
120
120
121 deltachunk() is useful only if you're applying delta data. Most
121 deltachunk() is useful only if you're applying delta data. Most
122 consumers should prefer apply() instead.
122 consumers should prefer apply() instead.
123
123
124 A few other public methods exist. Those are used only for
124 A few other public methods exist. Those are used only for
125 bundlerepo and some debug commands - their use is discouraged.
125 bundlerepo and some debug commands - their use is discouraged.
126 """
126 """
127
127
128 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
128 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
129 deltaheadersize = deltaheader.size
129 deltaheadersize = deltaheader.size
130 version = b'01'
130 version = b'01'
131 _grouplistcount = 1 # One list of files after the manifests
131 _grouplistcount = 1 # One list of files after the manifests
132
132
133 def __init__(self, fh, alg, extras=None):
133 def __init__(self, fh, alg, extras=None):
134 if alg is None:
134 if alg is None:
135 alg = b'UN'
135 alg = b'UN'
136 if alg not in util.compengines.supportedbundletypes:
136 if alg not in util.compengines.supportedbundletypes:
137 raise error.Abort(_(b'unknown stream compression type: %s') % alg)
137 raise error.Abort(_(b'unknown stream compression type: %s') % alg)
138 if alg == b'BZ':
138 if alg == b'BZ':
139 alg = b'_truncatedBZ'
139 alg = b'_truncatedBZ'
140
140
141 compengine = util.compengines.forbundletype(alg)
141 compengine = util.compengines.forbundletype(alg)
142 self._stream = compengine.decompressorreader(fh)
142 self._stream = compengine.decompressorreader(fh)
143 self._type = alg
143 self._type = alg
144 self.extras = extras or {}
144 self.extras = extras or {}
145 self.callback = None
145 self.callback = None
146
146
147 # These methods (compressed, read, seek, tell) all appear to only
147 # These methods (compressed, read, seek, tell) all appear to only
148 # be used by bundlerepo, but it's a little hard to tell.
148 # be used by bundlerepo, but it's a little hard to tell.
149 def compressed(self):
149 def compressed(self):
150 return self._type is not None and self._type != b'UN'
150 return self._type is not None and self._type != b'UN'
151
151
152 def read(self, l):
152 def read(self, l):
153 return self._stream.read(l)
153 return self._stream.read(l)
154
154
155 def seek(self, pos):
155 def seek(self, pos):
156 return self._stream.seek(pos)
156 return self._stream.seek(pos)
157
157
158 def tell(self):
158 def tell(self):
159 return self._stream.tell()
159 return self._stream.tell()
160
160
161 def close(self):
161 def close(self):
162 return self._stream.close()
162 return self._stream.close()
163
163
164 def _chunklength(self):
164 def _chunklength(self):
165 d = readexactly(self._stream, 4)
165 d = readexactly(self._stream, 4)
166 l = struct.unpack(b">l", d)[0]
166 l = struct.unpack(b">l", d)[0]
167 if l <= 4:
167 if l <= 4:
168 if l:
168 if l:
169 raise error.Abort(_(b"invalid chunk length %d") % l)
169 raise error.Abort(_(b"invalid chunk length %d") % l)
170 return 0
170 return 0
171 if self.callback:
171 if self.callback:
172 self.callback()
172 self.callback()
173 return l - 4
173 return l - 4
174
174
175 def changelogheader(self):
175 def changelogheader(self):
176 """v10 does not have a changelog header chunk"""
176 """v10 does not have a changelog header chunk"""
177 return {}
177 return {}
178
178
179 def manifestheader(self):
179 def manifestheader(self):
180 """v10 does not have a manifest header chunk"""
180 """v10 does not have a manifest header chunk"""
181 return {}
181 return {}
182
182
183 def filelogheader(self):
183 def filelogheader(self):
184 """return the header of the filelogs chunk, v10 only has the filename"""
184 """return the header of the filelogs chunk, v10 only has the filename"""
185 l = self._chunklength()
185 l = self._chunklength()
186 if not l:
186 if not l:
187 return {}
187 return {}
188 fname = readexactly(self._stream, l)
188 fname = readexactly(self._stream, l)
189 return {b'filename': fname}
189 return {b'filename': fname}
190
190
191 def _deltaheader(self, headertuple, prevnode):
191 def _deltaheader(self, headertuple, prevnode):
192 node, p1, p2, cs = headertuple
192 node, p1, p2, cs = headertuple
193 if prevnode is None:
193 if prevnode is None:
194 deltabase = p1
194 deltabase = p1
195 else:
195 else:
196 deltabase = prevnode
196 deltabase = prevnode
197 flags = 0
197 flags = 0
198 protocol_flags = 0
198 protocol_flags = 0
199 return node, p1, p2, deltabase, cs, flags, protocol_flags
199 return node, p1, p2, deltabase, cs, flags, protocol_flags
200
200
201 def deltachunk(self, prevnode):
201 def deltachunk(self, prevnode):
202 l = self._chunklength()
202 l = self._chunklength()
203 if not l:
203 if not l:
204 return {}
204 return {}
205 headerdata = readexactly(self._stream, self.deltaheadersize)
205 headerdata = readexactly(self._stream, self.deltaheadersize)
206 header = self.deltaheader.unpack(headerdata)
206 header = self.deltaheader.unpack(headerdata)
207 delta = readexactly(self._stream, l - self.deltaheadersize)
207 delta = readexactly(self._stream, l - self.deltaheadersize)
208 header = self._deltaheader(header, prevnode)
208 header = self._deltaheader(header, prevnode)
209 node, p1, p2, deltabase, cs, flags, protocol_flags = header
209 node, p1, p2, deltabase, cs, flags, protocol_flags = header
210 return node, p1, p2, cs, deltabase, delta, flags, protocol_flags
210 return node, p1, p2, cs, deltabase, delta, flags, protocol_flags
211
211
212 def getchunks(self):
212 def getchunks(self):
213 """returns all the chunks contains in the bundle
213 """returns all the chunks contains in the bundle
214
214
215 Used when you need to forward the binary stream to a file or another
215 Used when you need to forward the binary stream to a file or another
216 network API. To do so, it parse the changegroup data, otherwise it will
216 network API. To do so, it parse the changegroup data, otherwise it will
217 block in case of sshrepo because it don't know the end of the stream.
217 block in case of sshrepo because it don't know the end of the stream.
218 """
218 """
219 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
219 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
220 # and a list of filelogs. For changegroup 3, we expect 4 parts:
220 # and a list of filelogs. For changegroup 3, we expect 4 parts:
221 # changelog, manifestlog, a list of tree manifestlogs, and a list of
221 # changelog, manifestlog, a list of tree manifestlogs, and a list of
222 # filelogs.
222 # filelogs.
223 #
223 #
224 # Changelog and manifestlog parts are terminated with empty chunks. The
224 # Changelog and manifestlog parts are terminated with empty chunks. The
225 # tree and file parts are a list of entry sections. Each entry section
225 # tree and file parts are a list of entry sections. Each entry section
226 # is a series of chunks terminating in an empty chunk. The list of these
226 # is a series of chunks terminating in an empty chunk. The list of these
227 # entry sections is terminated in yet another empty chunk, so we know
227 # entry sections is terminated in yet another empty chunk, so we know
228 # we've reached the end of the tree/file list when we reach an empty
228 # we've reached the end of the tree/file list when we reach an empty
229 # chunk that was proceeded by no non-empty chunks.
229 # chunk that was proceeded by no non-empty chunks.
230
230
231 parts = 0
231 parts = 0
232 while parts < 2 + self._grouplistcount:
232 while parts < 2 + self._grouplistcount:
233 noentries = True
233 noentries = True
234 while True:
234 while True:
235 chunk = getchunk(self)
235 chunk = getchunk(self)
236 if not chunk:
236 if not chunk:
237 # The first two empty chunks represent the end of the
237 # The first two empty chunks represent the end of the
238 # changelog and the manifestlog portions. The remaining
238 # changelog and the manifestlog portions. The remaining
239 # empty chunks represent either A) the end of individual
239 # empty chunks represent either A) the end of individual
240 # tree or file entries in the file list, or B) the end of
240 # tree or file entries in the file list, or B) the end of
241 # the entire list. It's the end of the entire list if there
241 # the entire list. It's the end of the entire list if there
242 # were no entries (i.e. noentries is True).
242 # were no entries (i.e. noentries is True).
243 if parts < 2:
243 if parts < 2:
244 parts += 1
244 parts += 1
245 elif noentries:
245 elif noentries:
246 parts += 1
246 parts += 1
247 break
247 break
248 noentries = False
248 noentries = False
249 yield chunkheader(len(chunk))
249 yield chunkheader(len(chunk))
250 pos = 0
250 pos = 0
251 while pos < len(chunk):
251 while pos < len(chunk):
252 next = pos + 2 ** 20
252 next = pos + 2 ** 20
253 yield chunk[pos:next]
253 yield chunk[pos:next]
254 pos = next
254 pos = next
255 yield closechunk()
255 yield closechunk()
256
256
257 def _unpackmanifests(self, repo, revmap, trp, prog, addrevisioncb=None):
257 def _unpackmanifests(self, repo, revmap, trp, prog, addrevisioncb=None):
258 self.callback = prog.increment
258 self.callback = prog.increment
259 # no need to check for empty manifest group here:
259 # no need to check for empty manifest group here:
260 # if the result of the merge of 1 and 2 is the same in 3 and 4,
260 # if the result of the merge of 1 and 2 is the same in 3 and 4,
261 # no new manifest will be created and the manifest group will
261 # no new manifest will be created and the manifest group will
262 # be empty during the pull
262 # be empty during the pull
263 self.manifestheader()
263 self.manifestheader()
264 deltas = self.deltaiter()
264 deltas = self.deltaiter()
265 storage = repo.manifestlog.getstorage(b'')
265 storage = repo.manifestlog.getstorage(b'')
266 storage.addgroup(deltas, revmap, trp, addrevisioncb=addrevisioncb)
266 storage.addgroup(deltas, revmap, trp, addrevisioncb=addrevisioncb)
267 prog.complete()
267 prog.complete()
268 self.callback = None
268 self.callback = None
269
269
270 def apply(
270 def apply(
271 self,
271 self,
272 repo,
272 repo,
273 tr,
273 tr,
274 srctype,
274 srctype,
275 url,
275 url,
276 targetphase=phases.draft,
276 targetphase=phases.draft,
277 expectedtotal=None,
277 expectedtotal=None,
278 sidedata_categories=None,
278 sidedata_categories=None,
279 ):
279 ):
280 """Add the changegroup returned by source.read() to this repo.
280 """Add the changegroup returned by source.read() to this repo.
281 srctype is a string like 'push', 'pull', or 'unbundle'. url is
281 srctype is a string like 'push', 'pull', or 'unbundle'. url is
282 the URL of the repo where this changegroup is coming from.
282 the URL of the repo where this changegroup is coming from.
283
283
284 Return an integer summarizing the change to this repo:
284 Return an integer summarizing the change to this repo:
285 - nothing changed or no source: 0
285 - nothing changed or no source: 0
286 - more heads than before: 1+added heads (2..n)
286 - more heads than before: 1+added heads (2..n)
287 - fewer heads than before: -1-removed heads (-2..-n)
287 - fewer heads than before: -1-removed heads (-2..-n)
288 - number of heads stays the same: 1
288 - number of heads stays the same: 1
289
289
290 `sidedata_categories` is an optional set of the remote's sidedata wanted
290 `sidedata_categories` is an optional set of the remote's sidedata wanted
291 categories.
291 categories.
292 """
292 """
293 repo = repo.unfiltered()
293 repo = repo.unfiltered()
294
294
295 # Only useful if we're adding sidedata categories. If both peers have
295 # Only useful if we're adding sidedata categories. If both peers have
296 # the same categories, then we simply don't do anything.
296 # the same categories, then we simply don't do anything.
297 adding_sidedata = (
297 adding_sidedata = (
298 requirements.REVLOGV2_REQUIREMENT in repo.requirements
298 requirements.REVLOGV2_REQUIREMENT in repo.requirements
299 and self.version == b'04'
299 and self.version == b'04'
300 and srctype == b'pull'
300 and srctype == b'pull'
301 )
301 )
302 if adding_sidedata:
302 if adding_sidedata:
303 sidedata_helpers = sidedatamod.get_sidedata_helpers(
303 sidedata_helpers = sidedatamod.get_sidedata_helpers(
304 repo,
304 repo,
305 sidedata_categories or set(),
305 sidedata_categories or set(),
306 pull=True,
306 pull=True,
307 )
307 )
308 else:
308 else:
309 sidedata_helpers = None
309 sidedata_helpers = None
310
310
311 def csmap(x):
311 def csmap(x):
312 repo.ui.debug(b"add changeset %s\n" % short(x))
312 repo.ui.debug(b"add changeset %s\n" % short(x))
313 return len(cl)
313 return len(cl)
314
314
315 def revmap(x):
315 def revmap(x):
316 return cl.rev(x)
316 return cl.rev(x)
317
317
318 try:
318 try:
319 # The transaction may already carry source information. In this
319 # The transaction may already carry source information. In this
320 # case we use the top level data. We overwrite the argument
320 # case we use the top level data. We overwrite the argument
321 # because we need to use the top level value (if they exist)
321 # because we need to use the top level value (if they exist)
322 # in this function.
322 # in this function.
323 srctype = tr.hookargs.setdefault(b'source', srctype)
323 srctype = tr.hookargs.setdefault(b'source', srctype)
324 tr.hookargs.setdefault(b'url', url)
324 tr.hookargs.setdefault(b'url', url)
325 repo.hook(
325 repo.hook(
326 b'prechangegroup', throw=True, **pycompat.strkwargs(tr.hookargs)
326 b'prechangegroup', throw=True, **pycompat.strkwargs(tr.hookargs)
327 )
327 )
328
328
329 # write changelog data to temp files so concurrent readers
329 # write changelog data to temp files so concurrent readers
330 # will not see an inconsistent view
330 # will not see an inconsistent view
331 cl = repo.changelog
331 cl = repo.changelog
332 cl.delayupdate(tr)
332 cl.delayupdate(tr)
333 oldheads = set(cl.heads())
333 oldheads = set(cl.heads())
334
334
335 trp = weakref.proxy(tr)
335 trp = weakref.proxy(tr)
336 # pull off the changeset group
336 # pull off the changeset group
337 repo.ui.status(_(b"adding changesets\n"))
337 repo.ui.status(_(b"adding changesets\n"))
338 clstart = len(cl)
338 clstart = len(cl)
339 progress = repo.ui.makeprogress(
339 progress = repo.ui.makeprogress(
340 _(b'changesets'), unit=_(b'chunks'), total=expectedtotal
340 _(b'changesets'), unit=_(b'chunks'), total=expectedtotal
341 )
341 )
342 self.callback = progress.increment
342 self.callback = progress.increment
343
343
344 efilesset = set()
344 efilesset = set()
345 duprevs = []
345 duprevs = []
346
346
347 def ondupchangelog(cl, rev):
347 def ondupchangelog(cl, rev):
348 if rev < clstart:
348 if rev < clstart:
349 duprevs.append(rev)
349 duprevs.append(rev)
350
350
351 def onchangelog(cl, rev):
351 def onchangelog(cl, rev):
352 ctx = cl.changelogrevision(rev)
352 ctx = cl.changelogrevision(rev)
353 efilesset.update(ctx.files)
353 efilesset.update(ctx.files)
354 repo.register_changeset(rev, ctx)
354 repo.register_changeset(rev, ctx)
355
355
356 self.changelogheader()
356 self.changelogheader()
357 deltas = self.deltaiter()
357 deltas = self.deltaiter()
358 if not cl.addgroup(
358 if not cl.addgroup(
359 deltas,
359 deltas,
360 csmap,
360 csmap,
361 trp,
361 trp,
362 alwayscache=True,
362 alwayscache=True,
363 addrevisioncb=onchangelog,
363 addrevisioncb=onchangelog,
364 duplicaterevisioncb=ondupchangelog,
364 duplicaterevisioncb=ondupchangelog,
365 ):
365 ):
366 repo.ui.develwarn(
366 repo.ui.develwarn(
367 b'applied empty changelog from changegroup',
367 b'applied empty changelog from changegroup',
368 config=b'warn-empty-changegroup',
368 config=b'warn-empty-changegroup',
369 )
369 )
370 efiles = len(efilesset)
370 efiles = len(efilesset)
371 clend = len(cl)
371 clend = len(cl)
372 changesets = clend - clstart
372 changesets = clend - clstart
373 progress.complete()
373 progress.complete()
374 del deltas
374 del deltas
375 # TODO Python 2.7 removal
375 # TODO Python 2.7 removal
376 # del efilesset
376 # del efilesset
377 efilesset = None
377 efilesset = None
378 self.callback = None
378 self.callback = None
379
379
380 # Keep track of the (non-changelog) revlogs we've updated and their
380 # Keep track of the (non-changelog) revlogs we've updated and their
381 # range of new revisions for sidedata rewrite.
381 # range of new revisions for sidedata rewrite.
382 # TODO do something more efficient than keeping the reference to
382 # TODO do something more efficient than keeping the reference to
383 # the revlogs, especially memory-wise.
383 # the revlogs, especially memory-wise.
384 touched_manifests = {}
384 touched_manifests = {}
385 touched_filelogs = {}
385 touched_filelogs = {}
386
386
387 # pull off the manifest group
387 # pull off the manifest group
388 repo.ui.status(_(b"adding manifests\n"))
388 repo.ui.status(_(b"adding manifests\n"))
389 # We know that we'll never have more manifests than we had
389 # We know that we'll never have more manifests than we had
390 # changesets.
390 # changesets.
391 progress = repo.ui.makeprogress(
391 progress = repo.ui.makeprogress(
392 _(b'manifests'), unit=_(b'chunks'), total=changesets
392 _(b'manifests'), unit=_(b'chunks'), total=changesets
393 )
393 )
394 on_manifest_rev = None
394 on_manifest_rev = None
395 if sidedata_helpers:
395 if sidedata_helpers:
396 if revlog_constants.KIND_MANIFESTLOG in sidedata_helpers[1]:
396 if revlog_constants.KIND_MANIFESTLOG in sidedata_helpers[1]:
397
397
398 def on_manifest_rev(manifest, rev):
398 def on_manifest_rev(manifest, rev):
399 range = touched_manifests.get(manifest)
399 range = touched_manifests.get(manifest)
400 if not range:
400 if not range:
401 touched_manifests[manifest] = (rev, rev)
401 touched_manifests[manifest] = (rev, rev)
402 else:
402 else:
403 assert rev == range[1] + 1
403 assert rev == range[1] + 1
404 touched_manifests[manifest] = (range[0], rev)
404 touched_manifests[manifest] = (range[0], rev)
405
405
406 self._unpackmanifests(
406 self._unpackmanifests(
407 repo,
407 repo,
408 revmap,
408 revmap,
409 trp,
409 trp,
410 progress,
410 progress,
411 addrevisioncb=on_manifest_rev,
411 addrevisioncb=on_manifest_rev,
412 )
412 )
413
413
414 needfiles = {}
414 needfiles = {}
415 if repo.ui.configbool(b'server', b'validate'):
415 if repo.ui.configbool(b'server', b'validate'):
416 cl = repo.changelog
416 cl = repo.changelog
417 ml = repo.manifestlog
417 ml = repo.manifestlog
418 # validate incoming csets have their manifests
418 # validate incoming csets have their manifests
419 for cset in pycompat.xrange(clstart, clend):
419 for cset in pycompat.xrange(clstart, clend):
420 mfnode = cl.changelogrevision(cset).manifest
420 mfnode = cl.changelogrevision(cset).manifest
421 mfest = ml[mfnode].readdelta()
421 mfest = ml[mfnode].readdelta()
422 # store file nodes we must see
422 # store file nodes we must see
423 for f, n in pycompat.iteritems(mfest):
423 for f, n in pycompat.iteritems(mfest):
424 needfiles.setdefault(f, set()).add(n)
424 needfiles.setdefault(f, set()).add(n)
425
425
426 on_filelog_rev = None
426 on_filelog_rev = None
427 if sidedata_helpers:
427 if sidedata_helpers:
428 if revlog_constants.KIND_FILELOG in sidedata_helpers[1]:
428 if revlog_constants.KIND_FILELOG in sidedata_helpers[1]:
429
429
430 def on_filelog_rev(filelog, rev):
430 def on_filelog_rev(filelog, rev):
431 range = touched_filelogs.get(filelog)
431 range = touched_filelogs.get(filelog)
432 if not range:
432 if not range:
433 touched_filelogs[filelog] = (rev, rev)
433 touched_filelogs[filelog] = (rev, rev)
434 else:
434 else:
435 assert rev == range[1] + 1
435 assert rev == range[1] + 1
436 touched_filelogs[filelog] = (range[0], rev)
436 touched_filelogs[filelog] = (range[0], rev)
437
437
438 # process the files
438 # process the files
439 repo.ui.status(_(b"adding file changes\n"))
439 repo.ui.status(_(b"adding file changes\n"))
440 newrevs, newfiles = _addchangegroupfiles(
440 newrevs, newfiles = _addchangegroupfiles(
441 repo,
441 repo,
442 self,
442 self,
443 revmap,
443 revmap,
444 trp,
444 trp,
445 efiles,
445 efiles,
446 needfiles,
446 needfiles,
447 addrevisioncb=on_filelog_rev,
447 addrevisioncb=on_filelog_rev,
448 )
448 )
449
449
450 if sidedata_helpers:
450 if sidedata_helpers:
451 if revlog_constants.KIND_CHANGELOG in sidedata_helpers[1]:
451 if revlog_constants.KIND_CHANGELOG in sidedata_helpers[1]:
452 cl.rewrite_sidedata(sidedata_helpers, clstart, clend - 1)
452 cl.rewrite_sidedata(sidedata_helpers, clstart, clend - 1)
453 for mf, (startrev, endrev) in touched_manifests.items():
453 for mf, (startrev, endrev) in touched_manifests.items():
454 mf.rewrite_sidedata(sidedata_helpers, startrev, endrev)
454 mf.rewrite_sidedata(sidedata_helpers, startrev, endrev)
455 for fl, (startrev, endrev) in touched_filelogs.items():
455 for fl, (startrev, endrev) in touched_filelogs.items():
456 fl.rewrite_sidedata(sidedata_helpers, startrev, endrev)
456 fl.rewrite_sidedata(sidedata_helpers, startrev, endrev)
457
457
458 # making sure the value exists
458 # making sure the value exists
459 tr.changes.setdefault(b'changegroup-count-changesets', 0)
459 tr.changes.setdefault(b'changegroup-count-changesets', 0)
460 tr.changes.setdefault(b'changegroup-count-revisions', 0)
460 tr.changes.setdefault(b'changegroup-count-revisions', 0)
461 tr.changes.setdefault(b'changegroup-count-files', 0)
461 tr.changes.setdefault(b'changegroup-count-files', 0)
462 tr.changes.setdefault(b'changegroup-count-heads', 0)
462 tr.changes.setdefault(b'changegroup-count-heads', 0)
463
463
464 # some code use bundle operation for internal purpose. They usually
464 # some code use bundle operation for internal purpose. They usually
465 # set `ui.quiet` to do this outside of user sight. Size the report
465 # set `ui.quiet` to do this outside of user sight. Size the report
466 # of such operation now happens at the end of the transaction, that
466 # of such operation now happens at the end of the transaction, that
467 # ui.quiet has not direct effect on the output.
467 # ui.quiet has not direct effect on the output.
468 #
468 #
469 # To preserve this intend use an inelegant hack, we fail to report
469 # To preserve this intend use an inelegant hack, we fail to report
470 # the change if `quiet` is set. We should probably move to
470 # the change if `quiet` is set. We should probably move to
471 # something better, but this is a good first step to allow the "end
471 # something better, but this is a good first step to allow the "end
472 # of transaction report" to pass tests.
472 # of transaction report" to pass tests.
473 if not repo.ui.quiet:
473 if not repo.ui.quiet:
474 tr.changes[b'changegroup-count-changesets'] += changesets
474 tr.changes[b'changegroup-count-changesets'] += changesets
475 tr.changes[b'changegroup-count-revisions'] += newrevs
475 tr.changes[b'changegroup-count-revisions'] += newrevs
476 tr.changes[b'changegroup-count-files'] += newfiles
476 tr.changes[b'changegroup-count-files'] += newfiles
477
477
478 deltaheads = 0
478 deltaheads = 0
479 if oldheads:
479 if oldheads:
480 heads = cl.heads()
480 heads = cl.heads()
481 deltaheads += len(heads) - len(oldheads)
481 deltaheads += len(heads) - len(oldheads)
482 for h in heads:
482 for h in heads:
483 if h not in oldheads and repo[h].closesbranch():
483 if h not in oldheads and repo[h].closesbranch():
484 deltaheads -= 1
484 deltaheads -= 1
485
485
486 # see previous comment about checking ui.quiet
486 # see previous comment about checking ui.quiet
487 if not repo.ui.quiet:
487 if not repo.ui.quiet:
488 tr.changes[b'changegroup-count-heads'] += deltaheads
488 tr.changes[b'changegroup-count-heads'] += deltaheads
489 repo.invalidatevolatilesets()
489 repo.invalidatevolatilesets()
490
490
491 if changesets > 0:
491 if changesets > 0:
492 if b'node' not in tr.hookargs:
492 if b'node' not in tr.hookargs:
493 tr.hookargs[b'node'] = hex(cl.node(clstart))
493 tr.hookargs[b'node'] = hex(cl.node(clstart))
494 tr.hookargs[b'node_last'] = hex(cl.node(clend - 1))
494 tr.hookargs[b'node_last'] = hex(cl.node(clend - 1))
495 hookargs = dict(tr.hookargs)
495 hookargs = dict(tr.hookargs)
496 else:
496 else:
497 hookargs = dict(tr.hookargs)
497 hookargs = dict(tr.hookargs)
498 hookargs[b'node'] = hex(cl.node(clstart))
498 hookargs[b'node'] = hex(cl.node(clstart))
499 hookargs[b'node_last'] = hex(cl.node(clend - 1))
499 hookargs[b'node_last'] = hex(cl.node(clend - 1))
500 repo.hook(
500 repo.hook(
501 b'pretxnchangegroup',
501 b'pretxnchangegroup',
502 throw=True,
502 throw=True,
503 **pycompat.strkwargs(hookargs)
503 **pycompat.strkwargs(hookargs)
504 )
504 )
505
505
506 added = pycompat.xrange(clstart, clend)
506 added = pycompat.xrange(clstart, clend)
507 phaseall = None
507 phaseall = None
508 if srctype in (b'push', b'serve'):
508 if srctype in (b'push', b'serve'):
509 # Old servers can not push the boundary themselves.
509 # Old servers can not push the boundary themselves.
510 # New servers won't push the boundary if changeset already
510 # New servers won't push the boundary if changeset already
511 # exists locally as secret
511 # exists locally as secret
512 #
512 #
513 # We should not use added here but the list of all change in
513 # We should not use added here but the list of all change in
514 # the bundle
514 # the bundle
515 if repo.publishing():
515 if repo.publishing():
516 targetphase = phaseall = phases.public
516 targetphase = phaseall = phases.public
517 else:
517 else:
518 # closer target phase computation
518 # closer target phase computation
519
519
520 # Those changesets have been pushed from the
520 # Those changesets have been pushed from the
521 # outside, their phases are going to be pushed
521 # outside, their phases are going to be pushed
522 # alongside. Therefor `targetphase` is
522 # alongside. Therefor `targetphase` is
523 # ignored.
523 # ignored.
524 targetphase = phaseall = phases.draft
524 targetphase = phaseall = phases.draft
525 if added:
525 if added:
526 phases.registernew(repo, tr, targetphase, added)
526 phases.registernew(repo, tr, targetphase, added)
527 if phaseall is not None:
527 if phaseall is not None:
528 if duprevs:
528 if duprevs:
529 duprevs.extend(added)
529 duprevs.extend(added)
530 else:
530 else:
531 duprevs = added
531 duprevs = added
532 phases.advanceboundary(repo, tr, phaseall, [], revs=duprevs)
532 phases.advanceboundary(repo, tr, phaseall, [], revs=duprevs)
533 duprevs = []
533 duprevs = []
534
534
535 if changesets > 0:
535 if changesets > 0:
536
536
537 def runhooks(unused_success):
537 def runhooks(unused_success):
538 # These hooks run when the lock releases, not when the
538 # These hooks run when the lock releases, not when the
539 # transaction closes. So it's possible for the changelog
539 # transaction closes. So it's possible for the changelog
540 # to have changed since we last saw it.
540 # to have changed since we last saw it.
541 if clstart >= len(repo):
541 if clstart >= len(repo):
542 return
542 return
543
543
544 repo.hook(b"changegroup", **pycompat.strkwargs(hookargs))
544 repo.hook(b"changegroup", **pycompat.strkwargs(hookargs))
545
545
546 for rev in added:
546 for rev in added:
547 args = hookargs.copy()
547 args = hookargs.copy()
548 args[b'node'] = hex(cl.node(rev))
548 args[b'node'] = hex(cl.node(rev))
549 del args[b'node_last']
549 del args[b'node_last']
550 repo.hook(b"incoming", **pycompat.strkwargs(args))
550 repo.hook(b"incoming", **pycompat.strkwargs(args))
551
551
552 newheads = [h for h in repo.heads() if h not in oldheads]
552 newheads = [h for h in repo.heads() if h not in oldheads]
553 repo.ui.log(
553 repo.ui.log(
554 b"incoming",
554 b"incoming",
555 b"%d incoming changes - new heads: %s\n",
555 b"%d incoming changes - new heads: %s\n",
556 len(added),
556 len(added),
557 b', '.join([hex(c[:6]) for c in newheads]),
557 b', '.join([hex(c[:6]) for c in newheads]),
558 )
558 )
559
559
560 tr.addpostclose(
560 tr.addpostclose(
561 b'changegroup-runhooks-%020i' % clstart,
561 b'changegroup-runhooks-%020i' % clstart,
562 lambda tr: repo._afterlock(runhooks),
562 lambda tr: repo._afterlock(runhooks),
563 )
563 )
564 finally:
564 finally:
565 repo.ui.flush()
565 repo.ui.flush()
566 # never return 0 here:
566 # never return 0 here:
567 if deltaheads < 0:
567 if deltaheads < 0:
568 ret = deltaheads - 1
568 ret = deltaheads - 1
569 else:
569 else:
570 ret = deltaheads + 1
570 ret = deltaheads + 1
571 return ret
571 return ret
572
572
573 def deltaiter(self):
573 def deltaiter(self):
574 """
574 """
575 returns an iterator of the deltas in this changegroup
575 returns an iterator of the deltas in this changegroup
576
576
577 Useful for passing to the underlying storage system to be stored.
577 Useful for passing to the underlying storage system to be stored.
578 """
578 """
579 chain = None
579 chain = None
580 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
580 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
581 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags, sidedata)
581 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags, sidedata)
582 yield chunkdata
582 yield chunkdata
583 chain = chunkdata[0]
583 chain = chunkdata[0]
584
584
585
585
586 class cg2unpacker(cg1unpacker):
586 class cg2unpacker(cg1unpacker):
587 """Unpacker for cg2 streams.
587 """Unpacker for cg2 streams.
588
588
589 cg2 streams add support for generaldelta, so the delta header
589 cg2 streams add support for generaldelta, so the delta header
590 format is slightly different. All other features about the data
590 format is slightly different. All other features about the data
591 remain the same.
591 remain the same.
592 """
592 """
593
593
594 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
594 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
595 deltaheadersize = deltaheader.size
595 deltaheadersize = deltaheader.size
596 version = b'02'
596 version = b'02'
597
597
598 def _deltaheader(self, headertuple, prevnode):
598 def _deltaheader(self, headertuple, prevnode):
599 node, p1, p2, deltabase, cs = headertuple
599 node, p1, p2, deltabase, cs = headertuple
600 flags = 0
600 flags = 0
601 protocol_flags = 0
601 protocol_flags = 0
602 return node, p1, p2, deltabase, cs, flags, protocol_flags
602 return node, p1, p2, deltabase, cs, flags, protocol_flags
603
603
604
604
605 class cg3unpacker(cg2unpacker):
605 class cg3unpacker(cg2unpacker):
606 """Unpacker for cg3 streams.
606 """Unpacker for cg3 streams.
607
607
608 cg3 streams add support for exchanging treemanifests and revlog
608 cg3 streams add support for exchanging treemanifests and revlog
609 flags. It adds the revlog flags to the delta header and an empty chunk
609 flags. It adds the revlog flags to the delta header and an empty chunk
610 separating manifests and files.
610 separating manifests and files.
611 """
611 """
612
612
613 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
613 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
614 deltaheadersize = deltaheader.size
614 deltaheadersize = deltaheader.size
615 version = b'03'
615 version = b'03'
616 _grouplistcount = 2 # One list of manifests and one list of files
616 _grouplistcount = 2 # One list of manifests and one list of files
617
617
618 def _deltaheader(self, headertuple, prevnode):
618 def _deltaheader(self, headertuple, prevnode):
619 node, p1, p2, deltabase, cs, flags = headertuple
619 node, p1, p2, deltabase, cs, flags = headertuple
620 protocol_flags = 0
620 protocol_flags = 0
621 return node, p1, p2, deltabase, cs, flags, protocol_flags
621 return node, p1, p2, deltabase, cs, flags, protocol_flags
622
622
623 def _unpackmanifests(self, repo, revmap, trp, prog, addrevisioncb=None):
623 def _unpackmanifests(self, repo, revmap, trp, prog, addrevisioncb=None):
624 super(cg3unpacker, self)._unpackmanifests(
624 super(cg3unpacker, self)._unpackmanifests(
625 repo, revmap, trp, prog, addrevisioncb=addrevisioncb
625 repo, revmap, trp, prog, addrevisioncb=addrevisioncb
626 )
626 )
627 for chunkdata in iter(self.filelogheader, {}):
627 for chunkdata in iter(self.filelogheader, {}):
628 # If we get here, there are directory manifests in the changegroup
628 # If we get here, there are directory manifests in the changegroup
629 d = chunkdata[b"filename"]
629 d = chunkdata[b"filename"]
630 repo.ui.debug(b"adding %s revisions\n" % d)
630 repo.ui.debug(b"adding %s revisions\n" % d)
631 deltas = self.deltaiter()
631 deltas = self.deltaiter()
632 if not repo.manifestlog.getstorage(d).addgroup(
632 if not repo.manifestlog.getstorage(d).addgroup(
633 deltas, revmap, trp, addrevisioncb=addrevisioncb
633 deltas, revmap, trp, addrevisioncb=addrevisioncb
634 ):
634 ):
635 raise error.Abort(_(b"received dir revlog group is empty"))
635 raise error.Abort(_(b"received dir revlog group is empty"))
636
636
637
637
638 class cg4unpacker(cg3unpacker):
638 class cg4unpacker(cg3unpacker):
639 """Unpacker for cg4 streams.
639 """Unpacker for cg4 streams.
640
640
641 cg4 streams add support for exchanging sidedata.
641 cg4 streams add support for exchanging sidedata.
642 """
642 """
643
643
644 deltaheader = _CHANGEGROUPV4_DELTA_HEADER
644 deltaheader = _CHANGEGROUPV4_DELTA_HEADER
645 deltaheadersize = deltaheader.size
645 deltaheadersize = deltaheader.size
646 version = b'04'
646 version = b'04'
647
647
648 def _deltaheader(self, headertuple, prevnode):
648 def _deltaheader(self, headertuple, prevnode):
649 protocol_flags, node, p1, p2, deltabase, cs, flags = headertuple
649 protocol_flags, node, p1, p2, deltabase, cs, flags = headertuple
650 return node, p1, p2, deltabase, cs, flags, protocol_flags
650 return node, p1, p2, deltabase, cs, flags, protocol_flags
651
651
652 def deltachunk(self, prevnode):
652 def deltachunk(self, prevnode):
653 res = super(cg4unpacker, self).deltachunk(prevnode)
653 res = super(cg4unpacker, self).deltachunk(prevnode)
654 if not res:
654 if not res:
655 return res
655 return res
656
656
657 (node, p1, p2, cs, deltabase, delta, flags, protocol_flags) = res
657 (node, p1, p2, cs, deltabase, delta, flags, protocol_flags) = res
658
658
659 sidedata = {}
659 sidedata = {}
660 if protocol_flags & storageutil.CG_FLAG_SIDEDATA:
660 if protocol_flags & storageutil.CG_FLAG_SIDEDATA:
661 sidedata_raw = getchunk(self._stream)
661 sidedata_raw = getchunk(self._stream)
662 sidedata = sidedatamod.deserialize_sidedata(sidedata_raw)
662 sidedata = sidedatamod.deserialize_sidedata(sidedata_raw)
663
663
664 return node, p1, p2, cs, deltabase, delta, flags, sidedata
664 return node, p1, p2, cs, deltabase, delta, flags, sidedata
665
665
666
666
667 class headerlessfixup(object):
667 class headerlessfixup(object):
668 def __init__(self, fh, h):
668 def __init__(self, fh, h):
669 self._h = h
669 self._h = h
670 self._fh = fh
670 self._fh = fh
671
671
672 def read(self, n):
672 def read(self, n):
673 if self._h:
673 if self._h:
674 d, self._h = self._h[:n], self._h[n:]
674 d, self._h = self._h[:n], self._h[n:]
675 if len(d) < n:
675 if len(d) < n:
676 d += readexactly(self._fh, n - len(d))
676 d += readexactly(self._fh, n - len(d))
677 return d
677 return d
678 return readexactly(self._fh, n)
678 return readexactly(self._fh, n)
679
679
680
680
681 def _revisiondeltatochunks(repo, delta, headerfn):
681 def _revisiondeltatochunks(repo, delta, headerfn):
682 """Serialize a revisiondelta to changegroup chunks."""
682 """Serialize a revisiondelta to changegroup chunks."""
683
683
684 # The captured revision delta may be encoded as a delta against
684 # The captured revision delta may be encoded as a delta against
685 # a base revision or as a full revision. The changegroup format
685 # a base revision or as a full revision. The changegroup format
686 # requires that everything on the wire be deltas. So for full
686 # requires that everything on the wire be deltas. So for full
687 # revisions, we need to invent a header that says to rewrite
687 # revisions, we need to invent a header that says to rewrite
688 # data.
688 # data.
689
689
690 if delta.delta is not None:
690 if delta.delta is not None:
691 prefix, data = b'', delta.delta
691 prefix, data = b'', delta.delta
692 elif delta.basenode == repo.nullid:
692 elif delta.basenode == repo.nullid:
693 data = delta.revision
693 data = delta.revision
694 prefix = mdiff.trivialdiffheader(len(data))
694 prefix = mdiff.trivialdiffheader(len(data))
695 else:
695 else:
696 data = delta.revision
696 data = delta.revision
697 prefix = mdiff.replacediffheader(delta.baserevisionsize, len(data))
697 prefix = mdiff.replacediffheader(delta.baserevisionsize, len(data))
698
698
699 meta = headerfn(delta)
699 meta = headerfn(delta)
700
700
701 yield chunkheader(len(meta) + len(prefix) + len(data))
701 yield chunkheader(len(meta) + len(prefix) + len(data))
702 yield meta
702 yield meta
703 if prefix:
703 if prefix:
704 yield prefix
704 yield prefix
705 yield data
705 yield data
706
706
707 if delta.protocol_flags & storageutil.CG_FLAG_SIDEDATA:
707 if delta.protocol_flags & storageutil.CG_FLAG_SIDEDATA:
708 # Need a separate chunk for sidedata to be able to differentiate
708 # Need a separate chunk for sidedata to be able to differentiate
709 # "raw delta" length and sidedata length
709 # "raw delta" length and sidedata length
710 sidedata = delta.sidedata
710 sidedata = delta.sidedata
711 yield chunkheader(len(sidedata))
711 yield chunkheader(len(sidedata))
712 yield sidedata
712 yield sidedata
713
713
714
714
715 def _sortnodesellipsis(store, nodes, cl, lookup):
715 def _sortnodesellipsis(store, nodes, cl, lookup):
716 """Sort nodes for changegroup generation."""
716 """Sort nodes for changegroup generation."""
717 # Ellipses serving mode.
717 # Ellipses serving mode.
718 #
718 #
719 # In a perfect world, we'd generate better ellipsis-ified graphs
719 # In a perfect world, we'd generate better ellipsis-ified graphs
720 # for non-changelog revlogs. In practice, we haven't started doing
720 # for non-changelog revlogs. In practice, we haven't started doing
721 # that yet, so the resulting DAGs for the manifestlog and filelogs
721 # that yet, so the resulting DAGs for the manifestlog and filelogs
722 # are actually full of bogus parentage on all the ellipsis
722 # are actually full of bogus parentage on all the ellipsis
723 # nodes. This has the side effect that, while the contents are
723 # nodes. This has the side effect that, while the contents are
724 # correct, the individual DAGs might be completely out of whack in
724 # correct, the individual DAGs might be completely out of whack in
725 # a case like 882681bc3166 and its ancestors (back about 10
725 # a case like 882681bc3166 and its ancestors (back about 10
726 # revisions or so) in the main hg repo.
726 # revisions or so) in the main hg repo.
727 #
727 #
728 # The one invariant we *know* holds is that the new (potentially
728 # The one invariant we *know* holds is that the new (potentially
729 # bogus) DAG shape will be valid if we order the nodes in the
729 # bogus) DAG shape will be valid if we order the nodes in the
730 # order that they're introduced in dramatis personae by the
730 # order that they're introduced in dramatis personae by the
731 # changelog, so what we do is we sort the non-changelog histories
731 # changelog, so what we do is we sort the non-changelog histories
732 # by the order in which they are used by the changelog.
732 # by the order in which they are used by the changelog.
733 key = lambda n: cl.rev(lookup(n))
733 key = lambda n: cl.rev(lookup(n))
734 return sorted(nodes, key=key)
734 return sorted(nodes, key=key)
735
735
736
736
737 def _resolvenarrowrevisioninfo(
737 def _resolvenarrowrevisioninfo(
738 cl,
738 cl,
739 store,
739 store,
740 ischangelog,
740 ischangelog,
741 rev,
741 rev,
742 linkrev,
742 linkrev,
743 linknode,
743 linknode,
744 clrevtolocalrev,
744 clrevtolocalrev,
745 fullclnodes,
745 fullclnodes,
746 precomputedellipsis,
746 precomputedellipsis,
747 ):
747 ):
748 linkparents = precomputedellipsis[linkrev]
748 linkparents = precomputedellipsis[linkrev]
749
749
750 def local(clrev):
750 def local(clrev):
751 """Turn a changelog revnum into a local revnum.
751 """Turn a changelog revnum into a local revnum.
752
752
753 The ellipsis dag is stored as revnums on the changelog,
753 The ellipsis dag is stored as revnums on the changelog,
754 but when we're producing ellipsis entries for
754 but when we're producing ellipsis entries for
755 non-changelog revlogs, we need to turn those numbers into
755 non-changelog revlogs, we need to turn those numbers into
756 something local. This does that for us, and during the
756 something local. This does that for us, and during the
757 changelog sending phase will also expand the stored
757 changelog sending phase will also expand the stored
758 mappings as needed.
758 mappings as needed.
759 """
759 """
760 if clrev == nullrev:
760 if clrev == nullrev:
761 return nullrev
761 return nullrev
762
762
763 if ischangelog:
763 if ischangelog:
764 return clrev
764 return clrev
765
765
766 # Walk the ellipsis-ized changelog breadth-first looking for a
766 # Walk the ellipsis-ized changelog breadth-first looking for a
767 # change that has been linked from the current revlog.
767 # change that has been linked from the current revlog.
768 #
768 #
769 # For a flat manifest revlog only a single step should be necessary
769 # For a flat manifest revlog only a single step should be necessary
770 # as all relevant changelog entries are relevant to the flat
770 # as all relevant changelog entries are relevant to the flat
771 # manifest.
771 # manifest.
772 #
772 #
773 # For a filelog or tree manifest dirlog however not every changelog
773 # For a filelog or tree manifest dirlog however not every changelog
774 # entry will have been relevant, so we need to skip some changelog
774 # entry will have been relevant, so we need to skip some changelog
775 # nodes even after ellipsis-izing.
775 # nodes even after ellipsis-izing.
776 walk = [clrev]
776 walk = [clrev]
777 while walk:
777 while walk:
778 p = walk[0]
778 p = walk[0]
779 walk = walk[1:]
779 walk = walk[1:]
780 if p in clrevtolocalrev:
780 if p in clrevtolocalrev:
781 return clrevtolocalrev[p]
781 return clrevtolocalrev[p]
782 elif p in fullclnodes:
782 elif p in fullclnodes:
783 walk.extend([pp for pp in cl.parentrevs(p) if pp != nullrev])
783 walk.extend([pp for pp in cl.parentrevs(p) if pp != nullrev])
784 elif p in precomputedellipsis:
784 elif p in precomputedellipsis:
785 walk.extend(
785 walk.extend(
786 [pp for pp in precomputedellipsis[p] if pp != nullrev]
786 [pp for pp in precomputedellipsis[p] if pp != nullrev]
787 )
787 )
788 else:
788 else:
789 # In this case, we've got an ellipsis with parents
789 # In this case, we've got an ellipsis with parents
790 # outside the current bundle (likely an
790 # outside the current bundle (likely an
791 # incremental pull). We "know" that we can use the
791 # incremental pull). We "know" that we can use the
792 # value of this same revlog at whatever revision
792 # value of this same revlog at whatever revision
793 # is pointed to by linknode. "Know" is in scare
793 # is pointed to by linknode. "Know" is in scare
794 # quotes because I haven't done enough examination
794 # quotes because I haven't done enough examination
795 # of edge cases to convince myself this is really
795 # of edge cases to convince myself this is really
796 # a fact - it works for all the (admittedly
796 # a fact - it works for all the (admittedly
797 # thorough) cases in our testsuite, but I would be
797 # thorough) cases in our testsuite, but I would be
798 # somewhat unsurprised to find a case in the wild
798 # somewhat unsurprised to find a case in the wild
799 # where this breaks down a bit. That said, I don't
799 # where this breaks down a bit. That said, I don't
800 # know if it would hurt anything.
800 # know if it would hurt anything.
801 for i in pycompat.xrange(rev, 0, -1):
801 for i in pycompat.xrange(rev, 0, -1):
802 if store.linkrev(i) == clrev:
802 if store.linkrev(i) == clrev:
803 return i
803 return i
804 # We failed to resolve a parent for this node, so
804 # We failed to resolve a parent for this node, so
805 # we crash the changegroup construction.
805 # we crash the changegroup construction.
806 if util.safehasattr(store, 'target'):
806 if util.safehasattr(store, 'target'):
807 target = store._indexfile
807 target = store.display_id
808 else:
808 else:
809 # some revlog not actually a revlog
809 # some revlog not actually a revlog
810 target = store._revlog._indexfile
810 target = store._revlog.display_id
811
811
812 raise error.Abort(
812 raise error.Abort(
813 b"unable to resolve parent while packing '%s' %r"
813 b"unable to resolve parent while packing '%s' %r"
814 b' for changeset %r' % (target, rev, clrev)
814 b' for changeset %r' % (target, rev, clrev)
815 )
815 )
816
816
817 return nullrev
817 return nullrev
818
818
819 if not linkparents or (store.parentrevs(rev) == (nullrev, nullrev)):
819 if not linkparents or (store.parentrevs(rev) == (nullrev, nullrev)):
820 p1, p2 = nullrev, nullrev
820 p1, p2 = nullrev, nullrev
821 elif len(linkparents) == 1:
821 elif len(linkparents) == 1:
822 (p1,) = sorted(local(p) for p in linkparents)
822 (p1,) = sorted(local(p) for p in linkparents)
823 p2 = nullrev
823 p2 = nullrev
824 else:
824 else:
825 p1, p2 = sorted(local(p) for p in linkparents)
825 p1, p2 = sorted(local(p) for p in linkparents)
826
826
827 p1node, p2node = store.node(p1), store.node(p2)
827 p1node, p2node = store.node(p1), store.node(p2)
828
828
829 return p1node, p2node, linknode
829 return p1node, p2node, linknode
830
830
831
831
832 def deltagroup(
832 def deltagroup(
833 repo,
833 repo,
834 store,
834 store,
835 nodes,
835 nodes,
836 ischangelog,
836 ischangelog,
837 lookup,
837 lookup,
838 forcedeltaparentprev,
838 forcedeltaparentprev,
839 topic=None,
839 topic=None,
840 ellipses=False,
840 ellipses=False,
841 clrevtolocalrev=None,
841 clrevtolocalrev=None,
842 fullclnodes=None,
842 fullclnodes=None,
843 precomputedellipsis=None,
843 precomputedellipsis=None,
844 sidedata_helpers=None,
844 sidedata_helpers=None,
845 ):
845 ):
846 """Calculate deltas for a set of revisions.
846 """Calculate deltas for a set of revisions.
847
847
848 Is a generator of ``revisiondelta`` instances.
848 Is a generator of ``revisiondelta`` instances.
849
849
850 If topic is not None, progress detail will be generated using this
850 If topic is not None, progress detail will be generated using this
851 topic name (e.g. changesets, manifests, etc).
851 topic name (e.g. changesets, manifests, etc).
852
852
853 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
853 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
854 `sidedata_helpers`.
854 `sidedata_helpers`.
855 """
855 """
856 if not nodes:
856 if not nodes:
857 return
857 return
858
858
859 cl = repo.changelog
859 cl = repo.changelog
860
860
861 if ischangelog:
861 if ischangelog:
862 # `hg log` shows changesets in storage order. To preserve order
862 # `hg log` shows changesets in storage order. To preserve order
863 # across clones, send out changesets in storage order.
863 # across clones, send out changesets in storage order.
864 nodesorder = b'storage'
864 nodesorder = b'storage'
865 elif ellipses:
865 elif ellipses:
866 nodes = _sortnodesellipsis(store, nodes, cl, lookup)
866 nodes = _sortnodesellipsis(store, nodes, cl, lookup)
867 nodesorder = b'nodes'
867 nodesorder = b'nodes'
868 else:
868 else:
869 nodesorder = None
869 nodesorder = None
870
870
871 # Perform ellipses filtering and revision massaging. We do this before
871 # Perform ellipses filtering and revision massaging. We do this before
872 # emitrevisions() because a) filtering out revisions creates less work
872 # emitrevisions() because a) filtering out revisions creates less work
873 # for emitrevisions() b) dropping revisions would break emitrevisions()'s
873 # for emitrevisions() b) dropping revisions would break emitrevisions()'s
874 # assumptions about delta choices and we would possibly send a delta
874 # assumptions about delta choices and we would possibly send a delta
875 # referencing a missing base revision.
875 # referencing a missing base revision.
876 #
876 #
877 # Also, calling lookup() has side-effects with regards to populating
877 # Also, calling lookup() has side-effects with regards to populating
878 # data structures. If we don't call lookup() for each node or if we call
878 # data structures. If we don't call lookup() for each node or if we call
879 # lookup() after the first pass through each node, things can break -
879 # lookup() after the first pass through each node, things can break -
880 # possibly intermittently depending on the python hash seed! For that
880 # possibly intermittently depending on the python hash seed! For that
881 # reason, we store a mapping of all linknodes during the initial node
881 # reason, we store a mapping of all linknodes during the initial node
882 # pass rather than use lookup() on the output side.
882 # pass rather than use lookup() on the output side.
883 if ellipses:
883 if ellipses:
884 filtered = []
884 filtered = []
885 adjustedparents = {}
885 adjustedparents = {}
886 linknodes = {}
886 linknodes = {}
887
887
888 for node in nodes:
888 for node in nodes:
889 rev = store.rev(node)
889 rev = store.rev(node)
890 linknode = lookup(node)
890 linknode = lookup(node)
891 linkrev = cl.rev(linknode)
891 linkrev = cl.rev(linknode)
892 clrevtolocalrev[linkrev] = rev
892 clrevtolocalrev[linkrev] = rev
893
893
894 # If linknode is in fullclnodes, it means the corresponding
894 # If linknode is in fullclnodes, it means the corresponding
895 # changeset was a full changeset and is being sent unaltered.
895 # changeset was a full changeset and is being sent unaltered.
896 if linknode in fullclnodes:
896 if linknode in fullclnodes:
897 linknodes[node] = linknode
897 linknodes[node] = linknode
898
898
899 # If the corresponding changeset wasn't in the set computed
899 # If the corresponding changeset wasn't in the set computed
900 # as relevant to us, it should be dropped outright.
900 # as relevant to us, it should be dropped outright.
901 elif linkrev not in precomputedellipsis:
901 elif linkrev not in precomputedellipsis:
902 continue
902 continue
903
903
904 else:
904 else:
905 # We could probably do this later and avoid the dict
905 # We could probably do this later and avoid the dict
906 # holding state. But it likely doesn't matter.
906 # holding state. But it likely doesn't matter.
907 p1node, p2node, linknode = _resolvenarrowrevisioninfo(
907 p1node, p2node, linknode = _resolvenarrowrevisioninfo(
908 cl,
908 cl,
909 store,
909 store,
910 ischangelog,
910 ischangelog,
911 rev,
911 rev,
912 linkrev,
912 linkrev,
913 linknode,
913 linknode,
914 clrevtolocalrev,
914 clrevtolocalrev,
915 fullclnodes,
915 fullclnodes,
916 precomputedellipsis,
916 precomputedellipsis,
917 )
917 )
918
918
919 adjustedparents[node] = (p1node, p2node)
919 adjustedparents[node] = (p1node, p2node)
920 linknodes[node] = linknode
920 linknodes[node] = linknode
921
921
922 filtered.append(node)
922 filtered.append(node)
923
923
924 nodes = filtered
924 nodes = filtered
925
925
926 # We expect the first pass to be fast, so we only engage the progress
926 # We expect the first pass to be fast, so we only engage the progress
927 # meter for constructing the revision deltas.
927 # meter for constructing the revision deltas.
928 progress = None
928 progress = None
929 if topic is not None:
929 if topic is not None:
930 progress = repo.ui.makeprogress(
930 progress = repo.ui.makeprogress(
931 topic, unit=_(b'chunks'), total=len(nodes)
931 topic, unit=_(b'chunks'), total=len(nodes)
932 )
932 )
933
933
934 configtarget = repo.ui.config(b'devel', b'bundle.delta')
934 configtarget = repo.ui.config(b'devel', b'bundle.delta')
935 if configtarget not in (b'', b'p1', b'full'):
935 if configtarget not in (b'', b'p1', b'full'):
936 msg = _(b"""config "devel.bundle.delta" as unknown value: %s""")
936 msg = _(b"""config "devel.bundle.delta" as unknown value: %s""")
937 repo.ui.warn(msg % configtarget)
937 repo.ui.warn(msg % configtarget)
938
938
939 deltamode = repository.CG_DELTAMODE_STD
939 deltamode = repository.CG_DELTAMODE_STD
940 if forcedeltaparentprev:
940 if forcedeltaparentprev:
941 deltamode = repository.CG_DELTAMODE_PREV
941 deltamode = repository.CG_DELTAMODE_PREV
942 elif configtarget == b'p1':
942 elif configtarget == b'p1':
943 deltamode = repository.CG_DELTAMODE_P1
943 deltamode = repository.CG_DELTAMODE_P1
944 elif configtarget == b'full':
944 elif configtarget == b'full':
945 deltamode = repository.CG_DELTAMODE_FULL
945 deltamode = repository.CG_DELTAMODE_FULL
946
946
947 revisions = store.emitrevisions(
947 revisions = store.emitrevisions(
948 nodes,
948 nodes,
949 nodesorder=nodesorder,
949 nodesorder=nodesorder,
950 revisiondata=True,
950 revisiondata=True,
951 assumehaveparentrevisions=not ellipses,
951 assumehaveparentrevisions=not ellipses,
952 deltamode=deltamode,
952 deltamode=deltamode,
953 sidedata_helpers=sidedata_helpers,
953 sidedata_helpers=sidedata_helpers,
954 )
954 )
955
955
956 for i, revision in enumerate(revisions):
956 for i, revision in enumerate(revisions):
957 if progress:
957 if progress:
958 progress.update(i + 1)
958 progress.update(i + 1)
959
959
960 if ellipses:
960 if ellipses:
961 linknode = linknodes[revision.node]
961 linknode = linknodes[revision.node]
962
962
963 if revision.node in adjustedparents:
963 if revision.node in adjustedparents:
964 p1node, p2node = adjustedparents[revision.node]
964 p1node, p2node = adjustedparents[revision.node]
965 revision.p1node = p1node
965 revision.p1node = p1node
966 revision.p2node = p2node
966 revision.p2node = p2node
967 revision.flags |= repository.REVISION_FLAG_ELLIPSIS
967 revision.flags |= repository.REVISION_FLAG_ELLIPSIS
968
968
969 else:
969 else:
970 linknode = lookup(revision.node)
970 linknode = lookup(revision.node)
971
971
972 revision.linknode = linknode
972 revision.linknode = linknode
973 yield revision
973 yield revision
974
974
975 if progress:
975 if progress:
976 progress.complete()
976 progress.complete()
977
977
978
978
979 class cgpacker(object):
979 class cgpacker(object):
980 def __init__(
980 def __init__(
981 self,
981 self,
982 repo,
982 repo,
983 oldmatcher,
983 oldmatcher,
984 matcher,
984 matcher,
985 version,
985 version,
986 builddeltaheader,
986 builddeltaheader,
987 manifestsend,
987 manifestsend,
988 forcedeltaparentprev=False,
988 forcedeltaparentprev=False,
989 bundlecaps=None,
989 bundlecaps=None,
990 ellipses=False,
990 ellipses=False,
991 shallow=False,
991 shallow=False,
992 ellipsisroots=None,
992 ellipsisroots=None,
993 fullnodes=None,
993 fullnodes=None,
994 remote_sidedata=None,
994 remote_sidedata=None,
995 ):
995 ):
996 """Given a source repo, construct a bundler.
996 """Given a source repo, construct a bundler.
997
997
998 oldmatcher is a matcher that matches on files the client already has.
998 oldmatcher is a matcher that matches on files the client already has.
999 These will not be included in the changegroup.
999 These will not be included in the changegroup.
1000
1000
1001 matcher is a matcher that matches on files to include in the
1001 matcher is a matcher that matches on files to include in the
1002 changegroup. Used to facilitate sparse changegroups.
1002 changegroup. Used to facilitate sparse changegroups.
1003
1003
1004 forcedeltaparentprev indicates whether delta parents must be against
1004 forcedeltaparentprev indicates whether delta parents must be against
1005 the previous revision in a delta group. This should only be used for
1005 the previous revision in a delta group. This should only be used for
1006 compatibility with changegroup version 1.
1006 compatibility with changegroup version 1.
1007
1007
1008 builddeltaheader is a callable that constructs the header for a group
1008 builddeltaheader is a callable that constructs the header for a group
1009 delta.
1009 delta.
1010
1010
1011 manifestsend is a chunk to send after manifests have been fully emitted.
1011 manifestsend is a chunk to send after manifests have been fully emitted.
1012
1012
1013 ellipses indicates whether ellipsis serving mode is enabled.
1013 ellipses indicates whether ellipsis serving mode is enabled.
1014
1014
1015 bundlecaps is optional and can be used to specify the set of
1015 bundlecaps is optional and can be used to specify the set of
1016 capabilities which can be used to build the bundle. While bundlecaps is
1016 capabilities which can be used to build the bundle. While bundlecaps is
1017 unused in core Mercurial, extensions rely on this feature to communicate
1017 unused in core Mercurial, extensions rely on this feature to communicate
1018 capabilities to customize the changegroup packer.
1018 capabilities to customize the changegroup packer.
1019
1019
1020 shallow indicates whether shallow data might be sent. The packer may
1020 shallow indicates whether shallow data might be sent. The packer may
1021 need to pack file contents not introduced by the changes being packed.
1021 need to pack file contents not introduced by the changes being packed.
1022
1022
1023 fullnodes is the set of changelog nodes which should not be ellipsis
1023 fullnodes is the set of changelog nodes which should not be ellipsis
1024 nodes. We store this rather than the set of nodes that should be
1024 nodes. We store this rather than the set of nodes that should be
1025 ellipsis because for very large histories we expect this to be
1025 ellipsis because for very large histories we expect this to be
1026 significantly smaller.
1026 significantly smaller.
1027
1027
1028 remote_sidedata is the set of sidedata categories wanted by the remote.
1028 remote_sidedata is the set of sidedata categories wanted by the remote.
1029 """
1029 """
1030 assert oldmatcher
1030 assert oldmatcher
1031 assert matcher
1031 assert matcher
1032 self._oldmatcher = oldmatcher
1032 self._oldmatcher = oldmatcher
1033 self._matcher = matcher
1033 self._matcher = matcher
1034
1034
1035 self.version = version
1035 self.version = version
1036 self._forcedeltaparentprev = forcedeltaparentprev
1036 self._forcedeltaparentprev = forcedeltaparentprev
1037 self._builddeltaheader = builddeltaheader
1037 self._builddeltaheader = builddeltaheader
1038 self._manifestsend = manifestsend
1038 self._manifestsend = manifestsend
1039 self._ellipses = ellipses
1039 self._ellipses = ellipses
1040
1040
1041 # Set of capabilities we can use to build the bundle.
1041 # Set of capabilities we can use to build the bundle.
1042 if bundlecaps is None:
1042 if bundlecaps is None:
1043 bundlecaps = set()
1043 bundlecaps = set()
1044 self._bundlecaps = bundlecaps
1044 self._bundlecaps = bundlecaps
1045 if remote_sidedata is None:
1045 if remote_sidedata is None:
1046 remote_sidedata = set()
1046 remote_sidedata = set()
1047 self._remote_sidedata = remote_sidedata
1047 self._remote_sidedata = remote_sidedata
1048 self._isshallow = shallow
1048 self._isshallow = shallow
1049 self._fullclnodes = fullnodes
1049 self._fullclnodes = fullnodes
1050
1050
1051 # Maps ellipsis revs to their roots at the changelog level.
1051 # Maps ellipsis revs to their roots at the changelog level.
1052 self._precomputedellipsis = ellipsisroots
1052 self._precomputedellipsis = ellipsisroots
1053
1053
1054 self._repo = repo
1054 self._repo = repo
1055
1055
1056 if self._repo.ui.verbose and not self._repo.ui.debugflag:
1056 if self._repo.ui.verbose and not self._repo.ui.debugflag:
1057 self._verbosenote = self._repo.ui.note
1057 self._verbosenote = self._repo.ui.note
1058 else:
1058 else:
1059 self._verbosenote = lambda s: None
1059 self._verbosenote = lambda s: None
1060
1060
1061 def generate(
1061 def generate(
1062 self, commonrevs, clnodes, fastpathlinkrev, source, changelog=True
1062 self, commonrevs, clnodes, fastpathlinkrev, source, changelog=True
1063 ):
1063 ):
1064 """Yield a sequence of changegroup byte chunks.
1064 """Yield a sequence of changegroup byte chunks.
1065 If changelog is False, changelog data won't be added to changegroup
1065 If changelog is False, changelog data won't be added to changegroup
1066 """
1066 """
1067
1067
1068 repo = self._repo
1068 repo = self._repo
1069 cl = repo.changelog
1069 cl = repo.changelog
1070
1070
1071 self._verbosenote(_(b'uncompressed size of bundle content:\n'))
1071 self._verbosenote(_(b'uncompressed size of bundle content:\n'))
1072 size = 0
1072 size = 0
1073
1073
1074 sidedata_helpers = None
1074 sidedata_helpers = None
1075 if self.version == b'04':
1075 if self.version == b'04':
1076 remote_sidedata = self._remote_sidedata
1076 remote_sidedata = self._remote_sidedata
1077 if source == b'strip':
1077 if source == b'strip':
1078 # We're our own remote when stripping, get the no-op helpers
1078 # We're our own remote when stripping, get the no-op helpers
1079 # TODO a better approach would be for the strip bundle to
1079 # TODO a better approach would be for the strip bundle to
1080 # correctly advertise its sidedata categories directly.
1080 # correctly advertise its sidedata categories directly.
1081 remote_sidedata = repo._wanted_sidedata
1081 remote_sidedata = repo._wanted_sidedata
1082 sidedata_helpers = sidedatamod.get_sidedata_helpers(
1082 sidedata_helpers = sidedatamod.get_sidedata_helpers(
1083 repo, remote_sidedata
1083 repo, remote_sidedata
1084 )
1084 )
1085
1085
1086 clstate, deltas = self._generatechangelog(
1086 clstate, deltas = self._generatechangelog(
1087 cl,
1087 cl,
1088 clnodes,
1088 clnodes,
1089 generate=changelog,
1089 generate=changelog,
1090 sidedata_helpers=sidedata_helpers,
1090 sidedata_helpers=sidedata_helpers,
1091 )
1091 )
1092 for delta in deltas:
1092 for delta in deltas:
1093 for chunk in _revisiondeltatochunks(
1093 for chunk in _revisiondeltatochunks(
1094 self._repo, delta, self._builddeltaheader
1094 self._repo, delta, self._builddeltaheader
1095 ):
1095 ):
1096 size += len(chunk)
1096 size += len(chunk)
1097 yield chunk
1097 yield chunk
1098
1098
1099 close = closechunk()
1099 close = closechunk()
1100 size += len(close)
1100 size += len(close)
1101 yield closechunk()
1101 yield closechunk()
1102
1102
1103 self._verbosenote(_(b'%8.i (changelog)\n') % size)
1103 self._verbosenote(_(b'%8.i (changelog)\n') % size)
1104
1104
1105 clrevorder = clstate[b'clrevorder']
1105 clrevorder = clstate[b'clrevorder']
1106 manifests = clstate[b'manifests']
1106 manifests = clstate[b'manifests']
1107 changedfiles = clstate[b'changedfiles']
1107 changedfiles = clstate[b'changedfiles']
1108
1108
1109 # We need to make sure that the linkrev in the changegroup refers to
1109 # We need to make sure that the linkrev in the changegroup refers to
1110 # the first changeset that introduced the manifest or file revision.
1110 # the first changeset that introduced the manifest or file revision.
1111 # The fastpath is usually safer than the slowpath, because the filelogs
1111 # The fastpath is usually safer than the slowpath, because the filelogs
1112 # are walked in revlog order.
1112 # are walked in revlog order.
1113 #
1113 #
1114 # When taking the slowpath when the manifest revlog uses generaldelta,
1114 # When taking the slowpath when the manifest revlog uses generaldelta,
1115 # the manifest may be walked in the "wrong" order. Without 'clrevorder',
1115 # the manifest may be walked in the "wrong" order. Without 'clrevorder',
1116 # we would get an incorrect linkrev (see fix in cc0ff93d0c0c).
1116 # we would get an incorrect linkrev (see fix in cc0ff93d0c0c).
1117 #
1117 #
1118 # When taking the fastpath, we are only vulnerable to reordering
1118 # When taking the fastpath, we are only vulnerable to reordering
1119 # of the changelog itself. The changelog never uses generaldelta and is
1119 # of the changelog itself. The changelog never uses generaldelta and is
1120 # never reordered. To handle this case, we simply take the slowpath,
1120 # never reordered. To handle this case, we simply take the slowpath,
1121 # which already has the 'clrevorder' logic. This was also fixed in
1121 # which already has the 'clrevorder' logic. This was also fixed in
1122 # cc0ff93d0c0c.
1122 # cc0ff93d0c0c.
1123
1123
1124 # Treemanifests don't work correctly with fastpathlinkrev
1124 # Treemanifests don't work correctly with fastpathlinkrev
1125 # either, because we don't discover which directory nodes to
1125 # either, because we don't discover which directory nodes to
1126 # send along with files. This could probably be fixed.
1126 # send along with files. This could probably be fixed.
1127 fastpathlinkrev = fastpathlinkrev and not scmutil.istreemanifest(repo)
1127 fastpathlinkrev = fastpathlinkrev and not scmutil.istreemanifest(repo)
1128
1128
1129 fnodes = {} # needed file nodes
1129 fnodes = {} # needed file nodes
1130
1130
1131 size = 0
1131 size = 0
1132 it = self.generatemanifests(
1132 it = self.generatemanifests(
1133 commonrevs,
1133 commonrevs,
1134 clrevorder,
1134 clrevorder,
1135 fastpathlinkrev,
1135 fastpathlinkrev,
1136 manifests,
1136 manifests,
1137 fnodes,
1137 fnodes,
1138 source,
1138 source,
1139 clstate[b'clrevtomanifestrev'],
1139 clstate[b'clrevtomanifestrev'],
1140 sidedata_helpers=sidedata_helpers,
1140 sidedata_helpers=sidedata_helpers,
1141 )
1141 )
1142
1142
1143 for tree, deltas in it:
1143 for tree, deltas in it:
1144 if tree:
1144 if tree:
1145 assert self.version in (b'03', b'04')
1145 assert self.version in (b'03', b'04')
1146 chunk = _fileheader(tree)
1146 chunk = _fileheader(tree)
1147 size += len(chunk)
1147 size += len(chunk)
1148 yield chunk
1148 yield chunk
1149
1149
1150 for delta in deltas:
1150 for delta in deltas:
1151 chunks = _revisiondeltatochunks(
1151 chunks = _revisiondeltatochunks(
1152 self._repo, delta, self._builddeltaheader
1152 self._repo, delta, self._builddeltaheader
1153 )
1153 )
1154 for chunk in chunks:
1154 for chunk in chunks:
1155 size += len(chunk)
1155 size += len(chunk)
1156 yield chunk
1156 yield chunk
1157
1157
1158 close = closechunk()
1158 close = closechunk()
1159 size += len(close)
1159 size += len(close)
1160 yield close
1160 yield close
1161
1161
1162 self._verbosenote(_(b'%8.i (manifests)\n') % size)
1162 self._verbosenote(_(b'%8.i (manifests)\n') % size)
1163 yield self._manifestsend
1163 yield self._manifestsend
1164
1164
1165 mfdicts = None
1165 mfdicts = None
1166 if self._ellipses and self._isshallow:
1166 if self._ellipses and self._isshallow:
1167 mfdicts = [
1167 mfdicts = [
1168 (repo.manifestlog[n].read(), lr)
1168 (repo.manifestlog[n].read(), lr)
1169 for (n, lr) in pycompat.iteritems(manifests)
1169 for (n, lr) in pycompat.iteritems(manifests)
1170 ]
1170 ]
1171
1171
1172 manifests.clear()
1172 manifests.clear()
1173 clrevs = {cl.rev(x) for x in clnodes}
1173 clrevs = {cl.rev(x) for x in clnodes}
1174
1174
1175 it = self.generatefiles(
1175 it = self.generatefiles(
1176 changedfiles,
1176 changedfiles,
1177 commonrevs,
1177 commonrevs,
1178 source,
1178 source,
1179 mfdicts,
1179 mfdicts,
1180 fastpathlinkrev,
1180 fastpathlinkrev,
1181 fnodes,
1181 fnodes,
1182 clrevs,
1182 clrevs,
1183 sidedata_helpers=sidedata_helpers,
1183 sidedata_helpers=sidedata_helpers,
1184 )
1184 )
1185
1185
1186 for path, deltas in it:
1186 for path, deltas in it:
1187 h = _fileheader(path)
1187 h = _fileheader(path)
1188 size = len(h)
1188 size = len(h)
1189 yield h
1189 yield h
1190
1190
1191 for delta in deltas:
1191 for delta in deltas:
1192 chunks = _revisiondeltatochunks(
1192 chunks = _revisiondeltatochunks(
1193 self._repo, delta, self._builddeltaheader
1193 self._repo, delta, self._builddeltaheader
1194 )
1194 )
1195 for chunk in chunks:
1195 for chunk in chunks:
1196 size += len(chunk)
1196 size += len(chunk)
1197 yield chunk
1197 yield chunk
1198
1198
1199 close = closechunk()
1199 close = closechunk()
1200 size += len(close)
1200 size += len(close)
1201 yield close
1201 yield close
1202
1202
1203 self._verbosenote(_(b'%8.i %s\n') % (size, path))
1203 self._verbosenote(_(b'%8.i %s\n') % (size, path))
1204
1204
1205 yield closechunk()
1205 yield closechunk()
1206
1206
1207 if clnodes:
1207 if clnodes:
1208 repo.hook(b'outgoing', node=hex(clnodes[0]), source=source)
1208 repo.hook(b'outgoing', node=hex(clnodes[0]), source=source)
1209
1209
1210 def _generatechangelog(
1210 def _generatechangelog(
1211 self, cl, nodes, generate=True, sidedata_helpers=None
1211 self, cl, nodes, generate=True, sidedata_helpers=None
1212 ):
1212 ):
1213 """Generate data for changelog chunks.
1213 """Generate data for changelog chunks.
1214
1214
1215 Returns a 2-tuple of a dict containing state and an iterable of
1215 Returns a 2-tuple of a dict containing state and an iterable of
1216 byte chunks. The state will not be fully populated until the
1216 byte chunks. The state will not be fully populated until the
1217 chunk stream has been fully consumed.
1217 chunk stream has been fully consumed.
1218
1218
1219 if generate is False, the state will be fully populated and no chunk
1219 if generate is False, the state will be fully populated and no chunk
1220 stream will be yielded
1220 stream will be yielded
1221
1221
1222 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
1222 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
1223 `sidedata_helpers`.
1223 `sidedata_helpers`.
1224 """
1224 """
1225 clrevorder = {}
1225 clrevorder = {}
1226 manifests = {}
1226 manifests = {}
1227 mfl = self._repo.manifestlog
1227 mfl = self._repo.manifestlog
1228 changedfiles = set()
1228 changedfiles = set()
1229 clrevtomanifestrev = {}
1229 clrevtomanifestrev = {}
1230
1230
1231 state = {
1231 state = {
1232 b'clrevorder': clrevorder,
1232 b'clrevorder': clrevorder,
1233 b'manifests': manifests,
1233 b'manifests': manifests,
1234 b'changedfiles': changedfiles,
1234 b'changedfiles': changedfiles,
1235 b'clrevtomanifestrev': clrevtomanifestrev,
1235 b'clrevtomanifestrev': clrevtomanifestrev,
1236 }
1236 }
1237
1237
1238 if not (generate or self._ellipses):
1238 if not (generate or self._ellipses):
1239 # sort the nodes in storage order
1239 # sort the nodes in storage order
1240 nodes = sorted(nodes, key=cl.rev)
1240 nodes = sorted(nodes, key=cl.rev)
1241 for node in nodes:
1241 for node in nodes:
1242 c = cl.changelogrevision(node)
1242 c = cl.changelogrevision(node)
1243 clrevorder[node] = len(clrevorder)
1243 clrevorder[node] = len(clrevorder)
1244 # record the first changeset introducing this manifest version
1244 # record the first changeset introducing this manifest version
1245 manifests.setdefault(c.manifest, node)
1245 manifests.setdefault(c.manifest, node)
1246 # Record a complete list of potentially-changed files in
1246 # Record a complete list of potentially-changed files in
1247 # this manifest.
1247 # this manifest.
1248 changedfiles.update(c.files)
1248 changedfiles.update(c.files)
1249
1249
1250 return state, ()
1250 return state, ()
1251
1251
1252 # Callback for the changelog, used to collect changed files and
1252 # Callback for the changelog, used to collect changed files and
1253 # manifest nodes.
1253 # manifest nodes.
1254 # Returns the linkrev node (identity in the changelog case).
1254 # Returns the linkrev node (identity in the changelog case).
1255 def lookupcl(x):
1255 def lookupcl(x):
1256 c = cl.changelogrevision(x)
1256 c = cl.changelogrevision(x)
1257 clrevorder[x] = len(clrevorder)
1257 clrevorder[x] = len(clrevorder)
1258
1258
1259 if self._ellipses:
1259 if self._ellipses:
1260 # Only update manifests if x is going to be sent. Otherwise we
1260 # Only update manifests if x is going to be sent. Otherwise we
1261 # end up with bogus linkrevs specified for manifests and
1261 # end up with bogus linkrevs specified for manifests and
1262 # we skip some manifest nodes that we should otherwise
1262 # we skip some manifest nodes that we should otherwise
1263 # have sent.
1263 # have sent.
1264 if (
1264 if (
1265 x in self._fullclnodes
1265 x in self._fullclnodes
1266 or cl.rev(x) in self._precomputedellipsis
1266 or cl.rev(x) in self._precomputedellipsis
1267 ):
1267 ):
1268
1268
1269 manifestnode = c.manifest
1269 manifestnode = c.manifest
1270 # Record the first changeset introducing this manifest
1270 # Record the first changeset introducing this manifest
1271 # version.
1271 # version.
1272 manifests.setdefault(manifestnode, x)
1272 manifests.setdefault(manifestnode, x)
1273 # Set this narrow-specific dict so we have the lowest
1273 # Set this narrow-specific dict so we have the lowest
1274 # manifest revnum to look up for this cl revnum. (Part of
1274 # manifest revnum to look up for this cl revnum. (Part of
1275 # mapping changelog ellipsis parents to manifest ellipsis
1275 # mapping changelog ellipsis parents to manifest ellipsis
1276 # parents)
1276 # parents)
1277 clrevtomanifestrev.setdefault(
1277 clrevtomanifestrev.setdefault(
1278 cl.rev(x), mfl.rev(manifestnode)
1278 cl.rev(x), mfl.rev(manifestnode)
1279 )
1279 )
1280 # We can't trust the changed files list in the changeset if the
1280 # We can't trust the changed files list in the changeset if the
1281 # client requested a shallow clone.
1281 # client requested a shallow clone.
1282 if self._isshallow:
1282 if self._isshallow:
1283 changedfiles.update(mfl[c.manifest].read().keys())
1283 changedfiles.update(mfl[c.manifest].read().keys())
1284 else:
1284 else:
1285 changedfiles.update(c.files)
1285 changedfiles.update(c.files)
1286 else:
1286 else:
1287 # record the first changeset introducing this manifest version
1287 # record the first changeset introducing this manifest version
1288 manifests.setdefault(c.manifest, x)
1288 manifests.setdefault(c.manifest, x)
1289 # Record a complete list of potentially-changed files in
1289 # Record a complete list of potentially-changed files in
1290 # this manifest.
1290 # this manifest.
1291 changedfiles.update(c.files)
1291 changedfiles.update(c.files)
1292
1292
1293 return x
1293 return x
1294
1294
1295 gen = deltagroup(
1295 gen = deltagroup(
1296 self._repo,
1296 self._repo,
1297 cl,
1297 cl,
1298 nodes,
1298 nodes,
1299 True,
1299 True,
1300 lookupcl,
1300 lookupcl,
1301 self._forcedeltaparentprev,
1301 self._forcedeltaparentprev,
1302 ellipses=self._ellipses,
1302 ellipses=self._ellipses,
1303 topic=_(b'changesets'),
1303 topic=_(b'changesets'),
1304 clrevtolocalrev={},
1304 clrevtolocalrev={},
1305 fullclnodes=self._fullclnodes,
1305 fullclnodes=self._fullclnodes,
1306 precomputedellipsis=self._precomputedellipsis,
1306 precomputedellipsis=self._precomputedellipsis,
1307 sidedata_helpers=sidedata_helpers,
1307 sidedata_helpers=sidedata_helpers,
1308 )
1308 )
1309
1309
1310 return state, gen
1310 return state, gen
1311
1311
1312 def generatemanifests(
1312 def generatemanifests(
1313 self,
1313 self,
1314 commonrevs,
1314 commonrevs,
1315 clrevorder,
1315 clrevorder,
1316 fastpathlinkrev,
1316 fastpathlinkrev,
1317 manifests,
1317 manifests,
1318 fnodes,
1318 fnodes,
1319 source,
1319 source,
1320 clrevtolocalrev,
1320 clrevtolocalrev,
1321 sidedata_helpers=None,
1321 sidedata_helpers=None,
1322 ):
1322 ):
1323 """Returns an iterator of changegroup chunks containing manifests.
1323 """Returns an iterator of changegroup chunks containing manifests.
1324
1324
1325 `source` is unused here, but is used by extensions like remotefilelog to
1325 `source` is unused here, but is used by extensions like remotefilelog to
1326 change what is sent based in pulls vs pushes, etc.
1326 change what is sent based in pulls vs pushes, etc.
1327
1327
1328 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
1328 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
1329 `sidedata_helpers`.
1329 `sidedata_helpers`.
1330 """
1330 """
1331 repo = self._repo
1331 repo = self._repo
1332 mfl = repo.manifestlog
1332 mfl = repo.manifestlog
1333 tmfnodes = {b'': manifests}
1333 tmfnodes = {b'': manifests}
1334
1334
1335 # Callback for the manifest, used to collect linkrevs for filelog
1335 # Callback for the manifest, used to collect linkrevs for filelog
1336 # revisions.
1336 # revisions.
1337 # Returns the linkrev node (collected in lookupcl).
1337 # Returns the linkrev node (collected in lookupcl).
1338 def makelookupmflinknode(tree, nodes):
1338 def makelookupmflinknode(tree, nodes):
1339 if fastpathlinkrev:
1339 if fastpathlinkrev:
1340 assert not tree
1340 assert not tree
1341
1341
1342 # pytype: disable=unsupported-operands
1342 # pytype: disable=unsupported-operands
1343 return manifests.__getitem__
1343 return manifests.__getitem__
1344 # pytype: enable=unsupported-operands
1344 # pytype: enable=unsupported-operands
1345
1345
1346 def lookupmflinknode(x):
1346 def lookupmflinknode(x):
1347 """Callback for looking up the linknode for manifests.
1347 """Callback for looking up the linknode for manifests.
1348
1348
1349 Returns the linkrev node for the specified manifest.
1349 Returns the linkrev node for the specified manifest.
1350
1350
1351 SIDE EFFECT:
1351 SIDE EFFECT:
1352
1352
1353 1) fclnodes gets populated with the list of relevant
1353 1) fclnodes gets populated with the list of relevant
1354 file nodes if we're not using fastpathlinkrev
1354 file nodes if we're not using fastpathlinkrev
1355 2) When treemanifests are in use, collects treemanifest nodes
1355 2) When treemanifests are in use, collects treemanifest nodes
1356 to send
1356 to send
1357
1357
1358 Note that this means manifests must be completely sent to
1358 Note that this means manifests must be completely sent to
1359 the client before you can trust the list of files and
1359 the client before you can trust the list of files and
1360 treemanifests to send.
1360 treemanifests to send.
1361 """
1361 """
1362 clnode = nodes[x]
1362 clnode = nodes[x]
1363 mdata = mfl.get(tree, x).readfast(shallow=True)
1363 mdata = mfl.get(tree, x).readfast(shallow=True)
1364 for p, n, fl in mdata.iterentries():
1364 for p, n, fl in mdata.iterentries():
1365 if fl == b't': # subdirectory manifest
1365 if fl == b't': # subdirectory manifest
1366 subtree = tree + p + b'/'
1366 subtree = tree + p + b'/'
1367 tmfclnodes = tmfnodes.setdefault(subtree, {})
1367 tmfclnodes = tmfnodes.setdefault(subtree, {})
1368 tmfclnode = tmfclnodes.setdefault(n, clnode)
1368 tmfclnode = tmfclnodes.setdefault(n, clnode)
1369 if clrevorder[clnode] < clrevorder[tmfclnode]:
1369 if clrevorder[clnode] < clrevorder[tmfclnode]:
1370 tmfclnodes[n] = clnode
1370 tmfclnodes[n] = clnode
1371 else:
1371 else:
1372 f = tree + p
1372 f = tree + p
1373 fclnodes = fnodes.setdefault(f, {})
1373 fclnodes = fnodes.setdefault(f, {})
1374 fclnode = fclnodes.setdefault(n, clnode)
1374 fclnode = fclnodes.setdefault(n, clnode)
1375 if clrevorder[clnode] < clrevorder[fclnode]:
1375 if clrevorder[clnode] < clrevorder[fclnode]:
1376 fclnodes[n] = clnode
1376 fclnodes[n] = clnode
1377 return clnode
1377 return clnode
1378
1378
1379 return lookupmflinknode
1379 return lookupmflinknode
1380
1380
1381 while tmfnodes:
1381 while tmfnodes:
1382 tree, nodes = tmfnodes.popitem()
1382 tree, nodes = tmfnodes.popitem()
1383
1383
1384 should_visit = self._matcher.visitdir(tree[:-1])
1384 should_visit = self._matcher.visitdir(tree[:-1])
1385 if tree and not should_visit:
1385 if tree and not should_visit:
1386 continue
1386 continue
1387
1387
1388 store = mfl.getstorage(tree)
1388 store = mfl.getstorage(tree)
1389
1389
1390 if not should_visit:
1390 if not should_visit:
1391 # No nodes to send because this directory is out of
1391 # No nodes to send because this directory is out of
1392 # the client's view of the repository (probably
1392 # the client's view of the repository (probably
1393 # because of narrow clones). Do this even for the root
1393 # because of narrow clones). Do this even for the root
1394 # directory (tree=='')
1394 # directory (tree=='')
1395 prunednodes = []
1395 prunednodes = []
1396 else:
1396 else:
1397 # Avoid sending any manifest nodes we can prove the
1397 # Avoid sending any manifest nodes we can prove the
1398 # client already has by checking linkrevs. See the
1398 # client already has by checking linkrevs. See the
1399 # related comment in generatefiles().
1399 # related comment in generatefiles().
1400 prunednodes = self._prunemanifests(store, nodes, commonrevs)
1400 prunednodes = self._prunemanifests(store, nodes, commonrevs)
1401
1401
1402 if tree and not prunednodes:
1402 if tree and not prunednodes:
1403 continue
1403 continue
1404
1404
1405 lookupfn = makelookupmflinknode(tree, nodes)
1405 lookupfn = makelookupmflinknode(tree, nodes)
1406
1406
1407 deltas = deltagroup(
1407 deltas = deltagroup(
1408 self._repo,
1408 self._repo,
1409 store,
1409 store,
1410 prunednodes,
1410 prunednodes,
1411 False,
1411 False,
1412 lookupfn,
1412 lookupfn,
1413 self._forcedeltaparentprev,
1413 self._forcedeltaparentprev,
1414 ellipses=self._ellipses,
1414 ellipses=self._ellipses,
1415 topic=_(b'manifests'),
1415 topic=_(b'manifests'),
1416 clrevtolocalrev=clrevtolocalrev,
1416 clrevtolocalrev=clrevtolocalrev,
1417 fullclnodes=self._fullclnodes,
1417 fullclnodes=self._fullclnodes,
1418 precomputedellipsis=self._precomputedellipsis,
1418 precomputedellipsis=self._precomputedellipsis,
1419 sidedata_helpers=sidedata_helpers,
1419 sidedata_helpers=sidedata_helpers,
1420 )
1420 )
1421
1421
1422 if not self._oldmatcher.visitdir(store.tree[:-1]):
1422 if not self._oldmatcher.visitdir(store.tree[:-1]):
1423 yield tree, deltas
1423 yield tree, deltas
1424 else:
1424 else:
1425 # 'deltas' is a generator and we need to consume it even if
1425 # 'deltas' is a generator and we need to consume it even if
1426 # we are not going to send it because a side-effect is that
1426 # we are not going to send it because a side-effect is that
1427 # it updates tmdnodes (via lookupfn)
1427 # it updates tmdnodes (via lookupfn)
1428 for d in deltas:
1428 for d in deltas:
1429 pass
1429 pass
1430 if not tree:
1430 if not tree:
1431 yield tree, []
1431 yield tree, []
1432
1432
1433 def _prunemanifests(self, store, nodes, commonrevs):
1433 def _prunemanifests(self, store, nodes, commonrevs):
1434 if not self._ellipses:
1434 if not self._ellipses:
1435 # In non-ellipses case and large repositories, it is better to
1435 # In non-ellipses case and large repositories, it is better to
1436 # prevent calling of store.rev and store.linkrev on a lot of
1436 # prevent calling of store.rev and store.linkrev on a lot of
1437 # nodes as compared to sending some extra data
1437 # nodes as compared to sending some extra data
1438 return nodes.copy()
1438 return nodes.copy()
1439 # This is split out as a separate method to allow filtering
1439 # This is split out as a separate method to allow filtering
1440 # commonrevs in extension code.
1440 # commonrevs in extension code.
1441 #
1441 #
1442 # TODO(augie): this shouldn't be required, instead we should
1442 # TODO(augie): this shouldn't be required, instead we should
1443 # make filtering of revisions to send delegated to the store
1443 # make filtering of revisions to send delegated to the store
1444 # layer.
1444 # layer.
1445 frev, flr = store.rev, store.linkrev
1445 frev, flr = store.rev, store.linkrev
1446 return [n for n in nodes if flr(frev(n)) not in commonrevs]
1446 return [n for n in nodes if flr(frev(n)) not in commonrevs]
1447
1447
1448 # The 'source' parameter is useful for extensions
1448 # The 'source' parameter is useful for extensions
1449 def generatefiles(
1449 def generatefiles(
1450 self,
1450 self,
1451 changedfiles,
1451 changedfiles,
1452 commonrevs,
1452 commonrevs,
1453 source,
1453 source,
1454 mfdicts,
1454 mfdicts,
1455 fastpathlinkrev,
1455 fastpathlinkrev,
1456 fnodes,
1456 fnodes,
1457 clrevs,
1457 clrevs,
1458 sidedata_helpers=None,
1458 sidedata_helpers=None,
1459 ):
1459 ):
1460 changedfiles = [
1460 changedfiles = [
1461 f
1461 f
1462 for f in changedfiles
1462 for f in changedfiles
1463 if self._matcher(f) and not self._oldmatcher(f)
1463 if self._matcher(f) and not self._oldmatcher(f)
1464 ]
1464 ]
1465
1465
1466 if not fastpathlinkrev:
1466 if not fastpathlinkrev:
1467
1467
1468 def normallinknodes(unused, fname):
1468 def normallinknodes(unused, fname):
1469 return fnodes.get(fname, {})
1469 return fnodes.get(fname, {})
1470
1470
1471 else:
1471 else:
1472 cln = self._repo.changelog.node
1472 cln = self._repo.changelog.node
1473
1473
1474 def normallinknodes(store, fname):
1474 def normallinknodes(store, fname):
1475 flinkrev = store.linkrev
1475 flinkrev = store.linkrev
1476 fnode = store.node
1476 fnode = store.node
1477 revs = ((r, flinkrev(r)) for r in store)
1477 revs = ((r, flinkrev(r)) for r in store)
1478 return {fnode(r): cln(lr) for r, lr in revs if lr in clrevs}
1478 return {fnode(r): cln(lr) for r, lr in revs if lr in clrevs}
1479
1479
1480 clrevtolocalrev = {}
1480 clrevtolocalrev = {}
1481
1481
1482 if self._isshallow:
1482 if self._isshallow:
1483 # In a shallow clone, the linknodes callback needs to also include
1483 # In a shallow clone, the linknodes callback needs to also include
1484 # those file nodes that are in the manifests we sent but weren't
1484 # those file nodes that are in the manifests we sent but weren't
1485 # introduced by those manifests.
1485 # introduced by those manifests.
1486 commonctxs = [self._repo[c] for c in commonrevs]
1486 commonctxs = [self._repo[c] for c in commonrevs]
1487 clrev = self._repo.changelog.rev
1487 clrev = self._repo.changelog.rev
1488
1488
1489 def linknodes(flog, fname):
1489 def linknodes(flog, fname):
1490 for c in commonctxs:
1490 for c in commonctxs:
1491 try:
1491 try:
1492 fnode = c.filenode(fname)
1492 fnode = c.filenode(fname)
1493 clrevtolocalrev[c.rev()] = flog.rev(fnode)
1493 clrevtolocalrev[c.rev()] = flog.rev(fnode)
1494 except error.ManifestLookupError:
1494 except error.ManifestLookupError:
1495 pass
1495 pass
1496 links = normallinknodes(flog, fname)
1496 links = normallinknodes(flog, fname)
1497 if len(links) != len(mfdicts):
1497 if len(links) != len(mfdicts):
1498 for mf, lr in mfdicts:
1498 for mf, lr in mfdicts:
1499 fnode = mf.get(fname, None)
1499 fnode = mf.get(fname, None)
1500 if fnode in links:
1500 if fnode in links:
1501 links[fnode] = min(links[fnode], lr, key=clrev)
1501 links[fnode] = min(links[fnode], lr, key=clrev)
1502 elif fnode:
1502 elif fnode:
1503 links[fnode] = lr
1503 links[fnode] = lr
1504 return links
1504 return links
1505
1505
1506 else:
1506 else:
1507 linknodes = normallinknodes
1507 linknodes = normallinknodes
1508
1508
1509 repo = self._repo
1509 repo = self._repo
1510 progress = repo.ui.makeprogress(
1510 progress = repo.ui.makeprogress(
1511 _(b'files'), unit=_(b'files'), total=len(changedfiles)
1511 _(b'files'), unit=_(b'files'), total=len(changedfiles)
1512 )
1512 )
1513 for i, fname in enumerate(sorted(changedfiles)):
1513 for i, fname in enumerate(sorted(changedfiles)):
1514 filerevlog = repo.file(fname)
1514 filerevlog = repo.file(fname)
1515 if not filerevlog:
1515 if not filerevlog:
1516 raise error.Abort(
1516 raise error.Abort(
1517 _(b"empty or missing file data for %s") % fname
1517 _(b"empty or missing file data for %s") % fname
1518 )
1518 )
1519
1519
1520 clrevtolocalrev.clear()
1520 clrevtolocalrev.clear()
1521
1521
1522 linkrevnodes = linknodes(filerevlog, fname)
1522 linkrevnodes = linknodes(filerevlog, fname)
1523 # Lookup for filenodes, we collected the linkrev nodes above in the
1523 # Lookup for filenodes, we collected the linkrev nodes above in the
1524 # fastpath case and with lookupmf in the slowpath case.
1524 # fastpath case and with lookupmf in the slowpath case.
1525 def lookupfilelog(x):
1525 def lookupfilelog(x):
1526 return linkrevnodes[x]
1526 return linkrevnodes[x]
1527
1527
1528 frev, flr = filerevlog.rev, filerevlog.linkrev
1528 frev, flr = filerevlog.rev, filerevlog.linkrev
1529 # Skip sending any filenode we know the client already
1529 # Skip sending any filenode we know the client already
1530 # has. This avoids over-sending files relatively
1530 # has. This avoids over-sending files relatively
1531 # inexpensively, so it's not a problem if we under-filter
1531 # inexpensively, so it's not a problem if we under-filter
1532 # here.
1532 # here.
1533 filenodes = [
1533 filenodes = [
1534 n for n in linkrevnodes if flr(frev(n)) not in commonrevs
1534 n for n in linkrevnodes if flr(frev(n)) not in commonrevs
1535 ]
1535 ]
1536
1536
1537 if not filenodes:
1537 if not filenodes:
1538 continue
1538 continue
1539
1539
1540 progress.update(i + 1, item=fname)
1540 progress.update(i + 1, item=fname)
1541
1541
1542 deltas = deltagroup(
1542 deltas = deltagroup(
1543 self._repo,
1543 self._repo,
1544 filerevlog,
1544 filerevlog,
1545 filenodes,
1545 filenodes,
1546 False,
1546 False,
1547 lookupfilelog,
1547 lookupfilelog,
1548 self._forcedeltaparentprev,
1548 self._forcedeltaparentprev,
1549 ellipses=self._ellipses,
1549 ellipses=self._ellipses,
1550 clrevtolocalrev=clrevtolocalrev,
1550 clrevtolocalrev=clrevtolocalrev,
1551 fullclnodes=self._fullclnodes,
1551 fullclnodes=self._fullclnodes,
1552 precomputedellipsis=self._precomputedellipsis,
1552 precomputedellipsis=self._precomputedellipsis,
1553 sidedata_helpers=sidedata_helpers,
1553 sidedata_helpers=sidedata_helpers,
1554 )
1554 )
1555
1555
1556 yield fname, deltas
1556 yield fname, deltas
1557
1557
1558 progress.complete()
1558 progress.complete()
1559
1559
1560
1560
1561 def _makecg1packer(
1561 def _makecg1packer(
1562 repo,
1562 repo,
1563 oldmatcher,
1563 oldmatcher,
1564 matcher,
1564 matcher,
1565 bundlecaps,
1565 bundlecaps,
1566 ellipses=False,
1566 ellipses=False,
1567 shallow=False,
1567 shallow=False,
1568 ellipsisroots=None,
1568 ellipsisroots=None,
1569 fullnodes=None,
1569 fullnodes=None,
1570 remote_sidedata=None,
1570 remote_sidedata=None,
1571 ):
1571 ):
1572 builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack(
1572 builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack(
1573 d.node, d.p1node, d.p2node, d.linknode
1573 d.node, d.p1node, d.p2node, d.linknode
1574 )
1574 )
1575
1575
1576 return cgpacker(
1576 return cgpacker(
1577 repo,
1577 repo,
1578 oldmatcher,
1578 oldmatcher,
1579 matcher,
1579 matcher,
1580 b'01',
1580 b'01',
1581 builddeltaheader=builddeltaheader,
1581 builddeltaheader=builddeltaheader,
1582 manifestsend=b'',
1582 manifestsend=b'',
1583 forcedeltaparentprev=True,
1583 forcedeltaparentprev=True,
1584 bundlecaps=bundlecaps,
1584 bundlecaps=bundlecaps,
1585 ellipses=ellipses,
1585 ellipses=ellipses,
1586 shallow=shallow,
1586 shallow=shallow,
1587 ellipsisroots=ellipsisroots,
1587 ellipsisroots=ellipsisroots,
1588 fullnodes=fullnodes,
1588 fullnodes=fullnodes,
1589 )
1589 )
1590
1590
1591
1591
1592 def _makecg2packer(
1592 def _makecg2packer(
1593 repo,
1593 repo,
1594 oldmatcher,
1594 oldmatcher,
1595 matcher,
1595 matcher,
1596 bundlecaps,
1596 bundlecaps,
1597 ellipses=False,
1597 ellipses=False,
1598 shallow=False,
1598 shallow=False,
1599 ellipsisroots=None,
1599 ellipsisroots=None,
1600 fullnodes=None,
1600 fullnodes=None,
1601 remote_sidedata=None,
1601 remote_sidedata=None,
1602 ):
1602 ):
1603 builddeltaheader = lambda d: _CHANGEGROUPV2_DELTA_HEADER.pack(
1603 builddeltaheader = lambda d: _CHANGEGROUPV2_DELTA_HEADER.pack(
1604 d.node, d.p1node, d.p2node, d.basenode, d.linknode
1604 d.node, d.p1node, d.p2node, d.basenode, d.linknode
1605 )
1605 )
1606
1606
1607 return cgpacker(
1607 return cgpacker(
1608 repo,
1608 repo,
1609 oldmatcher,
1609 oldmatcher,
1610 matcher,
1610 matcher,
1611 b'02',
1611 b'02',
1612 builddeltaheader=builddeltaheader,
1612 builddeltaheader=builddeltaheader,
1613 manifestsend=b'',
1613 manifestsend=b'',
1614 bundlecaps=bundlecaps,
1614 bundlecaps=bundlecaps,
1615 ellipses=ellipses,
1615 ellipses=ellipses,
1616 shallow=shallow,
1616 shallow=shallow,
1617 ellipsisroots=ellipsisroots,
1617 ellipsisroots=ellipsisroots,
1618 fullnodes=fullnodes,
1618 fullnodes=fullnodes,
1619 )
1619 )
1620
1620
1621
1621
1622 def _makecg3packer(
1622 def _makecg3packer(
1623 repo,
1623 repo,
1624 oldmatcher,
1624 oldmatcher,
1625 matcher,
1625 matcher,
1626 bundlecaps,
1626 bundlecaps,
1627 ellipses=False,
1627 ellipses=False,
1628 shallow=False,
1628 shallow=False,
1629 ellipsisroots=None,
1629 ellipsisroots=None,
1630 fullnodes=None,
1630 fullnodes=None,
1631 remote_sidedata=None,
1631 remote_sidedata=None,
1632 ):
1632 ):
1633 builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
1633 builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
1634 d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags
1634 d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags
1635 )
1635 )
1636
1636
1637 return cgpacker(
1637 return cgpacker(
1638 repo,
1638 repo,
1639 oldmatcher,
1639 oldmatcher,
1640 matcher,
1640 matcher,
1641 b'03',
1641 b'03',
1642 builddeltaheader=builddeltaheader,
1642 builddeltaheader=builddeltaheader,
1643 manifestsend=closechunk(),
1643 manifestsend=closechunk(),
1644 bundlecaps=bundlecaps,
1644 bundlecaps=bundlecaps,
1645 ellipses=ellipses,
1645 ellipses=ellipses,
1646 shallow=shallow,
1646 shallow=shallow,
1647 ellipsisroots=ellipsisroots,
1647 ellipsisroots=ellipsisroots,
1648 fullnodes=fullnodes,
1648 fullnodes=fullnodes,
1649 )
1649 )
1650
1650
1651
1651
1652 def _makecg4packer(
1652 def _makecg4packer(
1653 repo,
1653 repo,
1654 oldmatcher,
1654 oldmatcher,
1655 matcher,
1655 matcher,
1656 bundlecaps,
1656 bundlecaps,
1657 ellipses=False,
1657 ellipses=False,
1658 shallow=False,
1658 shallow=False,
1659 ellipsisroots=None,
1659 ellipsisroots=None,
1660 fullnodes=None,
1660 fullnodes=None,
1661 remote_sidedata=None,
1661 remote_sidedata=None,
1662 ):
1662 ):
1663 # Sidedata is in a separate chunk from the delta to differentiate
1663 # Sidedata is in a separate chunk from the delta to differentiate
1664 # "raw delta" and sidedata.
1664 # "raw delta" and sidedata.
1665 def builddeltaheader(d):
1665 def builddeltaheader(d):
1666 return _CHANGEGROUPV4_DELTA_HEADER.pack(
1666 return _CHANGEGROUPV4_DELTA_HEADER.pack(
1667 d.protocol_flags,
1667 d.protocol_flags,
1668 d.node,
1668 d.node,
1669 d.p1node,
1669 d.p1node,
1670 d.p2node,
1670 d.p2node,
1671 d.basenode,
1671 d.basenode,
1672 d.linknode,
1672 d.linknode,
1673 d.flags,
1673 d.flags,
1674 )
1674 )
1675
1675
1676 return cgpacker(
1676 return cgpacker(
1677 repo,
1677 repo,
1678 oldmatcher,
1678 oldmatcher,
1679 matcher,
1679 matcher,
1680 b'04',
1680 b'04',
1681 builddeltaheader=builddeltaheader,
1681 builddeltaheader=builddeltaheader,
1682 manifestsend=closechunk(),
1682 manifestsend=closechunk(),
1683 bundlecaps=bundlecaps,
1683 bundlecaps=bundlecaps,
1684 ellipses=ellipses,
1684 ellipses=ellipses,
1685 shallow=shallow,
1685 shallow=shallow,
1686 ellipsisroots=ellipsisroots,
1686 ellipsisroots=ellipsisroots,
1687 fullnodes=fullnodes,
1687 fullnodes=fullnodes,
1688 remote_sidedata=remote_sidedata,
1688 remote_sidedata=remote_sidedata,
1689 )
1689 )
1690
1690
1691
1691
1692 _packermap = {
1692 _packermap = {
1693 b'01': (_makecg1packer, cg1unpacker),
1693 b'01': (_makecg1packer, cg1unpacker),
1694 # cg2 adds support for exchanging generaldelta
1694 # cg2 adds support for exchanging generaldelta
1695 b'02': (_makecg2packer, cg2unpacker),
1695 b'02': (_makecg2packer, cg2unpacker),
1696 # cg3 adds support for exchanging revlog flags and treemanifests
1696 # cg3 adds support for exchanging revlog flags and treemanifests
1697 b'03': (_makecg3packer, cg3unpacker),
1697 b'03': (_makecg3packer, cg3unpacker),
1698 # ch4 adds support for exchanging sidedata
1698 # ch4 adds support for exchanging sidedata
1699 b'04': (_makecg4packer, cg4unpacker),
1699 b'04': (_makecg4packer, cg4unpacker),
1700 }
1700 }
1701
1701
1702
1702
1703 def allsupportedversions(repo):
1703 def allsupportedversions(repo):
1704 versions = set(_packermap.keys())
1704 versions = set(_packermap.keys())
1705 needv03 = False
1705 needv03 = False
1706 if (
1706 if (
1707 repo.ui.configbool(b'experimental', b'changegroup3')
1707 repo.ui.configbool(b'experimental', b'changegroup3')
1708 or repo.ui.configbool(b'experimental', b'treemanifest')
1708 or repo.ui.configbool(b'experimental', b'treemanifest')
1709 or scmutil.istreemanifest(repo)
1709 or scmutil.istreemanifest(repo)
1710 ):
1710 ):
1711 # we keep version 03 because we need to to exchange treemanifest data
1711 # we keep version 03 because we need to to exchange treemanifest data
1712 #
1712 #
1713 # we also keep vresion 01 and 02, because it is possible for repo to
1713 # we also keep vresion 01 and 02, because it is possible for repo to
1714 # contains both normal and tree manifest at the same time. so using
1714 # contains both normal and tree manifest at the same time. so using
1715 # older version to pull data is viable
1715 # older version to pull data is viable
1716 #
1716 #
1717 # (or even to push subset of history)
1717 # (or even to push subset of history)
1718 needv03 = True
1718 needv03 = True
1719 if not needv03:
1719 if not needv03:
1720 versions.discard(b'03')
1720 versions.discard(b'03')
1721 want_v4 = (
1721 want_v4 = (
1722 repo.ui.configbool(b'experimental', b'changegroup4')
1722 repo.ui.configbool(b'experimental', b'changegroup4')
1723 or requirements.REVLOGV2_REQUIREMENT in repo.requirements
1723 or requirements.REVLOGV2_REQUIREMENT in repo.requirements
1724 )
1724 )
1725 if not want_v4:
1725 if not want_v4:
1726 versions.discard(b'04')
1726 versions.discard(b'04')
1727 return versions
1727 return versions
1728
1728
1729
1729
1730 # Changegroup versions that can be applied to the repo
1730 # Changegroup versions that can be applied to the repo
1731 def supportedincomingversions(repo):
1731 def supportedincomingversions(repo):
1732 return allsupportedversions(repo)
1732 return allsupportedversions(repo)
1733
1733
1734
1734
1735 # Changegroup versions that can be created from the repo
1735 # Changegroup versions that can be created from the repo
1736 def supportedoutgoingversions(repo):
1736 def supportedoutgoingversions(repo):
1737 versions = allsupportedversions(repo)
1737 versions = allsupportedversions(repo)
1738 if scmutil.istreemanifest(repo):
1738 if scmutil.istreemanifest(repo):
1739 # Versions 01 and 02 support only flat manifests and it's just too
1739 # Versions 01 and 02 support only flat manifests and it's just too
1740 # expensive to convert between the flat manifest and tree manifest on
1740 # expensive to convert between the flat manifest and tree manifest on
1741 # the fly. Since tree manifests are hashed differently, all of history
1741 # the fly. Since tree manifests are hashed differently, all of history
1742 # would have to be converted. Instead, we simply don't even pretend to
1742 # would have to be converted. Instead, we simply don't even pretend to
1743 # support versions 01 and 02.
1743 # support versions 01 and 02.
1744 versions.discard(b'01')
1744 versions.discard(b'01')
1745 versions.discard(b'02')
1745 versions.discard(b'02')
1746 if requirements.NARROW_REQUIREMENT in repo.requirements:
1746 if requirements.NARROW_REQUIREMENT in repo.requirements:
1747 # Versions 01 and 02 don't support revlog flags, and we need to
1747 # Versions 01 and 02 don't support revlog flags, and we need to
1748 # support that for stripping and unbundling to work.
1748 # support that for stripping and unbundling to work.
1749 versions.discard(b'01')
1749 versions.discard(b'01')
1750 versions.discard(b'02')
1750 versions.discard(b'02')
1751 if LFS_REQUIREMENT in repo.requirements:
1751 if LFS_REQUIREMENT in repo.requirements:
1752 # Versions 01 and 02 don't support revlog flags, and we need to
1752 # Versions 01 and 02 don't support revlog flags, and we need to
1753 # mark LFS entries with REVIDX_EXTSTORED.
1753 # mark LFS entries with REVIDX_EXTSTORED.
1754 versions.discard(b'01')
1754 versions.discard(b'01')
1755 versions.discard(b'02')
1755 versions.discard(b'02')
1756
1756
1757 return versions
1757 return versions
1758
1758
1759
1759
1760 def localversion(repo):
1760 def localversion(repo):
1761 # Finds the best version to use for bundles that are meant to be used
1761 # Finds the best version to use for bundles that are meant to be used
1762 # locally, such as those from strip and shelve, and temporary bundles.
1762 # locally, such as those from strip and shelve, and temporary bundles.
1763 return max(supportedoutgoingversions(repo))
1763 return max(supportedoutgoingversions(repo))
1764
1764
1765
1765
1766 def safeversion(repo):
1766 def safeversion(repo):
1767 # Finds the smallest version that it's safe to assume clients of the repo
1767 # Finds the smallest version that it's safe to assume clients of the repo
1768 # will support. For example, all hg versions that support generaldelta also
1768 # will support. For example, all hg versions that support generaldelta also
1769 # support changegroup 02.
1769 # support changegroup 02.
1770 versions = supportedoutgoingversions(repo)
1770 versions = supportedoutgoingversions(repo)
1771 if requirements.GENERALDELTA_REQUIREMENT in repo.requirements:
1771 if requirements.GENERALDELTA_REQUIREMENT in repo.requirements:
1772 versions.discard(b'01')
1772 versions.discard(b'01')
1773 assert versions
1773 assert versions
1774 return min(versions)
1774 return min(versions)
1775
1775
1776
1776
1777 def getbundler(
1777 def getbundler(
1778 version,
1778 version,
1779 repo,
1779 repo,
1780 bundlecaps=None,
1780 bundlecaps=None,
1781 oldmatcher=None,
1781 oldmatcher=None,
1782 matcher=None,
1782 matcher=None,
1783 ellipses=False,
1783 ellipses=False,
1784 shallow=False,
1784 shallow=False,
1785 ellipsisroots=None,
1785 ellipsisroots=None,
1786 fullnodes=None,
1786 fullnodes=None,
1787 remote_sidedata=None,
1787 remote_sidedata=None,
1788 ):
1788 ):
1789 assert version in supportedoutgoingversions(repo)
1789 assert version in supportedoutgoingversions(repo)
1790
1790
1791 if matcher is None:
1791 if matcher is None:
1792 matcher = matchmod.always()
1792 matcher = matchmod.always()
1793 if oldmatcher is None:
1793 if oldmatcher is None:
1794 oldmatcher = matchmod.never()
1794 oldmatcher = matchmod.never()
1795
1795
1796 if version == b'01' and not matcher.always():
1796 if version == b'01' and not matcher.always():
1797 raise error.ProgrammingError(
1797 raise error.ProgrammingError(
1798 b'version 01 changegroups do not support sparse file matchers'
1798 b'version 01 changegroups do not support sparse file matchers'
1799 )
1799 )
1800
1800
1801 if ellipses and version in (b'01', b'02'):
1801 if ellipses and version in (b'01', b'02'):
1802 raise error.Abort(
1802 raise error.Abort(
1803 _(
1803 _(
1804 b'ellipsis nodes require at least cg3 on client and server, '
1804 b'ellipsis nodes require at least cg3 on client and server, '
1805 b'but negotiated version %s'
1805 b'but negotiated version %s'
1806 )
1806 )
1807 % version
1807 % version
1808 )
1808 )
1809
1809
1810 # Requested files could include files not in the local store. So
1810 # Requested files could include files not in the local store. So
1811 # filter those out.
1811 # filter those out.
1812 matcher = repo.narrowmatch(matcher)
1812 matcher = repo.narrowmatch(matcher)
1813
1813
1814 fn = _packermap[version][0]
1814 fn = _packermap[version][0]
1815 return fn(
1815 return fn(
1816 repo,
1816 repo,
1817 oldmatcher,
1817 oldmatcher,
1818 matcher,
1818 matcher,
1819 bundlecaps,
1819 bundlecaps,
1820 ellipses=ellipses,
1820 ellipses=ellipses,
1821 shallow=shallow,
1821 shallow=shallow,
1822 ellipsisroots=ellipsisroots,
1822 ellipsisroots=ellipsisroots,
1823 fullnodes=fullnodes,
1823 fullnodes=fullnodes,
1824 remote_sidedata=remote_sidedata,
1824 remote_sidedata=remote_sidedata,
1825 )
1825 )
1826
1826
1827
1827
1828 def getunbundler(version, fh, alg, extras=None):
1828 def getunbundler(version, fh, alg, extras=None):
1829 return _packermap[version][1](fh, alg, extras=extras)
1829 return _packermap[version][1](fh, alg, extras=extras)
1830
1830
1831
1831
1832 def _changegroupinfo(repo, nodes, source):
1832 def _changegroupinfo(repo, nodes, source):
1833 if repo.ui.verbose or source == b'bundle':
1833 if repo.ui.verbose or source == b'bundle':
1834 repo.ui.status(_(b"%d changesets found\n") % len(nodes))
1834 repo.ui.status(_(b"%d changesets found\n") % len(nodes))
1835 if repo.ui.debugflag:
1835 if repo.ui.debugflag:
1836 repo.ui.debug(b"list of changesets:\n")
1836 repo.ui.debug(b"list of changesets:\n")
1837 for node in nodes:
1837 for node in nodes:
1838 repo.ui.debug(b"%s\n" % hex(node))
1838 repo.ui.debug(b"%s\n" % hex(node))
1839
1839
1840
1840
1841 def makechangegroup(
1841 def makechangegroup(
1842 repo, outgoing, version, source, fastpath=False, bundlecaps=None
1842 repo, outgoing, version, source, fastpath=False, bundlecaps=None
1843 ):
1843 ):
1844 cgstream = makestream(
1844 cgstream = makestream(
1845 repo,
1845 repo,
1846 outgoing,
1846 outgoing,
1847 version,
1847 version,
1848 source,
1848 source,
1849 fastpath=fastpath,
1849 fastpath=fastpath,
1850 bundlecaps=bundlecaps,
1850 bundlecaps=bundlecaps,
1851 )
1851 )
1852 return getunbundler(
1852 return getunbundler(
1853 version,
1853 version,
1854 util.chunkbuffer(cgstream),
1854 util.chunkbuffer(cgstream),
1855 None,
1855 None,
1856 {b'clcount': len(outgoing.missing)},
1856 {b'clcount': len(outgoing.missing)},
1857 )
1857 )
1858
1858
1859
1859
1860 def makestream(
1860 def makestream(
1861 repo,
1861 repo,
1862 outgoing,
1862 outgoing,
1863 version,
1863 version,
1864 source,
1864 source,
1865 fastpath=False,
1865 fastpath=False,
1866 bundlecaps=None,
1866 bundlecaps=None,
1867 matcher=None,
1867 matcher=None,
1868 remote_sidedata=None,
1868 remote_sidedata=None,
1869 ):
1869 ):
1870 bundler = getbundler(
1870 bundler = getbundler(
1871 version,
1871 version,
1872 repo,
1872 repo,
1873 bundlecaps=bundlecaps,
1873 bundlecaps=bundlecaps,
1874 matcher=matcher,
1874 matcher=matcher,
1875 remote_sidedata=remote_sidedata,
1875 remote_sidedata=remote_sidedata,
1876 )
1876 )
1877
1877
1878 repo = repo.unfiltered()
1878 repo = repo.unfiltered()
1879 commonrevs = outgoing.common
1879 commonrevs = outgoing.common
1880 csets = outgoing.missing
1880 csets = outgoing.missing
1881 heads = outgoing.ancestorsof
1881 heads = outgoing.ancestorsof
1882 # We go through the fast path if we get told to, or if all (unfiltered
1882 # We go through the fast path if we get told to, or if all (unfiltered
1883 # heads have been requested (since we then know there all linkrevs will
1883 # heads have been requested (since we then know there all linkrevs will
1884 # be pulled by the client).
1884 # be pulled by the client).
1885 heads.sort()
1885 heads.sort()
1886 fastpathlinkrev = fastpath or (
1886 fastpathlinkrev = fastpath or (
1887 repo.filtername is None and heads == sorted(repo.heads())
1887 repo.filtername is None and heads == sorted(repo.heads())
1888 )
1888 )
1889
1889
1890 repo.hook(b'preoutgoing', throw=True, source=source)
1890 repo.hook(b'preoutgoing', throw=True, source=source)
1891 _changegroupinfo(repo, csets, source)
1891 _changegroupinfo(repo, csets, source)
1892 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1892 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1893
1893
1894
1894
1895 def _addchangegroupfiles(
1895 def _addchangegroupfiles(
1896 repo,
1896 repo,
1897 source,
1897 source,
1898 revmap,
1898 revmap,
1899 trp,
1899 trp,
1900 expectedfiles,
1900 expectedfiles,
1901 needfiles,
1901 needfiles,
1902 addrevisioncb=None,
1902 addrevisioncb=None,
1903 ):
1903 ):
1904 revisions = 0
1904 revisions = 0
1905 files = 0
1905 files = 0
1906 progress = repo.ui.makeprogress(
1906 progress = repo.ui.makeprogress(
1907 _(b'files'), unit=_(b'files'), total=expectedfiles
1907 _(b'files'), unit=_(b'files'), total=expectedfiles
1908 )
1908 )
1909 for chunkdata in iter(source.filelogheader, {}):
1909 for chunkdata in iter(source.filelogheader, {}):
1910 files += 1
1910 files += 1
1911 f = chunkdata[b"filename"]
1911 f = chunkdata[b"filename"]
1912 repo.ui.debug(b"adding %s revisions\n" % f)
1912 repo.ui.debug(b"adding %s revisions\n" % f)
1913 progress.increment()
1913 progress.increment()
1914 fl = repo.file(f)
1914 fl = repo.file(f)
1915 o = len(fl)
1915 o = len(fl)
1916 try:
1916 try:
1917 deltas = source.deltaiter()
1917 deltas = source.deltaiter()
1918 added = fl.addgroup(
1918 added = fl.addgroup(
1919 deltas,
1919 deltas,
1920 revmap,
1920 revmap,
1921 trp,
1921 trp,
1922 addrevisioncb=addrevisioncb,
1922 addrevisioncb=addrevisioncb,
1923 )
1923 )
1924 if not added:
1924 if not added:
1925 raise error.Abort(_(b"received file revlog group is empty"))
1925 raise error.Abort(_(b"received file revlog group is empty"))
1926 except error.CensoredBaseError as e:
1926 except error.CensoredBaseError as e:
1927 raise error.Abort(_(b"received delta base is censored: %s") % e)
1927 raise error.Abort(_(b"received delta base is censored: %s") % e)
1928 revisions += len(fl) - o
1928 revisions += len(fl) - o
1929 if f in needfiles:
1929 if f in needfiles:
1930 needs = needfiles[f]
1930 needs = needfiles[f]
1931 for new in pycompat.xrange(o, len(fl)):
1931 for new in pycompat.xrange(o, len(fl)):
1932 n = fl.node(new)
1932 n = fl.node(new)
1933 if n in needs:
1933 if n in needs:
1934 needs.remove(n)
1934 needs.remove(n)
1935 else:
1935 else:
1936 raise error.Abort(_(b"received spurious file revlog entry"))
1936 raise error.Abort(_(b"received spurious file revlog entry"))
1937 if not needs:
1937 if not needs:
1938 del needfiles[f]
1938 del needfiles[f]
1939 progress.complete()
1939 progress.complete()
1940
1940
1941 for f, needs in pycompat.iteritems(needfiles):
1941 for f, needs in pycompat.iteritems(needfiles):
1942 fl = repo.file(f)
1942 fl = repo.file(f)
1943 for n in needs:
1943 for n in needs:
1944 try:
1944 try:
1945 fl.rev(n)
1945 fl.rev(n)
1946 except error.LookupError:
1946 except error.LookupError:
1947 raise error.Abort(
1947 raise error.Abort(
1948 _(b'missing file data for %s:%s - run hg verify')
1948 _(b'missing file data for %s:%s - run hg verify')
1949 % (f, hex(n))
1949 % (f, hex(n))
1950 )
1950 )
1951
1951
1952 return revisions, files
1952 return revisions, files
@@ -1,154 +1,154 b''
1 $ . "$TESTDIR/narrow-library.sh"
1 $ . "$TESTDIR/narrow-library.sh"
2
2
3 $ hg init master
3 $ hg init master
4 $ cd master
4 $ cd master
5 $ cat >> .hg/hgrc <<EOF
5 $ cat >> .hg/hgrc <<EOF
6 > [narrow]
6 > [narrow]
7 > serveellipses=True
7 > serveellipses=True
8 > EOF
8 > EOF
9 $ for x in `$TESTDIR/seq.py 10`
9 $ for x in `$TESTDIR/seq.py 10`
10 > do
10 > do
11 > echo $x > "f$x"
11 > echo $x > "f$x"
12 > hg add "f$x"
12 > hg add "f$x"
13 > hg commit -m "Commit f$x"
13 > hg commit -m "Commit f$x"
14 > done
14 > done
15 $ cd ..
15 $ cd ..
16
16
17 narrow clone a couple files, f2 and f8
17 narrow clone a couple files, f2 and f8
18
18
19 $ hg clone --narrow ssh://user@dummy/master narrow --include "f2" --include "f8"
19 $ hg clone --narrow ssh://user@dummy/master narrow --include "f2" --include "f8"
20 requesting all changes
20 requesting all changes
21 adding changesets
21 adding changesets
22 adding manifests
22 adding manifests
23 adding file changes
23 adding file changes
24 added 5 changesets with 2 changes to 2 files
24 added 5 changesets with 2 changes to 2 files
25 new changesets *:* (glob)
25 new changesets *:* (glob)
26 updating to branch default
26 updating to branch default
27 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
27 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
28 $ cd narrow
28 $ cd narrow
29 $ ls -A
29 $ ls -A
30 .hg
30 .hg
31 f2
31 f2
32 f8
32 f8
33 $ cat f2 f8
33 $ cat f2 f8
34 2
34 2
35 8
35 8
36
36
37 $ cd ..
37 $ cd ..
38
38
39 change every upstream file twice
39 change every upstream file twice
40
40
41 $ cd master
41 $ cd master
42 $ for x in `$TESTDIR/seq.py 10`
42 $ for x in `$TESTDIR/seq.py 10`
43 > do
43 > do
44 > echo "update#1 $x" >> "f$x"
44 > echo "update#1 $x" >> "f$x"
45 > hg commit -m "Update#1 to f$x" "f$x"
45 > hg commit -m "Update#1 to f$x" "f$x"
46 > done
46 > done
47 $ for x in `$TESTDIR/seq.py 10`
47 $ for x in `$TESTDIR/seq.py 10`
48 > do
48 > do
49 > echo "update#2 $x" >> "f$x"
49 > echo "update#2 $x" >> "f$x"
50 > hg commit -m "Update#2 to f$x" "f$x"
50 > hg commit -m "Update#2 to f$x" "f$x"
51 > done
51 > done
52 $ cd ..
52 $ cd ..
53
53
54 look for incoming changes
54 look for incoming changes
55
55
56 $ cd narrow
56 $ cd narrow
57 $ hg incoming --limit 3
57 $ hg incoming --limit 3
58 comparing with ssh://user@dummy/master
58 comparing with ssh://user@dummy/master
59 searching for changes
59 searching for changes
60 changeset: 5:ddc055582556
60 changeset: 5:ddc055582556
61 user: test
61 user: test
62 date: Thu Jan 01 00:00:00 1970 +0000
62 date: Thu Jan 01 00:00:00 1970 +0000
63 summary: Update#1 to f1
63 summary: Update#1 to f1
64
64
65 changeset: 6:f66eb5ad621d
65 changeset: 6:f66eb5ad621d
66 user: test
66 user: test
67 date: Thu Jan 01 00:00:00 1970 +0000
67 date: Thu Jan 01 00:00:00 1970 +0000
68 summary: Update#1 to f2
68 summary: Update#1 to f2
69
69
70 changeset: 7:c42ecff04e99
70 changeset: 7:c42ecff04e99
71 user: test
71 user: test
72 date: Thu Jan 01 00:00:00 1970 +0000
72 date: Thu Jan 01 00:00:00 1970 +0000
73 summary: Update#1 to f3
73 summary: Update#1 to f3
74
74
75
75
76 Interrupting the pull is safe
76 Interrupting the pull is safe
77 $ hg --config hooks.pretxnchangegroup.bad=false pull -q
77 $ hg --config hooks.pretxnchangegroup.bad=false pull -q
78 transaction abort!
78 transaction abort!
79 rollback completed
79 rollback completed
80 abort: pretxnchangegroup.bad hook exited with status 1
80 abort: pretxnchangegroup.bad hook exited with status 1
81 [40]
81 [40]
82 $ hg id
82 $ hg id
83 223311e70a6f tip
83 223311e70a6f tip
84
84
85 pull new changes down to the narrow clone. Should get 8 new changesets: 4
85 pull new changes down to the narrow clone. Should get 8 new changesets: 4
86 relevant to the narrow spec, and 4 ellipsis nodes gluing them all together.
86 relevant to the narrow spec, and 4 ellipsis nodes gluing them all together.
87
87
88 $ hg pull
88 $ hg pull
89 pulling from ssh://user@dummy/master
89 pulling from ssh://user@dummy/master
90 searching for changes
90 searching for changes
91 adding changesets
91 adding changesets
92 adding manifests
92 adding manifests
93 adding file changes
93 adding file changes
94 added 9 changesets with 4 changes to 2 files
94 added 9 changesets with 4 changes to 2 files
95 new changesets *:* (glob)
95 new changesets *:* (glob)
96 (run 'hg update' to get a working copy)
96 (run 'hg update' to get a working copy)
97 $ hg log -T '{rev}: {desc}\n'
97 $ hg log -T '{rev}: {desc}\n'
98 13: Update#2 to f10
98 13: Update#2 to f10
99 12: Update#2 to f8
99 12: Update#2 to f8
100 11: Update#2 to f7
100 11: Update#2 to f7
101 10: Update#2 to f2
101 10: Update#2 to f2
102 9: Update#2 to f1
102 9: Update#2 to f1
103 8: Update#1 to f8
103 8: Update#1 to f8
104 7: Update#1 to f7
104 7: Update#1 to f7
105 6: Update#1 to f2
105 6: Update#1 to f2
106 5: Update#1 to f1
106 5: Update#1 to f1
107 4: Commit f10
107 4: Commit f10
108 3: Commit f8
108 3: Commit f8
109 2: Commit f7
109 2: Commit f7
110 1: Commit f2
110 1: Commit f2
111 0: Commit f1
111 0: Commit f1
112 $ hg update tip
112 $ hg update tip
113 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
113 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
114
114
115 add a change and push it
115 add a change and push it
116
116
117 $ echo "update#3 2" >> f2
117 $ echo "update#3 2" >> f2
118 $ hg commit -m "Update#3 to f2" f2
118 $ hg commit -m "Update#3 to f2" f2
119 $ hg log f2 -T '{rev}: {desc}\n'
119 $ hg log f2 -T '{rev}: {desc}\n'
120 14: Update#3 to f2
120 14: Update#3 to f2
121 10: Update#2 to f2
121 10: Update#2 to f2
122 6: Update#1 to f2
122 6: Update#1 to f2
123 1: Commit f2
123 1: Commit f2
124 $ hg push
124 $ hg push
125 pushing to ssh://user@dummy/master
125 pushing to ssh://user@dummy/master
126 searching for changes
126 searching for changes
127 remote: adding changesets
127 remote: adding changesets
128 remote: adding manifests
128 remote: adding manifests
129 remote: adding file changes
129 remote: adding file changes
130 remote: added 1 changesets with 1 changes to 1 files
130 remote: added 1 changesets with 1 changes to 1 files
131 $ cd ..
131 $ cd ..
132
132
133 $ cd master
133 $ cd master
134 $ hg log f2 -T '{rev}: {desc}\n'
134 $ hg log f2 -T '{rev}: {desc}\n'
135 30: Update#3 to f2
135 30: Update#3 to f2
136 21: Update#2 to f2
136 21: Update#2 to f2
137 11: Update#1 to f2
137 11: Update#1 to f2
138 1: Commit f2
138 1: Commit f2
139 $ hg log -l 3 -T '{rev}: {desc}\n'
139 $ hg log -l 3 -T '{rev}: {desc}\n'
140 30: Update#3 to f2
140 30: Update#3 to f2
141 29: Update#2 to f10
141 29: Update#2 to f10
142 28: Update#2 to f9
142 28: Update#2 to f9
143
143
144 Can pull into repo with a single commit
144 Can pull into repo with a single commit
145
145
146 $ cd ..
146 $ cd ..
147 $ hg clone -q --narrow ssh://user@dummy/master narrow2 --include "f1" -r 0
147 $ hg clone -q --narrow ssh://user@dummy/master narrow2 --include "f1" -r 0
148 $ cd narrow2
148 $ cd narrow2
149 $ hg pull -q -r 1
149 $ hg pull -q -r 1
150 remote: abort: unexpected error: unable to resolve parent while packing '00manifest.i' 1 for changeset 0
150 remote: abort: unexpected error: unable to resolve parent while packing '00manifest' 1 for changeset 0
151 transaction abort!
151 transaction abort!
152 rollback completed
152 rollback completed
153 abort: pull failed on remote
153 abort: pull failed on remote
154 [100]
154 [100]
General Comments 0
You need to be logged in to leave comments. Login now