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