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