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