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