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