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