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