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