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