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