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