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