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