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