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