##// END OF EJS Templates
changegroup: remove dictionary creation from deltachunk...
Durham Goode -
r34295:05131c96 default
parent child Browse files
Show More
@@ -1,1014 +1,1005 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 tempfile
12 import tempfile
13 import weakref
13 import weakref
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 hex,
17 hex,
18 nullrev,
18 nullrev,
19 short,
19 short,
20 )
20 )
21
21
22 from . import (
22 from . import (
23 dagutil,
23 dagutil,
24 error,
24 error,
25 mdiff,
25 mdiff,
26 phases,
26 phases,
27 pycompat,
27 pycompat,
28 util,
28 util,
29 )
29 )
30
30
31 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
31 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
32 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
32 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
33 _CHANGEGROUPV3_DELTA_HEADER = ">20s20s20s20s20sH"
33 _CHANGEGROUPV3_DELTA_HEADER = ">20s20s20s20s20sH"
34
34
35 def readexactly(stream, n):
35 def readexactly(stream, n):
36 '''read n bytes from stream.read and abort if less was available'''
36 '''read n bytes from stream.read and abort if less was available'''
37 s = stream.read(n)
37 s = stream.read(n)
38 if len(s) < n:
38 if len(s) < n:
39 raise error.Abort(_("stream ended unexpectedly"
39 raise error.Abort(_("stream ended unexpectedly"
40 " (got %d bytes, expected %d)")
40 " (got %d bytes, expected %d)")
41 % (len(s), n))
41 % (len(s), n))
42 return s
42 return s
43
43
44 def getchunk(stream):
44 def getchunk(stream):
45 """return the next chunk from stream as a string"""
45 """return the next chunk from stream as a string"""
46 d = readexactly(stream, 4)
46 d = readexactly(stream, 4)
47 l = struct.unpack(">l", d)[0]
47 l = struct.unpack(">l", d)[0]
48 if l <= 4:
48 if l <= 4:
49 if l:
49 if l:
50 raise error.Abort(_("invalid chunk length %d") % l)
50 raise error.Abort(_("invalid chunk length %d") % l)
51 return ""
51 return ""
52 return readexactly(stream, l - 4)
52 return readexactly(stream, l - 4)
53
53
54 def chunkheader(length):
54 def chunkheader(length):
55 """return a changegroup chunk header (string)"""
55 """return a changegroup chunk header (string)"""
56 return struct.pack(">l", length + 4)
56 return struct.pack(">l", length + 4)
57
57
58 def closechunk():
58 def closechunk():
59 """return a changegroup chunk header (string) for a zero-length chunk"""
59 """return a changegroup chunk header (string) for a zero-length chunk"""
60 return struct.pack(">l", 0)
60 return struct.pack(">l", 0)
61
61
62 def writechunks(ui, chunks, filename, vfs=None):
62 def writechunks(ui, chunks, filename, vfs=None):
63 """Write chunks to a file and return its filename.
63 """Write chunks to a file and return its filename.
64
64
65 The stream is assumed to be a bundle file.
65 The stream is assumed to be a bundle file.
66 Existing files will not be overwritten.
66 Existing files will not be overwritten.
67 If no filename is specified, a temporary file is created.
67 If no filename is specified, a temporary file is created.
68 """
68 """
69 fh = None
69 fh = None
70 cleanup = None
70 cleanup = None
71 try:
71 try:
72 if filename:
72 if filename:
73 if vfs:
73 if vfs:
74 fh = vfs.open(filename, "wb")
74 fh = vfs.open(filename, "wb")
75 else:
75 else:
76 # Increase default buffer size because default is usually
76 # Increase default buffer size because default is usually
77 # small (4k is common on Linux).
77 # small (4k is common on Linux).
78 fh = open(filename, "wb", 131072)
78 fh = open(filename, "wb", 131072)
79 else:
79 else:
80 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
80 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
81 fh = os.fdopen(fd, pycompat.sysstr("wb"))
81 fh = os.fdopen(fd, pycompat.sysstr("wb"))
82 cleanup = filename
82 cleanup = filename
83 for c in chunks:
83 for c in chunks:
84 fh.write(c)
84 fh.write(c)
85 cleanup = None
85 cleanup = None
86 return filename
86 return filename
87 finally:
87 finally:
88 if fh is not None:
88 if fh is not None:
89 fh.close()
89 fh.close()
90 if cleanup is not None:
90 if cleanup is not None:
91 if filename and vfs:
91 if filename and vfs:
92 vfs.unlink(cleanup)
92 vfs.unlink(cleanup)
93 else:
93 else:
94 os.unlink(cleanup)
94 os.unlink(cleanup)
95
95
96 class cg1unpacker(object):
96 class cg1unpacker(object):
97 """Unpacker for cg1 changegroup streams.
97 """Unpacker for cg1 changegroup streams.
98
98
99 A changegroup unpacker handles the framing of the revision data in
99 A changegroup unpacker handles the framing of the revision data in
100 the wire format. Most consumers will want to use the apply()
100 the wire format. Most consumers will want to use the apply()
101 method to add the changes from the changegroup to a repository.
101 method to add the changes from the changegroup to a repository.
102
102
103 If you're forwarding a changegroup unmodified to another consumer,
103 If you're forwarding a changegroup unmodified to another consumer,
104 use getchunks(), which returns an iterator of changegroup
104 use getchunks(), which returns an iterator of changegroup
105 chunks. This is mostly useful for cases where you need to know the
105 chunks. This is mostly useful for cases where you need to know the
106 data stream has ended by observing the end of the changegroup.
106 data stream has ended by observing the end of the changegroup.
107
107
108 deltachunk() is useful only if you're applying delta data. Most
108 deltachunk() is useful only if you're applying delta data. Most
109 consumers should prefer apply() instead.
109 consumers should prefer apply() instead.
110
110
111 A few other public methods exist. Those are used only for
111 A few other public methods exist. Those are used only for
112 bundlerepo and some debug commands - their use is discouraged.
112 bundlerepo and some debug commands - their use is discouraged.
113 """
113 """
114 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
114 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
115 deltaheadersize = struct.calcsize(deltaheader)
115 deltaheadersize = struct.calcsize(deltaheader)
116 version = '01'
116 version = '01'
117 _grouplistcount = 1 # One list of files after the manifests
117 _grouplistcount = 1 # One list of files after the manifests
118
118
119 def __init__(self, fh, alg, extras=None):
119 def __init__(self, fh, alg, extras=None):
120 if alg is None:
120 if alg is None:
121 alg = 'UN'
121 alg = 'UN'
122 if alg not in util.compengines.supportedbundletypes:
122 if alg not in util.compengines.supportedbundletypes:
123 raise error.Abort(_('unknown stream compression type: %s')
123 raise error.Abort(_('unknown stream compression type: %s')
124 % alg)
124 % alg)
125 if alg == 'BZ':
125 if alg == 'BZ':
126 alg = '_truncatedBZ'
126 alg = '_truncatedBZ'
127
127
128 compengine = util.compengines.forbundletype(alg)
128 compengine = util.compengines.forbundletype(alg)
129 self._stream = compengine.decompressorreader(fh)
129 self._stream = compengine.decompressorreader(fh)
130 self._type = alg
130 self._type = alg
131 self.extras = extras or {}
131 self.extras = extras or {}
132 self.callback = None
132 self.callback = None
133
133
134 # These methods (compressed, read, seek, tell) all appear to only
134 # These methods (compressed, read, seek, tell) all appear to only
135 # be used by bundlerepo, but it's a little hard to tell.
135 # be used by bundlerepo, but it's a little hard to tell.
136 def compressed(self):
136 def compressed(self):
137 return self._type is not None and self._type != 'UN'
137 return self._type is not None and self._type != 'UN'
138 def read(self, l):
138 def read(self, l):
139 return self._stream.read(l)
139 return self._stream.read(l)
140 def seek(self, pos):
140 def seek(self, pos):
141 return self._stream.seek(pos)
141 return self._stream.seek(pos)
142 def tell(self):
142 def tell(self):
143 return self._stream.tell()
143 return self._stream.tell()
144 def close(self):
144 def close(self):
145 return self._stream.close()
145 return self._stream.close()
146
146
147 def _chunklength(self):
147 def _chunklength(self):
148 d = readexactly(self._stream, 4)
148 d = readexactly(self._stream, 4)
149 l = struct.unpack(">l", d)[0]
149 l = struct.unpack(">l", d)[0]
150 if l <= 4:
150 if l <= 4:
151 if l:
151 if l:
152 raise error.Abort(_("invalid chunk length %d") % l)
152 raise error.Abort(_("invalid chunk length %d") % l)
153 return 0
153 return 0
154 if self.callback:
154 if self.callback:
155 self.callback()
155 self.callback()
156 return l - 4
156 return l - 4
157
157
158 def changelogheader(self):
158 def changelogheader(self):
159 """v10 does not have a changelog header chunk"""
159 """v10 does not have a changelog header chunk"""
160 return {}
160 return {}
161
161
162 def manifestheader(self):
162 def manifestheader(self):
163 """v10 does not have a manifest header chunk"""
163 """v10 does not have a manifest header chunk"""
164 return {}
164 return {}
165
165
166 def filelogheader(self):
166 def filelogheader(self):
167 """return the header of the filelogs chunk, v10 only has the filename"""
167 """return the header of the filelogs chunk, v10 only has the filename"""
168 l = self._chunklength()
168 l = self._chunklength()
169 if not l:
169 if not l:
170 return {}
170 return {}
171 fname = readexactly(self._stream, l)
171 fname = readexactly(self._stream, l)
172 return {'filename': fname}
172 return {'filename': fname}
173
173
174 def _deltaheader(self, headertuple, prevnode):
174 def _deltaheader(self, headertuple, prevnode):
175 node, p1, p2, cs = headertuple
175 node, p1, p2, cs = headertuple
176 if prevnode is None:
176 if prevnode is None:
177 deltabase = p1
177 deltabase = p1
178 else:
178 else:
179 deltabase = prevnode
179 deltabase = prevnode
180 flags = 0
180 flags = 0
181 return node, p1, p2, deltabase, cs, flags
181 return node, p1, p2, deltabase, cs, flags
182
182
183 def deltachunk(self, prevnode):
183 def deltachunk(self, prevnode):
184 l = self._chunklength()
184 l = self._chunklength()
185 if not l:
185 if not l:
186 return {}
186 return {}
187 headerdata = readexactly(self._stream, self.deltaheadersize)
187 headerdata = readexactly(self._stream, self.deltaheadersize)
188 header = struct.unpack(self.deltaheader, headerdata)
188 header = struct.unpack(self.deltaheader, headerdata)
189 delta = readexactly(self._stream, l - self.deltaheadersize)
189 delta = readexactly(self._stream, l - self.deltaheadersize)
190 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
190 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
191 return {'node': node, 'p1': p1, 'p2': p2, 'cs': cs,
191 return (node, p1, p2, cs, deltabase, delta, flags)
192 'deltabase': deltabase, 'delta': delta, 'flags': flags}
193
192
194 def getchunks(self):
193 def getchunks(self):
195 """returns all the chunks contains in the bundle
194 """returns all the chunks contains in the bundle
196
195
197 Used when you need to forward the binary stream to a file or another
196 Used when you need to forward the binary stream to a file or another
198 network API. To do so, it parse the changegroup data, otherwise it will
197 network API. To do so, it parse the changegroup data, otherwise it will
199 block in case of sshrepo because it don't know the end of the stream.
198 block in case of sshrepo because it don't know the end of the stream.
200 """
199 """
201 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
200 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
202 # and a list of filelogs. For changegroup 3, we expect 4 parts:
201 # and a list of filelogs. For changegroup 3, we expect 4 parts:
203 # changelog, manifestlog, a list of tree manifestlogs, and a list of
202 # changelog, manifestlog, a list of tree manifestlogs, and a list of
204 # filelogs.
203 # filelogs.
205 #
204 #
206 # Changelog and manifestlog parts are terminated with empty chunks. The
205 # Changelog and manifestlog parts are terminated with empty chunks. The
207 # tree and file parts are a list of entry sections. Each entry section
206 # tree and file parts are a list of entry sections. Each entry section
208 # is a series of chunks terminating in an empty chunk. The list of these
207 # is a series of chunks terminating in an empty chunk. The list of these
209 # entry sections is terminated in yet another empty chunk, so we know
208 # entry sections is terminated in yet another empty chunk, so we know
210 # we've reached the end of the tree/file list when we reach an empty
209 # we've reached the end of the tree/file list when we reach an empty
211 # chunk that was proceeded by no non-empty chunks.
210 # chunk that was proceeded by no non-empty chunks.
212
211
213 parts = 0
212 parts = 0
214 while parts < 2 + self._grouplistcount:
213 while parts < 2 + self._grouplistcount:
215 noentries = True
214 noentries = True
216 while True:
215 while True:
217 chunk = getchunk(self)
216 chunk = getchunk(self)
218 if not chunk:
217 if not chunk:
219 # The first two empty chunks represent the end of the
218 # The first two empty chunks represent the end of the
220 # changelog and the manifestlog portions. The remaining
219 # changelog and the manifestlog portions. The remaining
221 # empty chunks represent either A) the end of individual
220 # empty chunks represent either A) the end of individual
222 # tree or file entries in the file list, or B) the end of
221 # tree or file entries in the file list, or B) the end of
223 # the entire list. It's the end of the entire list if there
222 # the entire list. It's the end of the entire list if there
224 # were no entries (i.e. noentries is True).
223 # were no entries (i.e. noentries is True).
225 if parts < 2:
224 if parts < 2:
226 parts += 1
225 parts += 1
227 elif noentries:
226 elif noentries:
228 parts += 1
227 parts += 1
229 break
228 break
230 noentries = False
229 noentries = False
231 yield chunkheader(len(chunk))
230 yield chunkheader(len(chunk))
232 pos = 0
231 pos = 0
233 while pos < len(chunk):
232 while pos < len(chunk):
234 next = pos + 2**20
233 next = pos + 2**20
235 yield chunk[pos:next]
234 yield chunk[pos:next]
236 pos = next
235 pos = next
237 yield closechunk()
236 yield closechunk()
238
237
239 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
238 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
240 # We know that we'll never have more manifests than we had
239 # We know that we'll never have more manifests than we had
241 # changesets.
240 # changesets.
242 self.callback = prog(_('manifests'), numchanges)
241 self.callback = prog(_('manifests'), numchanges)
243 # no need to check for empty manifest group here:
242 # no need to check for empty manifest group here:
244 # if the result of the merge of 1 and 2 is the same in 3 and 4,
243 # if the result of the merge of 1 and 2 is the same in 3 and 4,
245 # no new manifest will be created and the manifest group will
244 # no new manifest will be created and the manifest group will
246 # be empty during the pull
245 # be empty during the pull
247 self.manifestheader()
246 self.manifestheader()
248 deltas = self.deltaiter()
247 deltas = self.deltaiter()
249 repo.manifestlog._revlog.addgroup(deltas, revmap, trp)
248 repo.manifestlog._revlog.addgroup(deltas, revmap, trp)
250 repo.ui.progress(_('manifests'), None)
249 repo.ui.progress(_('manifests'), None)
251 self.callback = None
250 self.callback = None
252
251
253 def apply(self, repo, tr, srctype, url, targetphase=phases.draft,
252 def apply(self, repo, tr, srctype, url, targetphase=phases.draft,
254 expectedtotal=None):
253 expectedtotal=None):
255 """Add the changegroup returned by source.read() to this repo.
254 """Add the changegroup returned by source.read() to this repo.
256 srctype is a string like 'push', 'pull', or 'unbundle'. url is
255 srctype is a string like 'push', 'pull', or 'unbundle'. url is
257 the URL of the repo where this changegroup is coming from.
256 the URL of the repo where this changegroup is coming from.
258
257
259 Return an integer summarizing the change to this repo:
258 Return an integer summarizing the change to this repo:
260 - nothing changed or no source: 0
259 - nothing changed or no source: 0
261 - more heads than before: 1+added heads (2..n)
260 - more heads than before: 1+added heads (2..n)
262 - fewer heads than before: -1-removed heads (-2..-n)
261 - fewer heads than before: -1-removed heads (-2..-n)
263 - number of heads stays the same: 1
262 - number of heads stays the same: 1
264 """
263 """
265 repo = repo.unfiltered()
264 repo = repo.unfiltered()
266 def csmap(x):
265 def csmap(x):
267 repo.ui.debug("add changeset %s\n" % short(x))
266 repo.ui.debug("add changeset %s\n" % short(x))
268 return len(cl)
267 return len(cl)
269
268
270 def revmap(x):
269 def revmap(x):
271 return cl.rev(x)
270 return cl.rev(x)
272
271
273 changesets = files = revisions = 0
272 changesets = files = revisions = 0
274
273
275 try:
274 try:
276 # The transaction may already carry source information. In this
275 # The transaction may already carry source information. In this
277 # case we use the top level data. We overwrite the argument
276 # case we use the top level data. We overwrite the argument
278 # because we need to use the top level value (if they exist)
277 # because we need to use the top level value (if they exist)
279 # in this function.
278 # in this function.
280 srctype = tr.hookargs.setdefault('source', srctype)
279 srctype = tr.hookargs.setdefault('source', srctype)
281 url = tr.hookargs.setdefault('url', url)
280 url = tr.hookargs.setdefault('url', url)
282 repo.hook('prechangegroup',
281 repo.hook('prechangegroup',
283 throw=True, **pycompat.strkwargs(tr.hookargs))
282 throw=True, **pycompat.strkwargs(tr.hookargs))
284
283
285 # write changelog data to temp files so concurrent readers
284 # write changelog data to temp files so concurrent readers
286 # will not see an inconsistent view
285 # will not see an inconsistent view
287 cl = repo.changelog
286 cl = repo.changelog
288 cl.delayupdate(tr)
287 cl.delayupdate(tr)
289 oldheads = set(cl.heads())
288 oldheads = set(cl.heads())
290
289
291 trp = weakref.proxy(tr)
290 trp = weakref.proxy(tr)
292 # pull off the changeset group
291 # pull off the changeset group
293 repo.ui.status(_("adding changesets\n"))
292 repo.ui.status(_("adding changesets\n"))
294 clstart = len(cl)
293 clstart = len(cl)
295 class prog(object):
294 class prog(object):
296 def __init__(self, step, total):
295 def __init__(self, step, total):
297 self._step = step
296 self._step = step
298 self._total = total
297 self._total = total
299 self._count = 1
298 self._count = 1
300 def __call__(self):
299 def __call__(self):
301 repo.ui.progress(self._step, self._count, unit=_('chunks'),
300 repo.ui.progress(self._step, self._count, unit=_('chunks'),
302 total=self._total)
301 total=self._total)
303 self._count += 1
302 self._count += 1
304 self.callback = prog(_('changesets'), expectedtotal)
303 self.callback = prog(_('changesets'), expectedtotal)
305
304
306 efiles = set()
305 efiles = set()
307 def onchangelog(cl, node):
306 def onchangelog(cl, node):
308 efiles.update(cl.readfiles(node))
307 efiles.update(cl.readfiles(node))
309
308
310 self.changelogheader()
309 self.changelogheader()
311 deltas = self.deltaiter()
310 deltas = self.deltaiter()
312 cgnodes = cl.addgroup(deltas, csmap, trp, addrevisioncb=onchangelog)
311 cgnodes = cl.addgroup(deltas, csmap, trp, addrevisioncb=onchangelog)
313 efiles = len(efiles)
312 efiles = len(efiles)
314
313
315 if not cgnodes:
314 if not cgnodes:
316 repo.ui.develwarn('applied empty changegroup',
315 repo.ui.develwarn('applied empty changegroup',
317 config='empty-changegroup')
316 config='empty-changegroup')
318 clend = len(cl)
317 clend = len(cl)
319 changesets = clend - clstart
318 changesets = clend - clstart
320 repo.ui.progress(_('changesets'), None)
319 repo.ui.progress(_('changesets'), None)
321 self.callback = None
320 self.callback = None
322
321
323 # pull off the manifest group
322 # pull off the manifest group
324 repo.ui.status(_("adding manifests\n"))
323 repo.ui.status(_("adding manifests\n"))
325 self._unpackmanifests(repo, revmap, trp, prog, changesets)
324 self._unpackmanifests(repo, revmap, trp, prog, changesets)
326
325
327 needfiles = {}
326 needfiles = {}
328 if repo.ui.configbool('server', 'validate'):
327 if repo.ui.configbool('server', 'validate'):
329 cl = repo.changelog
328 cl = repo.changelog
330 ml = repo.manifestlog
329 ml = repo.manifestlog
331 # validate incoming csets have their manifests
330 # validate incoming csets have their manifests
332 for cset in xrange(clstart, clend):
331 for cset in xrange(clstart, clend):
333 mfnode = cl.changelogrevision(cset).manifest
332 mfnode = cl.changelogrevision(cset).manifest
334 mfest = ml[mfnode].readdelta()
333 mfest = ml[mfnode].readdelta()
335 # store file cgnodes we must see
334 # store file cgnodes we must see
336 for f, n in mfest.iteritems():
335 for f, n in mfest.iteritems():
337 needfiles.setdefault(f, set()).add(n)
336 needfiles.setdefault(f, set()).add(n)
338
337
339 # process the files
338 # process the files
340 repo.ui.status(_("adding file changes\n"))
339 repo.ui.status(_("adding file changes\n"))
341 newrevs, newfiles = _addchangegroupfiles(
340 newrevs, newfiles = _addchangegroupfiles(
342 repo, self, revmap, trp, efiles, needfiles)
341 repo, self, revmap, trp, efiles, needfiles)
343 revisions += newrevs
342 revisions += newrevs
344 files += newfiles
343 files += newfiles
345
344
346 deltaheads = 0
345 deltaheads = 0
347 if oldheads:
346 if oldheads:
348 heads = cl.heads()
347 heads = cl.heads()
349 deltaheads = len(heads) - len(oldheads)
348 deltaheads = len(heads) - len(oldheads)
350 for h in heads:
349 for h in heads:
351 if h not in oldheads and repo[h].closesbranch():
350 if h not in oldheads and repo[h].closesbranch():
352 deltaheads -= 1
351 deltaheads -= 1
353 htext = ""
352 htext = ""
354 if deltaheads:
353 if deltaheads:
355 htext = _(" (%+d heads)") % deltaheads
354 htext = _(" (%+d heads)") % deltaheads
356
355
357 repo.ui.status(_("added %d changesets"
356 repo.ui.status(_("added %d changesets"
358 " with %d changes to %d files%s\n")
357 " with %d changes to %d files%s\n")
359 % (changesets, revisions, files, htext))
358 % (changesets, revisions, files, htext))
360 repo.invalidatevolatilesets()
359 repo.invalidatevolatilesets()
361
360
362 if changesets > 0:
361 if changesets > 0:
363 if 'node' not in tr.hookargs:
362 if 'node' not in tr.hookargs:
364 tr.hookargs['node'] = hex(cl.node(clstart))
363 tr.hookargs['node'] = hex(cl.node(clstart))
365 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
364 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
366 hookargs = dict(tr.hookargs)
365 hookargs = dict(tr.hookargs)
367 else:
366 else:
368 hookargs = dict(tr.hookargs)
367 hookargs = dict(tr.hookargs)
369 hookargs['node'] = hex(cl.node(clstart))
368 hookargs['node'] = hex(cl.node(clstart))
370 hookargs['node_last'] = hex(cl.node(clend - 1))
369 hookargs['node_last'] = hex(cl.node(clend - 1))
371 repo.hook('pretxnchangegroup',
370 repo.hook('pretxnchangegroup',
372 throw=True, **pycompat.strkwargs(hookargs))
371 throw=True, **pycompat.strkwargs(hookargs))
373
372
374 added = [cl.node(r) for r in xrange(clstart, clend)]
373 added = [cl.node(r) for r in xrange(clstart, clend)]
375 phaseall = None
374 phaseall = None
376 if srctype in ('push', 'serve'):
375 if srctype in ('push', 'serve'):
377 # Old servers can not push the boundary themselves.
376 # Old servers can not push the boundary themselves.
378 # New servers won't push the boundary if changeset already
377 # New servers won't push the boundary if changeset already
379 # exists locally as secret
378 # exists locally as secret
380 #
379 #
381 # We should not use added here but the list of all change in
380 # We should not use added here but the list of all change in
382 # the bundle
381 # the bundle
383 if repo.publishing():
382 if repo.publishing():
384 targetphase = phaseall = phases.public
383 targetphase = phaseall = phases.public
385 else:
384 else:
386 # closer target phase computation
385 # closer target phase computation
387
386
388 # Those changesets have been pushed from the
387 # Those changesets have been pushed from the
389 # outside, their phases are going to be pushed
388 # outside, their phases are going to be pushed
390 # alongside. Therefor `targetphase` is
389 # alongside. Therefor `targetphase` is
391 # ignored.
390 # ignored.
392 targetphase = phaseall = phases.draft
391 targetphase = phaseall = phases.draft
393 if added:
392 if added:
394 phases.registernew(repo, tr, targetphase, added)
393 phases.registernew(repo, tr, targetphase, added)
395 if phaseall is not None:
394 if phaseall is not None:
396 phases.advanceboundary(repo, tr, phaseall, cgnodes)
395 phases.advanceboundary(repo, tr, phaseall, cgnodes)
397
396
398 if changesets > 0:
397 if changesets > 0:
399
398
400 def runhooks():
399 def runhooks():
401 # These hooks run when the lock releases, not when the
400 # These hooks run when the lock releases, not when the
402 # transaction closes. So it's possible for the changelog
401 # transaction closes. So it's possible for the changelog
403 # to have changed since we last saw it.
402 # to have changed since we last saw it.
404 if clstart >= len(repo):
403 if clstart >= len(repo):
405 return
404 return
406
405
407 repo.hook("changegroup", **pycompat.strkwargs(hookargs))
406 repo.hook("changegroup", **pycompat.strkwargs(hookargs))
408
407
409 for n in added:
408 for n in added:
410 args = hookargs.copy()
409 args = hookargs.copy()
411 args['node'] = hex(n)
410 args['node'] = hex(n)
412 del args['node_last']
411 del args['node_last']
413 repo.hook("incoming", **pycompat.strkwargs(args))
412 repo.hook("incoming", **pycompat.strkwargs(args))
414
413
415 newheads = [h for h in repo.heads()
414 newheads = [h for h in repo.heads()
416 if h not in oldheads]
415 if h not in oldheads]
417 repo.ui.log("incoming",
416 repo.ui.log("incoming",
418 "%s incoming changes - new heads: %s\n",
417 "%s incoming changes - new heads: %s\n",
419 len(added),
418 len(added),
420 ', '.join([hex(c[:6]) for c in newheads]))
419 ', '.join([hex(c[:6]) for c in newheads]))
421
420
422 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
421 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
423 lambda tr: repo._afterlock(runhooks))
422 lambda tr: repo._afterlock(runhooks))
424 finally:
423 finally:
425 repo.ui.flush()
424 repo.ui.flush()
426 # never return 0 here:
425 # never return 0 here:
427 if deltaheads < 0:
426 if deltaheads < 0:
428 ret = deltaheads - 1
427 ret = deltaheads - 1
429 else:
428 else:
430 ret = deltaheads + 1
429 ret = deltaheads + 1
431 return ret
430 return ret
432
431
433 def deltaiter(self):
432 def deltaiter(self):
434 """
433 """
435 returns an iterator of the deltas in this changegroup
434 returns an iterator of the deltas in this changegroup
436
435
437 Useful for passing to the underlying storage system to be stored.
436 Useful for passing to the underlying storage system to be stored.
438 """
437 """
439 chain = None
438 chain = None
440 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
439 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
441 node = chunkdata['node']
440 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags)
442 p1 = chunkdata['p1']
441 yield chunkdata
443 p2 = chunkdata['p2']
442 chain = chunkdata[0]
444 cs = chunkdata['cs']
445 deltabase = chunkdata['deltabase']
446 delta = chunkdata['delta']
447 flags = chunkdata['flags']
448
449 chain = node
450
451 yield (node, p1, p2, cs, deltabase, delta, flags)
452
443
453 class cg2unpacker(cg1unpacker):
444 class cg2unpacker(cg1unpacker):
454 """Unpacker for cg2 streams.
445 """Unpacker for cg2 streams.
455
446
456 cg2 streams add support for generaldelta, so the delta header
447 cg2 streams add support for generaldelta, so the delta header
457 format is slightly different. All other features about the data
448 format is slightly different. All other features about the data
458 remain the same.
449 remain the same.
459 """
450 """
460 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
451 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
461 deltaheadersize = struct.calcsize(deltaheader)
452 deltaheadersize = struct.calcsize(deltaheader)
462 version = '02'
453 version = '02'
463
454
464 def _deltaheader(self, headertuple, prevnode):
455 def _deltaheader(self, headertuple, prevnode):
465 node, p1, p2, deltabase, cs = headertuple
456 node, p1, p2, deltabase, cs = headertuple
466 flags = 0
457 flags = 0
467 return node, p1, p2, deltabase, cs, flags
458 return node, p1, p2, deltabase, cs, flags
468
459
469 class cg3unpacker(cg2unpacker):
460 class cg3unpacker(cg2unpacker):
470 """Unpacker for cg3 streams.
461 """Unpacker for cg3 streams.
471
462
472 cg3 streams add support for exchanging treemanifests and revlog
463 cg3 streams add support for exchanging treemanifests and revlog
473 flags. It adds the revlog flags to the delta header and an empty chunk
464 flags. It adds the revlog flags to the delta header and an empty chunk
474 separating manifests and files.
465 separating manifests and files.
475 """
466 """
476 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
467 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
477 deltaheadersize = struct.calcsize(deltaheader)
468 deltaheadersize = struct.calcsize(deltaheader)
478 version = '03'
469 version = '03'
479 _grouplistcount = 2 # One list of manifests and one list of files
470 _grouplistcount = 2 # One list of manifests and one list of files
480
471
481 def _deltaheader(self, headertuple, prevnode):
472 def _deltaheader(self, headertuple, prevnode):
482 node, p1, p2, deltabase, cs, flags = headertuple
473 node, p1, p2, deltabase, cs, flags = headertuple
483 return node, p1, p2, deltabase, cs, flags
474 return node, p1, p2, deltabase, cs, flags
484
475
485 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
476 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
486 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog,
477 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog,
487 numchanges)
478 numchanges)
488 for chunkdata in iter(self.filelogheader, {}):
479 for chunkdata in iter(self.filelogheader, {}):
489 # If we get here, there are directory manifests in the changegroup
480 # If we get here, there are directory manifests in the changegroup
490 d = chunkdata["filename"]
481 d = chunkdata["filename"]
491 repo.ui.debug("adding %s revisions\n" % d)
482 repo.ui.debug("adding %s revisions\n" % d)
492 dirlog = repo.manifestlog._revlog.dirlog(d)
483 dirlog = repo.manifestlog._revlog.dirlog(d)
493 deltas = self.deltaiter()
484 deltas = self.deltaiter()
494 if not dirlog.addgroup(deltas, revmap, trp):
485 if not dirlog.addgroup(deltas, revmap, trp):
495 raise error.Abort(_("received dir revlog group is empty"))
486 raise error.Abort(_("received dir revlog group is empty"))
496
487
497 class headerlessfixup(object):
488 class headerlessfixup(object):
498 def __init__(self, fh, h):
489 def __init__(self, fh, h):
499 self._h = h
490 self._h = h
500 self._fh = fh
491 self._fh = fh
501 def read(self, n):
492 def read(self, n):
502 if self._h:
493 if self._h:
503 d, self._h = self._h[:n], self._h[n:]
494 d, self._h = self._h[:n], self._h[n:]
504 if len(d) < n:
495 if len(d) < n:
505 d += readexactly(self._fh, n - len(d))
496 d += readexactly(self._fh, n - len(d))
506 return d
497 return d
507 return readexactly(self._fh, n)
498 return readexactly(self._fh, n)
508
499
509 class cg1packer(object):
500 class cg1packer(object):
510 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
501 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
511 version = '01'
502 version = '01'
512 def __init__(self, repo, bundlecaps=None):
503 def __init__(self, repo, bundlecaps=None):
513 """Given a source repo, construct a bundler.
504 """Given a source repo, construct a bundler.
514
505
515 bundlecaps is optional and can be used to specify the set of
506 bundlecaps is optional and can be used to specify the set of
516 capabilities which can be used to build the bundle. While bundlecaps is
507 capabilities which can be used to build the bundle. While bundlecaps is
517 unused in core Mercurial, extensions rely on this feature to communicate
508 unused in core Mercurial, extensions rely on this feature to communicate
518 capabilities to customize the changegroup packer.
509 capabilities to customize the changegroup packer.
519 """
510 """
520 # Set of capabilities we can use to build the bundle.
511 # Set of capabilities we can use to build the bundle.
521 if bundlecaps is None:
512 if bundlecaps is None:
522 bundlecaps = set()
513 bundlecaps = set()
523 self._bundlecaps = bundlecaps
514 self._bundlecaps = bundlecaps
524 # experimental config: bundle.reorder
515 # experimental config: bundle.reorder
525 reorder = repo.ui.config('bundle', 'reorder')
516 reorder = repo.ui.config('bundle', 'reorder')
526 if reorder == 'auto':
517 if reorder == 'auto':
527 reorder = None
518 reorder = None
528 else:
519 else:
529 reorder = util.parsebool(reorder)
520 reorder = util.parsebool(reorder)
530 self._repo = repo
521 self._repo = repo
531 self._reorder = reorder
522 self._reorder = reorder
532 self._progress = repo.ui.progress
523 self._progress = repo.ui.progress
533 if self._repo.ui.verbose and not self._repo.ui.debugflag:
524 if self._repo.ui.verbose and not self._repo.ui.debugflag:
534 self._verbosenote = self._repo.ui.note
525 self._verbosenote = self._repo.ui.note
535 else:
526 else:
536 self._verbosenote = lambda s: None
527 self._verbosenote = lambda s: None
537
528
538 def close(self):
529 def close(self):
539 return closechunk()
530 return closechunk()
540
531
541 def fileheader(self, fname):
532 def fileheader(self, fname):
542 return chunkheader(len(fname)) + fname
533 return chunkheader(len(fname)) + fname
543
534
544 # Extracted both for clarity and for overriding in extensions.
535 # Extracted both for clarity and for overriding in extensions.
545 def _sortgroup(self, revlog, nodelist, lookup):
536 def _sortgroup(self, revlog, nodelist, lookup):
546 """Sort nodes for change group and turn them into revnums."""
537 """Sort nodes for change group and turn them into revnums."""
547 # for generaldelta revlogs, we linearize the revs; this will both be
538 # for generaldelta revlogs, we linearize the revs; this will both be
548 # much quicker and generate a much smaller bundle
539 # much quicker and generate a much smaller bundle
549 if (revlog._generaldelta and self._reorder is None) or self._reorder:
540 if (revlog._generaldelta and self._reorder is None) or self._reorder:
550 dag = dagutil.revlogdag(revlog)
541 dag = dagutil.revlogdag(revlog)
551 return dag.linearize(set(revlog.rev(n) for n in nodelist))
542 return dag.linearize(set(revlog.rev(n) for n in nodelist))
552 else:
543 else:
553 return sorted([revlog.rev(n) for n in nodelist])
544 return sorted([revlog.rev(n) for n in nodelist])
554
545
555 def group(self, nodelist, revlog, lookup, units=None):
546 def group(self, nodelist, revlog, lookup, units=None):
556 """Calculate a delta group, yielding a sequence of changegroup chunks
547 """Calculate a delta group, yielding a sequence of changegroup chunks
557 (strings).
548 (strings).
558
549
559 Given a list of changeset revs, return a set of deltas and
550 Given a list of changeset revs, return a set of deltas and
560 metadata corresponding to nodes. The first delta is
551 metadata corresponding to nodes. The first delta is
561 first parent(nodelist[0]) -> nodelist[0], the receiver is
552 first parent(nodelist[0]) -> nodelist[0], the receiver is
562 guaranteed to have this parent as it has all history before
553 guaranteed to have this parent as it has all history before
563 these changesets. In the case firstparent is nullrev the
554 these changesets. In the case firstparent is nullrev the
564 changegroup starts with a full revision.
555 changegroup starts with a full revision.
565
556
566 If units is not None, progress detail will be generated, units specifies
557 If units is not None, progress detail will be generated, units specifies
567 the type of revlog that is touched (changelog, manifest, etc.).
558 the type of revlog that is touched (changelog, manifest, etc.).
568 """
559 """
569 # if we don't have any revisions touched by these changesets, bail
560 # if we don't have any revisions touched by these changesets, bail
570 if len(nodelist) == 0:
561 if len(nodelist) == 0:
571 yield self.close()
562 yield self.close()
572 return
563 return
573
564
574 revs = self._sortgroup(revlog, nodelist, lookup)
565 revs = self._sortgroup(revlog, nodelist, lookup)
575
566
576 # add the parent of the first rev
567 # add the parent of the first rev
577 p = revlog.parentrevs(revs[0])[0]
568 p = revlog.parentrevs(revs[0])[0]
578 revs.insert(0, p)
569 revs.insert(0, p)
579
570
580 # build deltas
571 # build deltas
581 total = len(revs) - 1
572 total = len(revs) - 1
582 msgbundling = _('bundling')
573 msgbundling = _('bundling')
583 for r in xrange(len(revs) - 1):
574 for r in xrange(len(revs) - 1):
584 if units is not None:
575 if units is not None:
585 self._progress(msgbundling, r + 1, unit=units, total=total)
576 self._progress(msgbundling, r + 1, unit=units, total=total)
586 prev, curr = revs[r], revs[r + 1]
577 prev, curr = revs[r], revs[r + 1]
587 linknode = lookup(revlog.node(curr))
578 linknode = lookup(revlog.node(curr))
588 for c in self.revchunk(revlog, curr, prev, linknode):
579 for c in self.revchunk(revlog, curr, prev, linknode):
589 yield c
580 yield c
590
581
591 if units is not None:
582 if units is not None:
592 self._progress(msgbundling, None)
583 self._progress(msgbundling, None)
593 yield self.close()
584 yield self.close()
594
585
595 # filter any nodes that claim to be part of the known set
586 # filter any nodes that claim to be part of the known set
596 def prune(self, revlog, missing, commonrevs):
587 def prune(self, revlog, missing, commonrevs):
597 rr, rl = revlog.rev, revlog.linkrev
588 rr, rl = revlog.rev, revlog.linkrev
598 return [n for n in missing if rl(rr(n)) not in commonrevs]
589 return [n for n in missing if rl(rr(n)) not in commonrevs]
599
590
600 def _packmanifests(self, dir, mfnodes, lookuplinknode):
591 def _packmanifests(self, dir, mfnodes, lookuplinknode):
601 """Pack flat manifests into a changegroup stream."""
592 """Pack flat manifests into a changegroup stream."""
602 assert not dir
593 assert not dir
603 for chunk in self.group(mfnodes, self._repo.manifestlog._revlog,
594 for chunk in self.group(mfnodes, self._repo.manifestlog._revlog,
604 lookuplinknode, units=_('manifests')):
595 lookuplinknode, units=_('manifests')):
605 yield chunk
596 yield chunk
606
597
607 def _manifestsdone(self):
598 def _manifestsdone(self):
608 return ''
599 return ''
609
600
610 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
601 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
611 '''yield a sequence of changegroup chunks (strings)'''
602 '''yield a sequence of changegroup chunks (strings)'''
612 repo = self._repo
603 repo = self._repo
613 cl = repo.changelog
604 cl = repo.changelog
614
605
615 clrevorder = {}
606 clrevorder = {}
616 mfs = {} # needed manifests
607 mfs = {} # needed manifests
617 fnodes = {} # needed file nodes
608 fnodes = {} # needed file nodes
618 changedfiles = set()
609 changedfiles = set()
619
610
620 # Callback for the changelog, used to collect changed files and manifest
611 # Callback for the changelog, used to collect changed files and manifest
621 # nodes.
612 # nodes.
622 # Returns the linkrev node (identity in the changelog case).
613 # Returns the linkrev node (identity in the changelog case).
623 def lookupcl(x):
614 def lookupcl(x):
624 c = cl.read(x)
615 c = cl.read(x)
625 clrevorder[x] = len(clrevorder)
616 clrevorder[x] = len(clrevorder)
626 n = c[0]
617 n = c[0]
627 # record the first changeset introducing this manifest version
618 # record the first changeset introducing this manifest version
628 mfs.setdefault(n, x)
619 mfs.setdefault(n, x)
629 # Record a complete list of potentially-changed files in
620 # Record a complete list of potentially-changed files in
630 # this manifest.
621 # this manifest.
631 changedfiles.update(c[3])
622 changedfiles.update(c[3])
632 return x
623 return x
633
624
634 self._verbosenote(_('uncompressed size of bundle content:\n'))
625 self._verbosenote(_('uncompressed size of bundle content:\n'))
635 size = 0
626 size = 0
636 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
627 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
637 size += len(chunk)
628 size += len(chunk)
638 yield chunk
629 yield chunk
639 self._verbosenote(_('%8.i (changelog)\n') % size)
630 self._verbosenote(_('%8.i (changelog)\n') % size)
640
631
641 # We need to make sure that the linkrev in the changegroup refers to
632 # We need to make sure that the linkrev in the changegroup refers to
642 # the first changeset that introduced the manifest or file revision.
633 # the first changeset that introduced the manifest or file revision.
643 # The fastpath is usually safer than the slowpath, because the filelogs
634 # The fastpath is usually safer than the slowpath, because the filelogs
644 # are walked in revlog order.
635 # are walked in revlog order.
645 #
636 #
646 # When taking the slowpath with reorder=None and the manifest revlog
637 # When taking the slowpath with reorder=None and the manifest revlog
647 # uses generaldelta, the manifest may be walked in the "wrong" order.
638 # uses generaldelta, the manifest may be walked in the "wrong" order.
648 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
639 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
649 # cc0ff93d0c0c).
640 # cc0ff93d0c0c).
650 #
641 #
651 # When taking the fastpath, we are only vulnerable to reordering
642 # When taking the fastpath, we are only vulnerable to reordering
652 # of the changelog itself. The changelog never uses generaldelta, so
643 # of the changelog itself. The changelog never uses generaldelta, so
653 # it is only reordered when reorder=True. To handle this case, we
644 # it is only reordered when reorder=True. To handle this case, we
654 # simply take the slowpath, which already has the 'clrevorder' logic.
645 # simply take the slowpath, which already has the 'clrevorder' logic.
655 # This was also fixed in cc0ff93d0c0c.
646 # This was also fixed in cc0ff93d0c0c.
656 fastpathlinkrev = fastpathlinkrev and not self._reorder
647 fastpathlinkrev = fastpathlinkrev and not self._reorder
657 # Treemanifests don't work correctly with fastpathlinkrev
648 # Treemanifests don't work correctly with fastpathlinkrev
658 # either, because we don't discover which directory nodes to
649 # either, because we don't discover which directory nodes to
659 # send along with files. This could probably be fixed.
650 # send along with files. This could probably be fixed.
660 fastpathlinkrev = fastpathlinkrev and (
651 fastpathlinkrev = fastpathlinkrev and (
661 'treemanifest' not in repo.requirements)
652 'treemanifest' not in repo.requirements)
662
653
663 for chunk in self.generatemanifests(commonrevs, clrevorder,
654 for chunk in self.generatemanifests(commonrevs, clrevorder,
664 fastpathlinkrev, mfs, fnodes, source):
655 fastpathlinkrev, mfs, fnodes, source):
665 yield chunk
656 yield chunk
666 mfs.clear()
657 mfs.clear()
667 clrevs = set(cl.rev(x) for x in clnodes)
658 clrevs = set(cl.rev(x) for x in clnodes)
668
659
669 if not fastpathlinkrev:
660 if not fastpathlinkrev:
670 def linknodes(unused, fname):
661 def linknodes(unused, fname):
671 return fnodes.get(fname, {})
662 return fnodes.get(fname, {})
672 else:
663 else:
673 cln = cl.node
664 cln = cl.node
674 def linknodes(filerevlog, fname):
665 def linknodes(filerevlog, fname):
675 llr = filerevlog.linkrev
666 llr = filerevlog.linkrev
676 fln = filerevlog.node
667 fln = filerevlog.node
677 revs = ((r, llr(r)) for r in filerevlog)
668 revs = ((r, llr(r)) for r in filerevlog)
678 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
669 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
679
670
680 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
671 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
681 source):
672 source):
682 yield chunk
673 yield chunk
683
674
684 yield self.close()
675 yield self.close()
685
676
686 if clnodes:
677 if clnodes:
687 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
678 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
688
679
689 def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev, mfs,
680 def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev, mfs,
690 fnodes, source):
681 fnodes, source):
691 """Returns an iterator of changegroup chunks containing manifests.
682 """Returns an iterator of changegroup chunks containing manifests.
692
683
693 `source` is unused here, but is used by extensions like remotefilelog to
684 `source` is unused here, but is used by extensions like remotefilelog to
694 change what is sent based in pulls vs pushes, etc.
685 change what is sent based in pulls vs pushes, etc.
695 """
686 """
696 repo = self._repo
687 repo = self._repo
697 mfl = repo.manifestlog
688 mfl = repo.manifestlog
698 dirlog = mfl._revlog.dirlog
689 dirlog = mfl._revlog.dirlog
699 tmfnodes = {'': mfs}
690 tmfnodes = {'': mfs}
700
691
701 # Callback for the manifest, used to collect linkrevs for filelog
692 # Callback for the manifest, used to collect linkrevs for filelog
702 # revisions.
693 # revisions.
703 # Returns the linkrev node (collected in lookupcl).
694 # Returns the linkrev node (collected in lookupcl).
704 def makelookupmflinknode(dir):
695 def makelookupmflinknode(dir):
705 if fastpathlinkrev:
696 if fastpathlinkrev:
706 assert not dir
697 assert not dir
707 return mfs.__getitem__
698 return mfs.__getitem__
708
699
709 def lookupmflinknode(x):
700 def lookupmflinknode(x):
710 """Callback for looking up the linknode for manifests.
701 """Callback for looking up the linknode for manifests.
711
702
712 Returns the linkrev node for the specified manifest.
703 Returns the linkrev node for the specified manifest.
713
704
714 SIDE EFFECT:
705 SIDE EFFECT:
715
706
716 1) fclnodes gets populated with the list of relevant
707 1) fclnodes gets populated with the list of relevant
717 file nodes if we're not using fastpathlinkrev
708 file nodes if we're not using fastpathlinkrev
718 2) When treemanifests are in use, collects treemanifest nodes
709 2) When treemanifests are in use, collects treemanifest nodes
719 to send
710 to send
720
711
721 Note that this means manifests must be completely sent to
712 Note that this means manifests must be completely sent to
722 the client before you can trust the list of files and
713 the client before you can trust the list of files and
723 treemanifests to send.
714 treemanifests to send.
724 """
715 """
725 clnode = tmfnodes[dir][x]
716 clnode = tmfnodes[dir][x]
726 mdata = mfl.get(dir, x).readfast(shallow=True)
717 mdata = mfl.get(dir, x).readfast(shallow=True)
727 for p, n, fl in mdata.iterentries():
718 for p, n, fl in mdata.iterentries():
728 if fl == 't': # subdirectory manifest
719 if fl == 't': # subdirectory manifest
729 subdir = dir + p + '/'
720 subdir = dir + p + '/'
730 tmfclnodes = tmfnodes.setdefault(subdir, {})
721 tmfclnodes = tmfnodes.setdefault(subdir, {})
731 tmfclnode = tmfclnodes.setdefault(n, clnode)
722 tmfclnode = tmfclnodes.setdefault(n, clnode)
732 if clrevorder[clnode] < clrevorder[tmfclnode]:
723 if clrevorder[clnode] < clrevorder[tmfclnode]:
733 tmfclnodes[n] = clnode
724 tmfclnodes[n] = clnode
734 else:
725 else:
735 f = dir + p
726 f = dir + p
736 fclnodes = fnodes.setdefault(f, {})
727 fclnodes = fnodes.setdefault(f, {})
737 fclnode = fclnodes.setdefault(n, clnode)
728 fclnode = fclnodes.setdefault(n, clnode)
738 if clrevorder[clnode] < clrevorder[fclnode]:
729 if clrevorder[clnode] < clrevorder[fclnode]:
739 fclnodes[n] = clnode
730 fclnodes[n] = clnode
740 return clnode
731 return clnode
741 return lookupmflinknode
732 return lookupmflinknode
742
733
743 size = 0
734 size = 0
744 while tmfnodes:
735 while tmfnodes:
745 dir = min(tmfnodes)
736 dir = min(tmfnodes)
746 nodes = tmfnodes[dir]
737 nodes = tmfnodes[dir]
747 prunednodes = self.prune(dirlog(dir), nodes, commonrevs)
738 prunednodes = self.prune(dirlog(dir), nodes, commonrevs)
748 if not dir or prunednodes:
739 if not dir or prunednodes:
749 for x in self._packmanifests(dir, prunednodes,
740 for x in self._packmanifests(dir, prunednodes,
750 makelookupmflinknode(dir)):
741 makelookupmflinknode(dir)):
751 size += len(x)
742 size += len(x)
752 yield x
743 yield x
753 del tmfnodes[dir]
744 del tmfnodes[dir]
754 self._verbosenote(_('%8.i (manifests)\n') % size)
745 self._verbosenote(_('%8.i (manifests)\n') % size)
755 yield self._manifestsdone()
746 yield self._manifestsdone()
756
747
757 # The 'source' parameter is useful for extensions
748 # The 'source' parameter is useful for extensions
758 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
749 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
759 repo = self._repo
750 repo = self._repo
760 progress = self._progress
751 progress = self._progress
761 msgbundling = _('bundling')
752 msgbundling = _('bundling')
762
753
763 total = len(changedfiles)
754 total = len(changedfiles)
764 # for progress output
755 # for progress output
765 msgfiles = _('files')
756 msgfiles = _('files')
766 for i, fname in enumerate(sorted(changedfiles)):
757 for i, fname in enumerate(sorted(changedfiles)):
767 filerevlog = repo.file(fname)
758 filerevlog = repo.file(fname)
768 if not filerevlog:
759 if not filerevlog:
769 raise error.Abort(_("empty or missing revlog for %s") % fname)
760 raise error.Abort(_("empty or missing revlog for %s") % fname)
770
761
771 linkrevnodes = linknodes(filerevlog, fname)
762 linkrevnodes = linknodes(filerevlog, fname)
772 # Lookup for filenodes, we collected the linkrev nodes above in the
763 # Lookup for filenodes, we collected the linkrev nodes above in the
773 # fastpath case and with lookupmf in the slowpath case.
764 # fastpath case and with lookupmf in the slowpath case.
774 def lookupfilelog(x):
765 def lookupfilelog(x):
775 return linkrevnodes[x]
766 return linkrevnodes[x]
776
767
777 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
768 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
778 if filenodes:
769 if filenodes:
779 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
770 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
780 total=total)
771 total=total)
781 h = self.fileheader(fname)
772 h = self.fileheader(fname)
782 size = len(h)
773 size = len(h)
783 yield h
774 yield h
784 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
775 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
785 size += len(chunk)
776 size += len(chunk)
786 yield chunk
777 yield chunk
787 self._verbosenote(_('%8.i %s\n') % (size, fname))
778 self._verbosenote(_('%8.i %s\n') % (size, fname))
788 progress(msgbundling, None)
779 progress(msgbundling, None)
789
780
790 def deltaparent(self, revlog, rev, p1, p2, prev):
781 def deltaparent(self, revlog, rev, p1, p2, prev):
791 return prev
782 return prev
792
783
793 def revchunk(self, revlog, rev, prev, linknode):
784 def revchunk(self, revlog, rev, prev, linknode):
794 node = revlog.node(rev)
785 node = revlog.node(rev)
795 p1, p2 = revlog.parentrevs(rev)
786 p1, p2 = revlog.parentrevs(rev)
796 base = self.deltaparent(revlog, rev, p1, p2, prev)
787 base = self.deltaparent(revlog, rev, p1, p2, prev)
797
788
798 prefix = ''
789 prefix = ''
799 if revlog.iscensored(base) or revlog.iscensored(rev):
790 if revlog.iscensored(base) or revlog.iscensored(rev):
800 try:
791 try:
801 delta = revlog.revision(node, raw=True)
792 delta = revlog.revision(node, raw=True)
802 except error.CensoredNodeError as e:
793 except error.CensoredNodeError as e:
803 delta = e.tombstone
794 delta = e.tombstone
804 if base == nullrev:
795 if base == nullrev:
805 prefix = mdiff.trivialdiffheader(len(delta))
796 prefix = mdiff.trivialdiffheader(len(delta))
806 else:
797 else:
807 baselen = revlog.rawsize(base)
798 baselen = revlog.rawsize(base)
808 prefix = mdiff.replacediffheader(baselen, len(delta))
799 prefix = mdiff.replacediffheader(baselen, len(delta))
809 elif base == nullrev:
800 elif base == nullrev:
810 delta = revlog.revision(node, raw=True)
801 delta = revlog.revision(node, raw=True)
811 prefix = mdiff.trivialdiffheader(len(delta))
802 prefix = mdiff.trivialdiffheader(len(delta))
812 else:
803 else:
813 delta = revlog.revdiff(base, rev)
804 delta = revlog.revdiff(base, rev)
814 p1n, p2n = revlog.parents(node)
805 p1n, p2n = revlog.parents(node)
815 basenode = revlog.node(base)
806 basenode = revlog.node(base)
816 flags = revlog.flags(rev)
807 flags = revlog.flags(rev)
817 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode, flags)
808 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode, flags)
818 meta += prefix
809 meta += prefix
819 l = len(meta) + len(delta)
810 l = len(meta) + len(delta)
820 yield chunkheader(l)
811 yield chunkheader(l)
821 yield meta
812 yield meta
822 yield delta
813 yield delta
823 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
814 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
824 # do nothing with basenode, it is implicitly the previous one in HG10
815 # do nothing with basenode, it is implicitly the previous one in HG10
825 # do nothing with flags, it is implicitly 0 for cg1 and cg2
816 # do nothing with flags, it is implicitly 0 for cg1 and cg2
826 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
817 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
827
818
828 class cg2packer(cg1packer):
819 class cg2packer(cg1packer):
829 version = '02'
820 version = '02'
830 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
821 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
831
822
832 def __init__(self, repo, bundlecaps=None):
823 def __init__(self, repo, bundlecaps=None):
833 super(cg2packer, self).__init__(repo, bundlecaps)
824 super(cg2packer, self).__init__(repo, bundlecaps)
834 if self._reorder is None:
825 if self._reorder is None:
835 # Since generaldelta is directly supported by cg2, reordering
826 # Since generaldelta is directly supported by cg2, reordering
836 # generally doesn't help, so we disable it by default (treating
827 # generally doesn't help, so we disable it by default (treating
837 # bundle.reorder=auto just like bundle.reorder=False).
828 # bundle.reorder=auto just like bundle.reorder=False).
838 self._reorder = False
829 self._reorder = False
839
830
840 def deltaparent(self, revlog, rev, p1, p2, prev):
831 def deltaparent(self, revlog, rev, p1, p2, prev):
841 dp = revlog.deltaparent(rev)
832 dp = revlog.deltaparent(rev)
842 if dp == nullrev and revlog.storedeltachains:
833 if dp == nullrev and revlog.storedeltachains:
843 # Avoid sending full revisions when delta parent is null. Pick prev
834 # Avoid sending full revisions when delta parent is null. Pick prev
844 # in that case. It's tempting to pick p1 in this case, as p1 will
835 # in that case. It's tempting to pick p1 in this case, as p1 will
845 # be smaller in the common case. However, computing a delta against
836 # be smaller in the common case. However, computing a delta against
846 # p1 may require resolving the raw text of p1, which could be
837 # p1 may require resolving the raw text of p1, which could be
847 # expensive. The revlog caches should have prev cached, meaning
838 # expensive. The revlog caches should have prev cached, meaning
848 # less CPU for changegroup generation. There is likely room to add
839 # less CPU for changegroup generation. There is likely room to add
849 # a flag and/or config option to control this behavior.
840 # a flag and/or config option to control this behavior.
850 return prev
841 return prev
851 elif dp == nullrev:
842 elif dp == nullrev:
852 # revlog is configured to use full snapshot for a reason,
843 # revlog is configured to use full snapshot for a reason,
853 # stick to full snapshot.
844 # stick to full snapshot.
854 return nullrev
845 return nullrev
855 elif dp not in (p1, p2, prev):
846 elif dp not in (p1, p2, prev):
856 # Pick prev when we can't be sure remote has the base revision.
847 # Pick prev when we can't be sure remote has the base revision.
857 return prev
848 return prev
858 else:
849 else:
859 return dp
850 return dp
860
851
861 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
852 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
862 # Do nothing with flags, it is implicitly 0 in cg1 and cg2
853 # Do nothing with flags, it is implicitly 0 in cg1 and cg2
863 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
854 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
864
855
865 class cg3packer(cg2packer):
856 class cg3packer(cg2packer):
866 version = '03'
857 version = '03'
867 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
858 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
868
859
869 def _packmanifests(self, dir, mfnodes, lookuplinknode):
860 def _packmanifests(self, dir, mfnodes, lookuplinknode):
870 if dir:
861 if dir:
871 yield self.fileheader(dir)
862 yield self.fileheader(dir)
872
863
873 dirlog = self._repo.manifestlog._revlog.dirlog(dir)
864 dirlog = self._repo.manifestlog._revlog.dirlog(dir)
874 for chunk in self.group(mfnodes, dirlog, lookuplinknode,
865 for chunk in self.group(mfnodes, dirlog, lookuplinknode,
875 units=_('manifests')):
866 units=_('manifests')):
876 yield chunk
867 yield chunk
877
868
878 def _manifestsdone(self):
869 def _manifestsdone(self):
879 return self.close()
870 return self.close()
880
871
881 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
872 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
882 return struct.pack(
873 return struct.pack(
883 self.deltaheader, node, p1n, p2n, basenode, linknode, flags)
874 self.deltaheader, node, p1n, p2n, basenode, linknode, flags)
884
875
885 _packermap = {'01': (cg1packer, cg1unpacker),
876 _packermap = {'01': (cg1packer, cg1unpacker),
886 # cg2 adds support for exchanging generaldelta
877 # cg2 adds support for exchanging generaldelta
887 '02': (cg2packer, cg2unpacker),
878 '02': (cg2packer, cg2unpacker),
888 # cg3 adds support for exchanging revlog flags and treemanifests
879 # cg3 adds support for exchanging revlog flags and treemanifests
889 '03': (cg3packer, cg3unpacker),
880 '03': (cg3packer, cg3unpacker),
890 }
881 }
891
882
892 def allsupportedversions(repo):
883 def allsupportedversions(repo):
893 versions = set(_packermap.keys())
884 versions = set(_packermap.keys())
894 if not (repo.ui.configbool('experimental', 'changegroup3') or
885 if not (repo.ui.configbool('experimental', 'changegroup3') or
895 repo.ui.configbool('experimental', 'treemanifest') or
886 repo.ui.configbool('experimental', 'treemanifest') or
896 'treemanifest' in repo.requirements):
887 'treemanifest' in repo.requirements):
897 versions.discard('03')
888 versions.discard('03')
898 return versions
889 return versions
899
890
900 # Changegroup versions that can be applied to the repo
891 # Changegroup versions that can be applied to the repo
901 def supportedincomingversions(repo):
892 def supportedincomingversions(repo):
902 return allsupportedversions(repo)
893 return allsupportedversions(repo)
903
894
904 # Changegroup versions that can be created from the repo
895 # Changegroup versions that can be created from the repo
905 def supportedoutgoingversions(repo):
896 def supportedoutgoingversions(repo):
906 versions = allsupportedversions(repo)
897 versions = allsupportedversions(repo)
907 if 'treemanifest' in repo.requirements:
898 if 'treemanifest' in repo.requirements:
908 # Versions 01 and 02 support only flat manifests and it's just too
899 # Versions 01 and 02 support only flat manifests and it's just too
909 # expensive to convert between the flat manifest and tree manifest on
900 # expensive to convert between the flat manifest and tree manifest on
910 # the fly. Since tree manifests are hashed differently, all of history
901 # the fly. Since tree manifests are hashed differently, all of history
911 # would have to be converted. Instead, we simply don't even pretend to
902 # would have to be converted. Instead, we simply don't even pretend to
912 # support versions 01 and 02.
903 # support versions 01 and 02.
913 versions.discard('01')
904 versions.discard('01')
914 versions.discard('02')
905 versions.discard('02')
915 return versions
906 return versions
916
907
917 def localversion(repo):
908 def localversion(repo):
918 # Finds the best version to use for bundles that are meant to be used
909 # Finds the best version to use for bundles that are meant to be used
919 # locally, such as those from strip and shelve, and temporary bundles.
910 # locally, such as those from strip and shelve, and temporary bundles.
920 return max(supportedoutgoingversions(repo))
911 return max(supportedoutgoingversions(repo))
921
912
922 def safeversion(repo):
913 def safeversion(repo):
923 # Finds the smallest version that it's safe to assume clients of the repo
914 # Finds the smallest version that it's safe to assume clients of the repo
924 # will support. For example, all hg versions that support generaldelta also
915 # will support. For example, all hg versions that support generaldelta also
925 # support changegroup 02.
916 # support changegroup 02.
926 versions = supportedoutgoingversions(repo)
917 versions = supportedoutgoingversions(repo)
927 if 'generaldelta' in repo.requirements:
918 if 'generaldelta' in repo.requirements:
928 versions.discard('01')
919 versions.discard('01')
929 assert versions
920 assert versions
930 return min(versions)
921 return min(versions)
931
922
932 def getbundler(version, repo, bundlecaps=None):
923 def getbundler(version, repo, bundlecaps=None):
933 assert version in supportedoutgoingversions(repo)
924 assert version in supportedoutgoingversions(repo)
934 return _packermap[version][0](repo, bundlecaps)
925 return _packermap[version][0](repo, bundlecaps)
935
926
936 def getunbundler(version, fh, alg, extras=None):
927 def getunbundler(version, fh, alg, extras=None):
937 return _packermap[version][1](fh, alg, extras=extras)
928 return _packermap[version][1](fh, alg, extras=extras)
938
929
939 def _changegroupinfo(repo, nodes, source):
930 def _changegroupinfo(repo, nodes, source):
940 if repo.ui.verbose or source == 'bundle':
931 if repo.ui.verbose or source == 'bundle':
941 repo.ui.status(_("%d changesets found\n") % len(nodes))
932 repo.ui.status(_("%d changesets found\n") % len(nodes))
942 if repo.ui.debugflag:
933 if repo.ui.debugflag:
943 repo.ui.debug("list of changesets:\n")
934 repo.ui.debug("list of changesets:\n")
944 for node in nodes:
935 for node in nodes:
945 repo.ui.debug("%s\n" % hex(node))
936 repo.ui.debug("%s\n" % hex(node))
946
937
947 def makechangegroup(repo, outgoing, version, source, fastpath=False,
938 def makechangegroup(repo, outgoing, version, source, fastpath=False,
948 bundlecaps=None):
939 bundlecaps=None):
949 cgstream = makestream(repo, outgoing, version, source,
940 cgstream = makestream(repo, outgoing, version, source,
950 fastpath=fastpath, bundlecaps=bundlecaps)
941 fastpath=fastpath, bundlecaps=bundlecaps)
951 return getunbundler(version, util.chunkbuffer(cgstream), None,
942 return getunbundler(version, util.chunkbuffer(cgstream), None,
952 {'clcount': len(outgoing.missing) })
943 {'clcount': len(outgoing.missing) })
953
944
954 def makestream(repo, outgoing, version, source, fastpath=False,
945 def makestream(repo, outgoing, version, source, fastpath=False,
955 bundlecaps=None):
946 bundlecaps=None):
956 bundler = getbundler(version, repo, bundlecaps=bundlecaps)
947 bundler = getbundler(version, repo, bundlecaps=bundlecaps)
957
948
958 repo = repo.unfiltered()
949 repo = repo.unfiltered()
959 commonrevs = outgoing.common
950 commonrevs = outgoing.common
960 csets = outgoing.missing
951 csets = outgoing.missing
961 heads = outgoing.missingheads
952 heads = outgoing.missingheads
962 # We go through the fast path if we get told to, or if all (unfiltered
953 # We go through the fast path if we get told to, or if all (unfiltered
963 # heads have been requested (since we then know there all linkrevs will
954 # heads have been requested (since we then know there all linkrevs will
964 # be pulled by the client).
955 # be pulled by the client).
965 heads.sort()
956 heads.sort()
966 fastpathlinkrev = fastpath or (
957 fastpathlinkrev = fastpath or (
967 repo.filtername is None and heads == sorted(repo.heads()))
958 repo.filtername is None and heads == sorted(repo.heads()))
968
959
969 repo.hook('preoutgoing', throw=True, source=source)
960 repo.hook('preoutgoing', throw=True, source=source)
970 _changegroupinfo(repo, csets, source)
961 _changegroupinfo(repo, csets, source)
971 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
962 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
972
963
973 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
964 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
974 revisions = 0
965 revisions = 0
975 files = 0
966 files = 0
976 for chunkdata in iter(source.filelogheader, {}):
967 for chunkdata in iter(source.filelogheader, {}):
977 files += 1
968 files += 1
978 f = chunkdata["filename"]
969 f = chunkdata["filename"]
979 repo.ui.debug("adding %s revisions\n" % f)
970 repo.ui.debug("adding %s revisions\n" % f)
980 repo.ui.progress(_('files'), files, unit=_('files'),
971 repo.ui.progress(_('files'), files, unit=_('files'),
981 total=expectedfiles)
972 total=expectedfiles)
982 fl = repo.file(f)
973 fl = repo.file(f)
983 o = len(fl)
974 o = len(fl)
984 try:
975 try:
985 deltas = source.deltaiter()
976 deltas = source.deltaiter()
986 if not fl.addgroup(deltas, revmap, trp):
977 if not fl.addgroup(deltas, revmap, trp):
987 raise error.Abort(_("received file revlog group is empty"))
978 raise error.Abort(_("received file revlog group is empty"))
988 except error.CensoredBaseError as e:
979 except error.CensoredBaseError as e:
989 raise error.Abort(_("received delta base is censored: %s") % e)
980 raise error.Abort(_("received delta base is censored: %s") % e)
990 revisions += len(fl) - o
981 revisions += len(fl) - o
991 if f in needfiles:
982 if f in needfiles:
992 needs = needfiles[f]
983 needs = needfiles[f]
993 for new in xrange(o, len(fl)):
984 for new in xrange(o, len(fl)):
994 n = fl.node(new)
985 n = fl.node(new)
995 if n in needs:
986 if n in needs:
996 needs.remove(n)
987 needs.remove(n)
997 else:
988 else:
998 raise error.Abort(
989 raise error.Abort(
999 _("received spurious file revlog entry"))
990 _("received spurious file revlog entry"))
1000 if not needs:
991 if not needs:
1001 del needfiles[f]
992 del needfiles[f]
1002 repo.ui.progress(_('files'), None)
993 repo.ui.progress(_('files'), None)
1003
994
1004 for f, needs in needfiles.iteritems():
995 for f, needs in needfiles.iteritems():
1005 fl = repo.file(f)
996 fl = repo.file(f)
1006 for n in needs:
997 for n in needs:
1007 try:
998 try:
1008 fl.rev(n)
999 fl.rev(n)
1009 except error.LookupError:
1000 except error.LookupError:
1010 raise error.Abort(
1001 raise error.Abort(
1011 _('missing file data for %s:%s - run hg verify') %
1002 _('missing file data for %s:%s - run hg verify') %
1012 (f, hex(n)))
1003 (f, hex(n)))
1013
1004
1014 return revisions, files
1005 return revisions, files
General Comments 0
You need to be logged in to leave comments. Login now