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