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