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