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