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