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