##// END OF EJS Templates
changegroup: use any node, not min(), in treemanifest's generatemanifests...
Kyle Lippincott -
r35013:d80380ba default
parent child Browse files
Show More
@@ -1,1005 +1,1003 b''
1 # changegroup.py - Mercurial changegroup manipulation functions
1 # changegroup.py - Mercurial changegroup manipulation functions
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import os
10 import os
11 import struct
11 import struct
12 import tempfile
12 import tempfile
13 import weakref
13 import weakref
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 hex,
17 hex,
18 nullrev,
18 nullrev,
19 short,
19 short,
20 )
20 )
21
21
22 from . import (
22 from . import (
23 dagutil,
23 dagutil,
24 error,
24 error,
25 mdiff,
25 mdiff,
26 phases,
26 phases,
27 pycompat,
27 pycompat,
28 util,
28 util,
29 )
29 )
30
30
31 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
31 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
32 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
32 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
33 _CHANGEGROUPV3_DELTA_HEADER = ">20s20s20s20s20sH"
33 _CHANGEGROUPV3_DELTA_HEADER = ">20s20s20s20s20sH"
34
34
35 def readexactly(stream, n):
35 def readexactly(stream, n):
36 '''read n bytes from stream.read and abort if less was available'''
36 '''read n bytes from stream.read and abort if less was available'''
37 s = stream.read(n)
37 s = stream.read(n)
38 if len(s) < n:
38 if len(s) < n:
39 raise error.Abort(_("stream ended unexpectedly"
39 raise error.Abort(_("stream ended unexpectedly"
40 " (got %d bytes, expected %d)")
40 " (got %d bytes, expected %d)")
41 % (len(s), n))
41 % (len(s), n))
42 return s
42 return s
43
43
44 def getchunk(stream):
44 def getchunk(stream):
45 """return the next chunk from stream as a string"""
45 """return the next chunk from stream as a string"""
46 d = readexactly(stream, 4)
46 d = readexactly(stream, 4)
47 l = struct.unpack(">l", d)[0]
47 l = struct.unpack(">l", d)[0]
48 if l <= 4:
48 if l <= 4:
49 if l:
49 if l:
50 raise error.Abort(_("invalid chunk length %d") % l)
50 raise error.Abort(_("invalid chunk length %d") % l)
51 return ""
51 return ""
52 return readexactly(stream, l - 4)
52 return readexactly(stream, l - 4)
53
53
54 def chunkheader(length):
54 def chunkheader(length):
55 """return a changegroup chunk header (string)"""
55 """return a changegroup chunk header (string)"""
56 return struct.pack(">l", length + 4)
56 return struct.pack(">l", length + 4)
57
57
58 def closechunk():
58 def closechunk():
59 """return a changegroup chunk header (string) for a zero-length chunk"""
59 """return a changegroup chunk header (string) for a zero-length chunk"""
60 return struct.pack(">l", 0)
60 return struct.pack(">l", 0)
61
61
62 def writechunks(ui, chunks, filename, vfs=None):
62 def writechunks(ui, chunks, filename, vfs=None):
63 """Write chunks to a file and return its filename.
63 """Write chunks to a file and return its filename.
64
64
65 The stream is assumed to be a bundle file.
65 The stream is assumed to be a bundle file.
66 Existing files will not be overwritten.
66 Existing files will not be overwritten.
67 If no filename is specified, a temporary file is created.
67 If no filename is specified, a temporary file is created.
68 """
68 """
69 fh = None
69 fh = None
70 cleanup = None
70 cleanup = None
71 try:
71 try:
72 if filename:
72 if filename:
73 if vfs:
73 if vfs:
74 fh = vfs.open(filename, "wb")
74 fh = vfs.open(filename, "wb")
75 else:
75 else:
76 # Increase default buffer size because default is usually
76 # Increase default buffer size because default is usually
77 # small (4k is common on Linux).
77 # small (4k is common on Linux).
78 fh = open(filename, "wb", 131072)
78 fh = open(filename, "wb", 131072)
79 else:
79 else:
80 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
80 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
81 fh = os.fdopen(fd, pycompat.sysstr("wb"))
81 fh = os.fdopen(fd, pycompat.sysstr("wb"))
82 cleanup = filename
82 cleanup = filename
83 for c in chunks:
83 for c in chunks:
84 fh.write(c)
84 fh.write(c)
85 cleanup = None
85 cleanup = None
86 return filename
86 return filename
87 finally:
87 finally:
88 if fh is not None:
88 if fh is not None:
89 fh.close()
89 fh.close()
90 if cleanup is not None:
90 if cleanup is not None:
91 if filename and vfs:
91 if filename and vfs:
92 vfs.unlink(cleanup)
92 vfs.unlink(cleanup)
93 else:
93 else:
94 os.unlink(cleanup)
94 os.unlink(cleanup)
95
95
96 class cg1unpacker(object):
96 class cg1unpacker(object):
97 """Unpacker for cg1 changegroup streams.
97 """Unpacker for cg1 changegroup streams.
98
98
99 A changegroup unpacker handles the framing of the revision data in
99 A changegroup unpacker handles the framing of the revision data in
100 the wire format. Most consumers will want to use the apply()
100 the wire format. Most consumers will want to use the apply()
101 method to add the changes from the changegroup to a repository.
101 method to add the changes from the changegroup to a repository.
102
102
103 If you're forwarding a changegroup unmodified to another consumer,
103 If you're forwarding a changegroup unmodified to another consumer,
104 use getchunks(), which returns an iterator of changegroup
104 use getchunks(), which returns an iterator of changegroup
105 chunks. This is mostly useful for cases where you need to know the
105 chunks. This is mostly useful for cases where you need to know the
106 data stream has ended by observing the end of the changegroup.
106 data stream has ended by observing the end of the changegroup.
107
107
108 deltachunk() is useful only if you're applying delta data. Most
108 deltachunk() is useful only if you're applying delta data. Most
109 consumers should prefer apply() instead.
109 consumers should prefer apply() instead.
110
110
111 A few other public methods exist. Those are used only for
111 A few other public methods exist. Those are used only for
112 bundlerepo and some debug commands - their use is discouraged.
112 bundlerepo and some debug commands - their use is discouraged.
113 """
113 """
114 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
114 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
115 deltaheadersize = struct.calcsize(deltaheader)
115 deltaheadersize = struct.calcsize(deltaheader)
116 version = '01'
116 version = '01'
117 _grouplistcount = 1 # One list of files after the manifests
117 _grouplistcount = 1 # One list of files after the manifests
118
118
119 def __init__(self, fh, alg, extras=None):
119 def __init__(self, fh, alg, extras=None):
120 if alg is None:
120 if alg is None:
121 alg = 'UN'
121 alg = 'UN'
122 if alg not in util.compengines.supportedbundletypes:
122 if alg not in util.compengines.supportedbundletypes:
123 raise error.Abort(_('unknown stream compression type: %s')
123 raise error.Abort(_('unknown stream compression type: %s')
124 % alg)
124 % alg)
125 if alg == 'BZ':
125 if alg == 'BZ':
126 alg = '_truncatedBZ'
126 alg = '_truncatedBZ'
127
127
128 compengine = util.compengines.forbundletype(alg)
128 compengine = util.compengines.forbundletype(alg)
129 self._stream = compengine.decompressorreader(fh)
129 self._stream = compengine.decompressorreader(fh)
130 self._type = alg
130 self._type = alg
131 self.extras = extras or {}
131 self.extras = extras or {}
132 self.callback = None
132 self.callback = None
133
133
134 # These methods (compressed, read, seek, tell) all appear to only
134 # These methods (compressed, read, seek, tell) all appear to only
135 # be used by bundlerepo, but it's a little hard to tell.
135 # be used by bundlerepo, but it's a little hard to tell.
136 def compressed(self):
136 def compressed(self):
137 return self._type is not None and self._type != 'UN'
137 return self._type is not None and self._type != 'UN'
138 def read(self, l):
138 def read(self, l):
139 return self._stream.read(l)
139 return self._stream.read(l)
140 def seek(self, pos):
140 def seek(self, pos):
141 return self._stream.seek(pos)
141 return self._stream.seek(pos)
142 def tell(self):
142 def tell(self):
143 return self._stream.tell()
143 return self._stream.tell()
144 def close(self):
144 def close(self):
145 return self._stream.close()
145 return self._stream.close()
146
146
147 def _chunklength(self):
147 def _chunklength(self):
148 d = readexactly(self._stream, 4)
148 d = readexactly(self._stream, 4)
149 l = struct.unpack(">l", d)[0]
149 l = struct.unpack(">l", d)[0]
150 if l <= 4:
150 if l <= 4:
151 if l:
151 if l:
152 raise error.Abort(_("invalid chunk length %d") % l)
152 raise error.Abort(_("invalid chunk length %d") % l)
153 return 0
153 return 0
154 if self.callback:
154 if self.callback:
155 self.callback()
155 self.callback()
156 return l - 4
156 return l - 4
157
157
158 def changelogheader(self):
158 def changelogheader(self):
159 """v10 does not have a changelog header chunk"""
159 """v10 does not have a changelog header chunk"""
160 return {}
160 return {}
161
161
162 def manifestheader(self):
162 def manifestheader(self):
163 """v10 does not have a manifest header chunk"""
163 """v10 does not have a manifest header chunk"""
164 return {}
164 return {}
165
165
166 def filelogheader(self):
166 def filelogheader(self):
167 """return the header of the filelogs chunk, v10 only has the filename"""
167 """return the header of the filelogs chunk, v10 only has the filename"""
168 l = self._chunklength()
168 l = self._chunklength()
169 if not l:
169 if not l:
170 return {}
170 return {}
171 fname = readexactly(self._stream, l)
171 fname = readexactly(self._stream, l)
172 return {'filename': fname}
172 return {'filename': fname}
173
173
174 def _deltaheader(self, headertuple, prevnode):
174 def _deltaheader(self, headertuple, prevnode):
175 node, p1, p2, cs = headertuple
175 node, p1, p2, cs = headertuple
176 if prevnode is None:
176 if prevnode is None:
177 deltabase = p1
177 deltabase = p1
178 else:
178 else:
179 deltabase = prevnode
179 deltabase = prevnode
180 flags = 0
180 flags = 0
181 return node, p1, p2, deltabase, cs, flags
181 return node, p1, p2, deltabase, cs, flags
182
182
183 def deltachunk(self, prevnode):
183 def deltachunk(self, prevnode):
184 l = self._chunklength()
184 l = self._chunklength()
185 if not l:
185 if not l:
186 return {}
186 return {}
187 headerdata = readexactly(self._stream, self.deltaheadersize)
187 headerdata = readexactly(self._stream, self.deltaheadersize)
188 header = struct.unpack(self.deltaheader, headerdata)
188 header = struct.unpack(self.deltaheader, headerdata)
189 delta = readexactly(self._stream, l - self.deltaheadersize)
189 delta = readexactly(self._stream, l - self.deltaheadersize)
190 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
190 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
191 return (node, p1, p2, cs, deltabase, delta, flags)
191 return (node, p1, p2, cs, deltabase, delta, flags)
192
192
193 def getchunks(self):
193 def getchunks(self):
194 """returns all the chunks contains in the bundle
194 """returns all the chunks contains in the bundle
195
195
196 Used when you need to forward the binary stream to a file or another
196 Used when you need to forward the binary stream to a file or another
197 network API. To do so, it parse the changegroup data, otherwise it will
197 network API. To do so, it parse the changegroup data, otherwise it will
198 block in case of sshrepo because it don't know the end of the stream.
198 block in case of sshrepo because it don't know the end of the stream.
199 """
199 """
200 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
200 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
201 # and a list of filelogs. For changegroup 3, we expect 4 parts:
201 # and a list of filelogs. For changegroup 3, we expect 4 parts:
202 # changelog, manifestlog, a list of tree manifestlogs, and a list of
202 # changelog, manifestlog, a list of tree manifestlogs, and a list of
203 # filelogs.
203 # filelogs.
204 #
204 #
205 # Changelog and manifestlog parts are terminated with empty chunks. The
205 # Changelog and manifestlog parts are terminated with empty chunks. The
206 # tree and file parts are a list of entry sections. Each entry section
206 # tree and file parts are a list of entry sections. Each entry section
207 # is a series of chunks terminating in an empty chunk. The list of these
207 # is a series of chunks terminating in an empty chunk. The list of these
208 # entry sections is terminated in yet another empty chunk, so we know
208 # entry sections is terminated in yet another empty chunk, so we know
209 # we've reached the end of the tree/file list when we reach an empty
209 # we've reached the end of the tree/file list when we reach an empty
210 # chunk that was proceeded by no non-empty chunks.
210 # chunk that was proceeded by no non-empty chunks.
211
211
212 parts = 0
212 parts = 0
213 while parts < 2 + self._grouplistcount:
213 while parts < 2 + self._grouplistcount:
214 noentries = True
214 noentries = True
215 while True:
215 while True:
216 chunk = getchunk(self)
216 chunk = getchunk(self)
217 if not chunk:
217 if not chunk:
218 # The first two empty chunks represent the end of the
218 # The first two empty chunks represent the end of the
219 # changelog and the manifestlog portions. The remaining
219 # changelog and the manifestlog portions. The remaining
220 # empty chunks represent either A) the end of individual
220 # empty chunks represent either A) the end of individual
221 # tree or file entries in the file list, or B) the end of
221 # tree or file entries in the file list, or B) the end of
222 # the entire list. It's the end of the entire list if there
222 # the entire list. It's the end of the entire list if there
223 # were no entries (i.e. noentries is True).
223 # were no entries (i.e. noentries is True).
224 if parts < 2:
224 if parts < 2:
225 parts += 1
225 parts += 1
226 elif noentries:
226 elif noentries:
227 parts += 1
227 parts += 1
228 break
228 break
229 noentries = False
229 noentries = False
230 yield chunkheader(len(chunk))
230 yield chunkheader(len(chunk))
231 pos = 0
231 pos = 0
232 while pos < len(chunk):
232 while pos < len(chunk):
233 next = pos + 2**20
233 next = pos + 2**20
234 yield chunk[pos:next]
234 yield chunk[pos:next]
235 pos = next
235 pos = next
236 yield closechunk()
236 yield closechunk()
237
237
238 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
238 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
239 # We know that we'll never have more manifests than we had
239 # We know that we'll never have more manifests than we had
240 # changesets.
240 # changesets.
241 self.callback = prog(_('manifests'), numchanges)
241 self.callback = prog(_('manifests'), numchanges)
242 # no need to check for empty manifest group here:
242 # no need to check for empty manifest group here:
243 # if the result of the merge of 1 and 2 is the same in 3 and 4,
243 # if the result of the merge of 1 and 2 is the same in 3 and 4,
244 # no new manifest will be created and the manifest group will
244 # no new manifest will be created and the manifest group will
245 # be empty during the pull
245 # be empty during the pull
246 self.manifestheader()
246 self.manifestheader()
247 deltas = self.deltaiter()
247 deltas = self.deltaiter()
248 repo.manifestlog._revlog.addgroup(deltas, revmap, trp)
248 repo.manifestlog._revlog.addgroup(deltas, revmap, trp)
249 repo.ui.progress(_('manifests'), None)
249 repo.ui.progress(_('manifests'), None)
250 self.callback = None
250 self.callback = None
251
251
252 def apply(self, repo, tr, srctype, url, targetphase=phases.draft,
252 def apply(self, repo, tr, srctype, url, targetphase=phases.draft,
253 expectedtotal=None):
253 expectedtotal=None):
254 """Add the changegroup returned by source.read() to this repo.
254 """Add the changegroup returned by source.read() to this repo.
255 srctype is a string like 'push', 'pull', or 'unbundle'. url is
255 srctype is a string like 'push', 'pull', or 'unbundle'. url is
256 the URL of the repo where this changegroup is coming from.
256 the URL of the repo where this changegroup is coming from.
257
257
258 Return an integer summarizing the change to this repo:
258 Return an integer summarizing the change to this repo:
259 - nothing changed or no source: 0
259 - nothing changed or no source: 0
260 - more heads than before: 1+added heads (2..n)
260 - more heads than before: 1+added heads (2..n)
261 - fewer heads than before: -1-removed heads (-2..-n)
261 - fewer heads than before: -1-removed heads (-2..-n)
262 - number of heads stays the same: 1
262 - number of heads stays the same: 1
263 """
263 """
264 repo = repo.unfiltered()
264 repo = repo.unfiltered()
265 def csmap(x):
265 def csmap(x):
266 repo.ui.debug("add changeset %s\n" % short(x))
266 repo.ui.debug("add changeset %s\n" % short(x))
267 return len(cl)
267 return len(cl)
268
268
269 def revmap(x):
269 def revmap(x):
270 return cl.rev(x)
270 return cl.rev(x)
271
271
272 changesets = files = revisions = 0
272 changesets = files = revisions = 0
273
273
274 try:
274 try:
275 # The transaction may already carry source information. In this
275 # The transaction may already carry source information. In this
276 # case we use the top level data. We overwrite the argument
276 # case we use the top level data. We overwrite the argument
277 # because we need to use the top level value (if they exist)
277 # because we need to use the top level value (if they exist)
278 # in this function.
278 # in this function.
279 srctype = tr.hookargs.setdefault('source', srctype)
279 srctype = tr.hookargs.setdefault('source', srctype)
280 url = tr.hookargs.setdefault('url', url)
280 url = tr.hookargs.setdefault('url', url)
281 repo.hook('prechangegroup',
281 repo.hook('prechangegroup',
282 throw=True, **pycompat.strkwargs(tr.hookargs))
282 throw=True, **pycompat.strkwargs(tr.hookargs))
283
283
284 # write changelog data to temp files so concurrent readers
284 # write changelog data to temp files so concurrent readers
285 # will not see an inconsistent view
285 # will not see an inconsistent view
286 cl = repo.changelog
286 cl = repo.changelog
287 cl.delayupdate(tr)
287 cl.delayupdate(tr)
288 oldheads = set(cl.heads())
288 oldheads = set(cl.heads())
289
289
290 trp = weakref.proxy(tr)
290 trp = weakref.proxy(tr)
291 # pull off the changeset group
291 # pull off the changeset group
292 repo.ui.status(_("adding changesets\n"))
292 repo.ui.status(_("adding changesets\n"))
293 clstart = len(cl)
293 clstart = len(cl)
294 class prog(object):
294 class prog(object):
295 def __init__(self, step, total):
295 def __init__(self, step, total):
296 self._step = step
296 self._step = step
297 self._total = total
297 self._total = total
298 self._count = 1
298 self._count = 1
299 def __call__(self):
299 def __call__(self):
300 repo.ui.progress(self._step, self._count, unit=_('chunks'),
300 repo.ui.progress(self._step, self._count, unit=_('chunks'),
301 total=self._total)
301 total=self._total)
302 self._count += 1
302 self._count += 1
303 self.callback = prog(_('changesets'), expectedtotal)
303 self.callback = prog(_('changesets'), expectedtotal)
304
304
305 efiles = set()
305 efiles = set()
306 def onchangelog(cl, node):
306 def onchangelog(cl, node):
307 efiles.update(cl.readfiles(node))
307 efiles.update(cl.readfiles(node))
308
308
309 self.changelogheader()
309 self.changelogheader()
310 deltas = self.deltaiter()
310 deltas = self.deltaiter()
311 cgnodes = cl.addgroup(deltas, csmap, trp, addrevisioncb=onchangelog)
311 cgnodes = cl.addgroup(deltas, csmap, trp, addrevisioncb=onchangelog)
312 efiles = len(efiles)
312 efiles = len(efiles)
313
313
314 if not cgnodes:
314 if not cgnodes:
315 repo.ui.develwarn('applied empty changegroup',
315 repo.ui.develwarn('applied empty changegroup',
316 config='warn-empty-changegroup')
316 config='warn-empty-changegroup')
317 clend = len(cl)
317 clend = len(cl)
318 changesets = clend - clstart
318 changesets = clend - clstart
319 repo.ui.progress(_('changesets'), None)
319 repo.ui.progress(_('changesets'), None)
320 self.callback = None
320 self.callback = None
321
321
322 # pull off the manifest group
322 # pull off the manifest group
323 repo.ui.status(_("adding manifests\n"))
323 repo.ui.status(_("adding manifests\n"))
324 self._unpackmanifests(repo, revmap, trp, prog, changesets)
324 self._unpackmanifests(repo, revmap, trp, prog, changesets)
325
325
326 needfiles = {}
326 needfiles = {}
327 if repo.ui.configbool('server', 'validate'):
327 if repo.ui.configbool('server', 'validate'):
328 cl = repo.changelog
328 cl = repo.changelog
329 ml = repo.manifestlog
329 ml = repo.manifestlog
330 # validate incoming csets have their manifests
330 # validate incoming csets have their manifests
331 for cset in xrange(clstart, clend):
331 for cset in xrange(clstart, clend):
332 mfnode = cl.changelogrevision(cset).manifest
332 mfnode = cl.changelogrevision(cset).manifest
333 mfest = ml[mfnode].readdelta()
333 mfest = ml[mfnode].readdelta()
334 # store file cgnodes we must see
334 # store file cgnodes we must see
335 for f, n in mfest.iteritems():
335 for f, n in mfest.iteritems():
336 needfiles.setdefault(f, set()).add(n)
336 needfiles.setdefault(f, set()).add(n)
337
337
338 # process the files
338 # process the files
339 repo.ui.status(_("adding file changes\n"))
339 repo.ui.status(_("adding file changes\n"))
340 newrevs, newfiles = _addchangegroupfiles(
340 newrevs, newfiles = _addchangegroupfiles(
341 repo, self, revmap, trp, efiles, needfiles)
341 repo, self, revmap, trp, efiles, needfiles)
342 revisions += newrevs
342 revisions += newrevs
343 files += newfiles
343 files += newfiles
344
344
345 deltaheads = 0
345 deltaheads = 0
346 if oldheads:
346 if oldheads:
347 heads = cl.heads()
347 heads = cl.heads()
348 deltaheads = len(heads) - len(oldheads)
348 deltaheads = len(heads) - len(oldheads)
349 for h in heads:
349 for h in heads:
350 if h not in oldheads and repo[h].closesbranch():
350 if h not in oldheads and repo[h].closesbranch():
351 deltaheads -= 1
351 deltaheads -= 1
352 htext = ""
352 htext = ""
353 if deltaheads:
353 if deltaheads:
354 htext = _(" (%+d heads)") % deltaheads
354 htext = _(" (%+d heads)") % deltaheads
355
355
356 repo.ui.status(_("added %d changesets"
356 repo.ui.status(_("added %d changesets"
357 " with %d changes to %d files%s\n")
357 " with %d changes to %d files%s\n")
358 % (changesets, revisions, files, htext))
358 % (changesets, revisions, files, htext))
359 repo.invalidatevolatilesets()
359 repo.invalidatevolatilesets()
360
360
361 if changesets > 0:
361 if changesets > 0:
362 if 'node' not in tr.hookargs:
362 if 'node' not in tr.hookargs:
363 tr.hookargs['node'] = hex(cl.node(clstart))
363 tr.hookargs['node'] = hex(cl.node(clstart))
364 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
364 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
365 hookargs = dict(tr.hookargs)
365 hookargs = dict(tr.hookargs)
366 else:
366 else:
367 hookargs = dict(tr.hookargs)
367 hookargs = dict(tr.hookargs)
368 hookargs['node'] = hex(cl.node(clstart))
368 hookargs['node'] = hex(cl.node(clstart))
369 hookargs['node_last'] = hex(cl.node(clend - 1))
369 hookargs['node_last'] = hex(cl.node(clend - 1))
370 repo.hook('pretxnchangegroup',
370 repo.hook('pretxnchangegroup',
371 throw=True, **pycompat.strkwargs(hookargs))
371 throw=True, **pycompat.strkwargs(hookargs))
372
372
373 added = [cl.node(r) for r in xrange(clstart, clend)]
373 added = [cl.node(r) for r in xrange(clstart, clend)]
374 phaseall = None
374 phaseall = None
375 if srctype in ('push', 'serve'):
375 if srctype in ('push', 'serve'):
376 # Old servers can not push the boundary themselves.
376 # Old servers can not push the boundary themselves.
377 # New servers won't push the boundary if changeset already
377 # New servers won't push the boundary if changeset already
378 # exists locally as secret
378 # exists locally as secret
379 #
379 #
380 # We should not use added here but the list of all change in
380 # We should not use added here but the list of all change in
381 # the bundle
381 # the bundle
382 if repo.publishing():
382 if repo.publishing():
383 targetphase = phaseall = phases.public
383 targetphase = phaseall = phases.public
384 else:
384 else:
385 # closer target phase computation
385 # closer target phase computation
386
386
387 # Those changesets have been pushed from the
387 # Those changesets have been pushed from the
388 # outside, their phases are going to be pushed
388 # outside, their phases are going to be pushed
389 # alongside. Therefor `targetphase` is
389 # alongside. Therefor `targetphase` is
390 # ignored.
390 # ignored.
391 targetphase = phaseall = phases.draft
391 targetphase = phaseall = phases.draft
392 if added:
392 if added:
393 phases.registernew(repo, tr, targetphase, added)
393 phases.registernew(repo, tr, targetphase, added)
394 if phaseall is not None:
394 if phaseall is not None:
395 phases.advanceboundary(repo, tr, phaseall, cgnodes)
395 phases.advanceboundary(repo, tr, phaseall, cgnodes)
396
396
397 if changesets > 0:
397 if changesets > 0:
398
398
399 def runhooks():
399 def runhooks():
400 # These hooks run when the lock releases, not when the
400 # These hooks run when the lock releases, not when the
401 # transaction closes. So it's possible for the changelog
401 # transaction closes. So it's possible for the changelog
402 # to have changed since we last saw it.
402 # to have changed since we last saw it.
403 if clstart >= len(repo):
403 if clstart >= len(repo):
404 return
404 return
405
405
406 repo.hook("changegroup", **pycompat.strkwargs(hookargs))
406 repo.hook("changegroup", **pycompat.strkwargs(hookargs))
407
407
408 for n in added:
408 for n in added:
409 args = hookargs.copy()
409 args = hookargs.copy()
410 args['node'] = hex(n)
410 args['node'] = hex(n)
411 del args['node_last']
411 del args['node_last']
412 repo.hook("incoming", **pycompat.strkwargs(args))
412 repo.hook("incoming", **pycompat.strkwargs(args))
413
413
414 newheads = [h for h in repo.heads()
414 newheads = [h for h in repo.heads()
415 if h not in oldheads]
415 if h not in oldheads]
416 repo.ui.log("incoming",
416 repo.ui.log("incoming",
417 "%s incoming changes - new heads: %s\n",
417 "%s incoming changes - new heads: %s\n",
418 len(added),
418 len(added),
419 ', '.join([hex(c[:6]) for c in newheads]))
419 ', '.join([hex(c[:6]) for c in newheads]))
420
420
421 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
421 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
422 lambda tr: repo._afterlock(runhooks))
422 lambda tr: repo._afterlock(runhooks))
423 finally:
423 finally:
424 repo.ui.flush()
424 repo.ui.flush()
425 # never return 0 here:
425 # never return 0 here:
426 if deltaheads < 0:
426 if deltaheads < 0:
427 ret = deltaheads - 1
427 ret = deltaheads - 1
428 else:
428 else:
429 ret = deltaheads + 1
429 ret = deltaheads + 1
430 return ret
430 return ret
431
431
432 def deltaiter(self):
432 def deltaiter(self):
433 """
433 """
434 returns an iterator of the deltas in this changegroup
434 returns an iterator of the deltas in this changegroup
435
435
436 Useful for passing to the underlying storage system to be stored.
436 Useful for passing to the underlying storage system to be stored.
437 """
437 """
438 chain = None
438 chain = None
439 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
439 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
440 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags)
440 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags)
441 yield chunkdata
441 yield chunkdata
442 chain = chunkdata[0]
442 chain = chunkdata[0]
443
443
444 class cg2unpacker(cg1unpacker):
444 class cg2unpacker(cg1unpacker):
445 """Unpacker for cg2 streams.
445 """Unpacker for cg2 streams.
446
446
447 cg2 streams add support for generaldelta, so the delta header
447 cg2 streams add support for generaldelta, so the delta header
448 format is slightly different. All other features about the data
448 format is slightly different. All other features about the data
449 remain the same.
449 remain the same.
450 """
450 """
451 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
451 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
452 deltaheadersize = struct.calcsize(deltaheader)
452 deltaheadersize = struct.calcsize(deltaheader)
453 version = '02'
453 version = '02'
454
454
455 def _deltaheader(self, headertuple, prevnode):
455 def _deltaheader(self, headertuple, prevnode):
456 node, p1, p2, deltabase, cs = headertuple
456 node, p1, p2, deltabase, cs = headertuple
457 flags = 0
457 flags = 0
458 return node, p1, p2, deltabase, cs, flags
458 return node, p1, p2, deltabase, cs, flags
459
459
460 class cg3unpacker(cg2unpacker):
460 class cg3unpacker(cg2unpacker):
461 """Unpacker for cg3 streams.
461 """Unpacker for cg3 streams.
462
462
463 cg3 streams add support for exchanging treemanifests and revlog
463 cg3 streams add support for exchanging treemanifests and revlog
464 flags. It adds the revlog flags to the delta header and an empty chunk
464 flags. It adds the revlog flags to the delta header and an empty chunk
465 separating manifests and files.
465 separating manifests and files.
466 """
466 """
467 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
467 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
468 deltaheadersize = struct.calcsize(deltaheader)
468 deltaheadersize = struct.calcsize(deltaheader)
469 version = '03'
469 version = '03'
470 _grouplistcount = 2 # One list of manifests and one list of files
470 _grouplistcount = 2 # One list of manifests and one list of files
471
471
472 def _deltaheader(self, headertuple, prevnode):
472 def _deltaheader(self, headertuple, prevnode):
473 node, p1, p2, deltabase, cs, flags = headertuple
473 node, p1, p2, deltabase, cs, flags = headertuple
474 return node, p1, p2, deltabase, cs, flags
474 return node, p1, p2, deltabase, cs, flags
475
475
476 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
476 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
477 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog,
477 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog,
478 numchanges)
478 numchanges)
479 for chunkdata in iter(self.filelogheader, {}):
479 for chunkdata in iter(self.filelogheader, {}):
480 # If we get here, there are directory manifests in the changegroup
480 # If we get here, there are directory manifests in the changegroup
481 d = chunkdata["filename"]
481 d = chunkdata["filename"]
482 repo.ui.debug("adding %s revisions\n" % d)
482 repo.ui.debug("adding %s revisions\n" % d)
483 dirlog = repo.manifestlog._revlog.dirlog(d)
483 dirlog = repo.manifestlog._revlog.dirlog(d)
484 deltas = self.deltaiter()
484 deltas = self.deltaiter()
485 if not dirlog.addgroup(deltas, revmap, trp):
485 if not dirlog.addgroup(deltas, revmap, trp):
486 raise error.Abort(_("received dir revlog group is empty"))
486 raise error.Abort(_("received dir revlog group is empty"))
487
487
488 class headerlessfixup(object):
488 class headerlessfixup(object):
489 def __init__(self, fh, h):
489 def __init__(self, fh, h):
490 self._h = h
490 self._h = h
491 self._fh = fh
491 self._fh = fh
492 def read(self, n):
492 def read(self, n):
493 if self._h:
493 if self._h:
494 d, self._h = self._h[:n], self._h[n:]
494 d, self._h = self._h[:n], self._h[n:]
495 if len(d) < n:
495 if len(d) < n:
496 d += readexactly(self._fh, n - len(d))
496 d += readexactly(self._fh, n - len(d))
497 return d
497 return d
498 return readexactly(self._fh, n)
498 return readexactly(self._fh, n)
499
499
500 class cg1packer(object):
500 class cg1packer(object):
501 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
501 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
502 version = '01'
502 version = '01'
503 def __init__(self, repo, bundlecaps=None):
503 def __init__(self, repo, bundlecaps=None):
504 """Given a source repo, construct a bundler.
504 """Given a source repo, construct a bundler.
505
505
506 bundlecaps is optional and can be used to specify the set of
506 bundlecaps is optional and can be used to specify the set of
507 capabilities which can be used to build the bundle. While bundlecaps is
507 capabilities which can be used to build the bundle. While bundlecaps is
508 unused in core Mercurial, extensions rely on this feature to communicate
508 unused in core Mercurial, extensions rely on this feature to communicate
509 capabilities to customize the changegroup packer.
509 capabilities to customize the changegroup packer.
510 """
510 """
511 # Set of capabilities we can use to build the bundle.
511 # Set of capabilities we can use to build the bundle.
512 if bundlecaps is None:
512 if bundlecaps is None:
513 bundlecaps = set()
513 bundlecaps = set()
514 self._bundlecaps = bundlecaps
514 self._bundlecaps = bundlecaps
515 # experimental config: bundle.reorder
515 # experimental config: bundle.reorder
516 reorder = repo.ui.config('bundle', 'reorder')
516 reorder = repo.ui.config('bundle', 'reorder')
517 if reorder == 'auto':
517 if reorder == 'auto':
518 reorder = None
518 reorder = None
519 else:
519 else:
520 reorder = util.parsebool(reorder)
520 reorder = util.parsebool(reorder)
521 self._repo = repo
521 self._repo = repo
522 self._reorder = reorder
522 self._reorder = reorder
523 self._progress = repo.ui.progress
523 self._progress = repo.ui.progress
524 if self._repo.ui.verbose and not self._repo.ui.debugflag:
524 if self._repo.ui.verbose and not self._repo.ui.debugflag:
525 self._verbosenote = self._repo.ui.note
525 self._verbosenote = self._repo.ui.note
526 else:
526 else:
527 self._verbosenote = lambda s: None
527 self._verbosenote = lambda s: None
528
528
529 def close(self):
529 def close(self):
530 return closechunk()
530 return closechunk()
531
531
532 def fileheader(self, fname):
532 def fileheader(self, fname):
533 return chunkheader(len(fname)) + fname
533 return chunkheader(len(fname)) + fname
534
534
535 # Extracted both for clarity and for overriding in extensions.
535 # Extracted both for clarity and for overriding in extensions.
536 def _sortgroup(self, revlog, nodelist, lookup):
536 def _sortgroup(self, revlog, nodelist, lookup):
537 """Sort nodes for change group and turn them into revnums."""
537 """Sort nodes for change group and turn them into revnums."""
538 # for generaldelta revlogs, we linearize the revs; this will both be
538 # for generaldelta revlogs, we linearize the revs; this will both be
539 # much quicker and generate a much smaller bundle
539 # much quicker and generate a much smaller bundle
540 if (revlog._generaldelta and self._reorder is None) or self._reorder:
540 if (revlog._generaldelta and self._reorder is None) or self._reorder:
541 dag = dagutil.revlogdag(revlog)
541 dag = dagutil.revlogdag(revlog)
542 return dag.linearize(set(revlog.rev(n) for n in nodelist))
542 return dag.linearize(set(revlog.rev(n) for n in nodelist))
543 else:
543 else:
544 return sorted([revlog.rev(n) for n in nodelist])
544 return sorted([revlog.rev(n) for n in nodelist])
545
545
546 def group(self, nodelist, revlog, lookup, units=None):
546 def group(self, nodelist, revlog, lookup, units=None):
547 """Calculate a delta group, yielding a sequence of changegroup chunks
547 """Calculate a delta group, yielding a sequence of changegroup chunks
548 (strings).
548 (strings).
549
549
550 Given a list of changeset revs, return a set of deltas and
550 Given a list of changeset revs, return a set of deltas and
551 metadata corresponding to nodes. The first delta is
551 metadata corresponding to nodes. The first delta is
552 first parent(nodelist[0]) -> nodelist[0], the receiver is
552 first parent(nodelist[0]) -> nodelist[0], the receiver is
553 guaranteed to have this parent as it has all history before
553 guaranteed to have this parent as it has all history before
554 these changesets. In the case firstparent is nullrev the
554 these changesets. In the case firstparent is nullrev the
555 changegroup starts with a full revision.
555 changegroup starts with a full revision.
556
556
557 If units is not None, progress detail will be generated, units specifies
557 If units is not None, progress detail will be generated, units specifies
558 the type of revlog that is touched (changelog, manifest, etc.).
558 the type of revlog that is touched (changelog, manifest, etc.).
559 """
559 """
560 # if we don't have any revisions touched by these changesets, bail
560 # if we don't have any revisions touched by these changesets, bail
561 if len(nodelist) == 0:
561 if len(nodelist) == 0:
562 yield self.close()
562 yield self.close()
563 return
563 return
564
564
565 revs = self._sortgroup(revlog, nodelist, lookup)
565 revs = self._sortgroup(revlog, nodelist, lookup)
566
566
567 # add the parent of the first rev
567 # add the parent of the first rev
568 p = revlog.parentrevs(revs[0])[0]
568 p = revlog.parentrevs(revs[0])[0]
569 revs.insert(0, p)
569 revs.insert(0, p)
570
570
571 # build deltas
571 # build deltas
572 total = len(revs) - 1
572 total = len(revs) - 1
573 msgbundling = _('bundling')
573 msgbundling = _('bundling')
574 for r in xrange(len(revs) - 1):
574 for r in xrange(len(revs) - 1):
575 if units is not None:
575 if units is not None:
576 self._progress(msgbundling, r + 1, unit=units, total=total)
576 self._progress(msgbundling, r + 1, unit=units, total=total)
577 prev, curr = revs[r], revs[r + 1]
577 prev, curr = revs[r], revs[r + 1]
578 linknode = lookup(revlog.node(curr))
578 linknode = lookup(revlog.node(curr))
579 for c in self.revchunk(revlog, curr, prev, linknode):
579 for c in self.revchunk(revlog, curr, prev, linknode):
580 yield c
580 yield c
581
581
582 if units is not None:
582 if units is not None:
583 self._progress(msgbundling, None)
583 self._progress(msgbundling, None)
584 yield self.close()
584 yield self.close()
585
585
586 # filter any nodes that claim to be part of the known set
586 # filter any nodes that claim to be part of the known set
587 def prune(self, revlog, missing, commonrevs):
587 def prune(self, revlog, missing, commonrevs):
588 rr, rl = revlog.rev, revlog.linkrev
588 rr, rl = revlog.rev, revlog.linkrev
589 return [n for n in missing if rl(rr(n)) not in commonrevs]
589 return [n for n in missing if rl(rr(n)) not in commonrevs]
590
590
591 def _packmanifests(self, dir, mfnodes, lookuplinknode):
591 def _packmanifests(self, dir, mfnodes, lookuplinknode):
592 """Pack flat manifests into a changegroup stream."""
592 """Pack flat manifests into a changegroup stream."""
593 assert not dir
593 assert not dir
594 for chunk in self.group(mfnodes, self._repo.manifestlog._revlog,
594 for chunk in self.group(mfnodes, self._repo.manifestlog._revlog,
595 lookuplinknode, units=_('manifests')):
595 lookuplinknode, units=_('manifests')):
596 yield chunk
596 yield chunk
597
597
598 def _manifestsdone(self):
598 def _manifestsdone(self):
599 return ''
599 return ''
600
600
601 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
601 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
602 '''yield a sequence of changegroup chunks (strings)'''
602 '''yield a sequence of changegroup chunks (strings)'''
603 repo = self._repo
603 repo = self._repo
604 cl = repo.changelog
604 cl = repo.changelog
605
605
606 clrevorder = {}
606 clrevorder = {}
607 mfs = {} # needed manifests
607 mfs = {} # needed manifests
608 fnodes = {} # needed file nodes
608 fnodes = {} # needed file nodes
609 changedfiles = set()
609 changedfiles = set()
610
610
611 # Callback for the changelog, used to collect changed files and manifest
611 # Callback for the changelog, used to collect changed files and manifest
612 # nodes.
612 # nodes.
613 # Returns the linkrev node (identity in the changelog case).
613 # Returns the linkrev node (identity in the changelog case).
614 def lookupcl(x):
614 def lookupcl(x):
615 c = cl.read(x)
615 c = cl.read(x)
616 clrevorder[x] = len(clrevorder)
616 clrevorder[x] = len(clrevorder)
617 n = c[0]
617 n = c[0]
618 # record the first changeset introducing this manifest version
618 # record the first changeset introducing this manifest version
619 mfs.setdefault(n, x)
619 mfs.setdefault(n, x)
620 # Record a complete list of potentially-changed files in
620 # Record a complete list of potentially-changed files in
621 # this manifest.
621 # this manifest.
622 changedfiles.update(c[3])
622 changedfiles.update(c[3])
623 return x
623 return x
624
624
625 self._verbosenote(_('uncompressed size of bundle content:\n'))
625 self._verbosenote(_('uncompressed size of bundle content:\n'))
626 size = 0
626 size = 0
627 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
627 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
628 size += len(chunk)
628 size += len(chunk)
629 yield chunk
629 yield chunk
630 self._verbosenote(_('%8.i (changelog)\n') % size)
630 self._verbosenote(_('%8.i (changelog)\n') % size)
631
631
632 # We need to make sure that the linkrev in the changegroup refers to
632 # We need to make sure that the linkrev in the changegroup refers to
633 # the first changeset that introduced the manifest or file revision.
633 # the first changeset that introduced the manifest or file revision.
634 # The fastpath is usually safer than the slowpath, because the filelogs
634 # The fastpath is usually safer than the slowpath, because the filelogs
635 # are walked in revlog order.
635 # are walked in revlog order.
636 #
636 #
637 # When taking the slowpath with reorder=None and the manifest revlog
637 # When taking the slowpath with reorder=None and the manifest revlog
638 # uses generaldelta, the manifest may be walked in the "wrong" order.
638 # uses generaldelta, the manifest may be walked in the "wrong" order.
639 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
639 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
640 # cc0ff93d0c0c).
640 # cc0ff93d0c0c).
641 #
641 #
642 # When taking the fastpath, we are only vulnerable to reordering
642 # When taking the fastpath, we are only vulnerable to reordering
643 # of the changelog itself. The changelog never uses generaldelta, so
643 # of the changelog itself. The changelog never uses generaldelta, so
644 # it is only reordered when reorder=True. To handle this case, we
644 # it is only reordered when reorder=True. To handle this case, we
645 # simply take the slowpath, which already has the 'clrevorder' logic.
645 # simply take the slowpath, which already has the 'clrevorder' logic.
646 # This was also fixed in cc0ff93d0c0c.
646 # This was also fixed in cc0ff93d0c0c.
647 fastpathlinkrev = fastpathlinkrev and not self._reorder
647 fastpathlinkrev = fastpathlinkrev and not self._reorder
648 # Treemanifests don't work correctly with fastpathlinkrev
648 # Treemanifests don't work correctly with fastpathlinkrev
649 # either, because we don't discover which directory nodes to
649 # either, because we don't discover which directory nodes to
650 # send along with files. This could probably be fixed.
650 # send along with files. This could probably be fixed.
651 fastpathlinkrev = fastpathlinkrev and (
651 fastpathlinkrev = fastpathlinkrev and (
652 'treemanifest' not in repo.requirements)
652 'treemanifest' not in repo.requirements)
653
653
654 for chunk in self.generatemanifests(commonrevs, clrevorder,
654 for chunk in self.generatemanifests(commonrevs, clrevorder,
655 fastpathlinkrev, mfs, fnodes, source):
655 fastpathlinkrev, mfs, fnodes, source):
656 yield chunk
656 yield chunk
657 mfs.clear()
657 mfs.clear()
658 clrevs = set(cl.rev(x) for x in clnodes)
658 clrevs = set(cl.rev(x) for x in clnodes)
659
659
660 if not fastpathlinkrev:
660 if not fastpathlinkrev:
661 def linknodes(unused, fname):
661 def linknodes(unused, fname):
662 return fnodes.get(fname, {})
662 return fnodes.get(fname, {})
663 else:
663 else:
664 cln = cl.node
664 cln = cl.node
665 def linknodes(filerevlog, fname):
665 def linknodes(filerevlog, fname):
666 llr = filerevlog.linkrev
666 llr = filerevlog.linkrev
667 fln = filerevlog.node
667 fln = filerevlog.node
668 revs = ((r, llr(r)) for r in filerevlog)
668 revs = ((r, llr(r)) for r in filerevlog)
669 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
669 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
670
670
671 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
671 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
672 source):
672 source):
673 yield chunk
673 yield chunk
674
674
675 yield self.close()
675 yield self.close()
676
676
677 if clnodes:
677 if clnodes:
678 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
678 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
679
679
680 def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev, mfs,
680 def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev, mfs,
681 fnodes, source):
681 fnodes, source):
682 """Returns an iterator of changegroup chunks containing manifests.
682 """Returns an iterator of changegroup chunks containing manifests.
683
683
684 `source` is unused here, but is used by extensions like remotefilelog to
684 `source` is unused here, but is used by extensions like remotefilelog to
685 change what is sent based in pulls vs pushes, etc.
685 change what is sent based in pulls vs pushes, etc.
686 """
686 """
687 repo = self._repo
687 repo = self._repo
688 mfl = repo.manifestlog
688 mfl = repo.manifestlog
689 dirlog = mfl._revlog.dirlog
689 dirlog = mfl._revlog.dirlog
690 tmfnodes = {'': mfs}
690 tmfnodes = {'': mfs}
691
691
692 # Callback for the manifest, used to collect linkrevs for filelog
692 # Callback for the manifest, used to collect linkrevs for filelog
693 # revisions.
693 # revisions.
694 # Returns the linkrev node (collected in lookupcl).
694 # Returns the linkrev node (collected in lookupcl).
695 def makelookupmflinknode(dir):
695 def makelookupmflinknode(dir, nodes):
696 if fastpathlinkrev:
696 if fastpathlinkrev:
697 assert not dir
697 assert not dir
698 return mfs.__getitem__
698 return mfs.__getitem__
699
699
700 def lookupmflinknode(x):
700 def lookupmflinknode(x):
701 """Callback for looking up the linknode for manifests.
701 """Callback for looking up the linknode for manifests.
702
702
703 Returns the linkrev node for the specified manifest.
703 Returns the linkrev node for the specified manifest.
704
704
705 SIDE EFFECT:
705 SIDE EFFECT:
706
706
707 1) fclnodes gets populated with the list of relevant
707 1) fclnodes gets populated with the list of relevant
708 file nodes if we're not using fastpathlinkrev
708 file nodes if we're not using fastpathlinkrev
709 2) When treemanifests are in use, collects treemanifest nodes
709 2) When treemanifests are in use, collects treemanifest nodes
710 to send
710 to send
711
711
712 Note that this means manifests must be completely sent to
712 Note that this means manifests must be completely sent to
713 the client before you can trust the list of files and
713 the client before you can trust the list of files and
714 treemanifests to send.
714 treemanifests to send.
715 """
715 """
716 clnode = tmfnodes[dir][x]
716 clnode = nodes[x]
717 mdata = mfl.get(dir, x).readfast(shallow=True)
717 mdata = mfl.get(dir, x).readfast(shallow=True)
718 for p, n, fl in mdata.iterentries():
718 for p, n, fl in mdata.iterentries():
719 if fl == 't': # subdirectory manifest
719 if fl == 't': # subdirectory manifest
720 subdir = dir + p + '/'
720 subdir = dir + p + '/'
721 tmfclnodes = tmfnodes.setdefault(subdir, {})
721 tmfclnodes = tmfnodes.setdefault(subdir, {})
722 tmfclnode = tmfclnodes.setdefault(n, clnode)
722 tmfclnode = tmfclnodes.setdefault(n, clnode)
723 if clrevorder[clnode] < clrevorder[tmfclnode]:
723 if clrevorder[clnode] < clrevorder[tmfclnode]:
724 tmfclnodes[n] = clnode
724 tmfclnodes[n] = clnode
725 else:
725 else:
726 f = dir + p
726 f = dir + p
727 fclnodes = fnodes.setdefault(f, {})
727 fclnodes = fnodes.setdefault(f, {})
728 fclnode = fclnodes.setdefault(n, clnode)
728 fclnode = fclnodes.setdefault(n, clnode)
729 if clrevorder[clnode] < clrevorder[fclnode]:
729 if clrevorder[clnode] < clrevorder[fclnode]:
730 fclnodes[n] = clnode
730 fclnodes[n] = clnode
731 return clnode
731 return clnode
732 return lookupmflinknode
732 return lookupmflinknode
733
733
734 size = 0
734 size = 0
735 while tmfnodes:
735 while tmfnodes:
736 dir = min(tmfnodes)
736 dir, nodes = tmfnodes.popitem()
737 nodes = tmfnodes[dir]
738 prunednodes = self.prune(dirlog(dir), nodes, commonrevs)
737 prunednodes = self.prune(dirlog(dir), nodes, commonrevs)
739 if not dir or prunednodes:
738 if not dir or prunednodes:
740 for x in self._packmanifests(dir, prunednodes,
739 for x in self._packmanifests(dir, prunednodes,
741 makelookupmflinknode(dir)):
740 makelookupmflinknode(dir, nodes)):
742 size += len(x)
741 size += len(x)
743 yield x
742 yield x
744 del tmfnodes[dir]
745 self._verbosenote(_('%8.i (manifests)\n') % size)
743 self._verbosenote(_('%8.i (manifests)\n') % size)
746 yield self._manifestsdone()
744 yield self._manifestsdone()
747
745
748 # The 'source' parameter is useful for extensions
746 # The 'source' parameter is useful for extensions
749 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
747 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
750 repo = self._repo
748 repo = self._repo
751 progress = self._progress
749 progress = self._progress
752 msgbundling = _('bundling')
750 msgbundling = _('bundling')
753
751
754 total = len(changedfiles)
752 total = len(changedfiles)
755 # for progress output
753 # for progress output
756 msgfiles = _('files')
754 msgfiles = _('files')
757 for i, fname in enumerate(sorted(changedfiles)):
755 for i, fname in enumerate(sorted(changedfiles)):
758 filerevlog = repo.file(fname)
756 filerevlog = repo.file(fname)
759 if not filerevlog:
757 if not filerevlog:
760 raise error.Abort(_("empty or missing revlog for %s") % fname)
758 raise error.Abort(_("empty or missing revlog for %s") % fname)
761
759
762 linkrevnodes = linknodes(filerevlog, fname)
760 linkrevnodes = linknodes(filerevlog, fname)
763 # Lookup for filenodes, we collected the linkrev nodes above in the
761 # Lookup for filenodes, we collected the linkrev nodes above in the
764 # fastpath case and with lookupmf in the slowpath case.
762 # fastpath case and with lookupmf in the slowpath case.
765 def lookupfilelog(x):
763 def lookupfilelog(x):
766 return linkrevnodes[x]
764 return linkrevnodes[x]
767
765
768 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
766 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
769 if filenodes:
767 if filenodes:
770 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
768 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
771 total=total)
769 total=total)
772 h = self.fileheader(fname)
770 h = self.fileheader(fname)
773 size = len(h)
771 size = len(h)
774 yield h
772 yield h
775 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
773 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
776 size += len(chunk)
774 size += len(chunk)
777 yield chunk
775 yield chunk
778 self._verbosenote(_('%8.i %s\n') % (size, fname))
776 self._verbosenote(_('%8.i %s\n') % (size, fname))
779 progress(msgbundling, None)
777 progress(msgbundling, None)
780
778
781 def deltaparent(self, revlog, rev, p1, p2, prev):
779 def deltaparent(self, revlog, rev, p1, p2, prev):
782 return prev
780 return prev
783
781
784 def revchunk(self, revlog, rev, prev, linknode):
782 def revchunk(self, revlog, rev, prev, linknode):
785 node = revlog.node(rev)
783 node = revlog.node(rev)
786 p1, p2 = revlog.parentrevs(rev)
784 p1, p2 = revlog.parentrevs(rev)
787 base = self.deltaparent(revlog, rev, p1, p2, prev)
785 base = self.deltaparent(revlog, rev, p1, p2, prev)
788
786
789 prefix = ''
787 prefix = ''
790 if revlog.iscensored(base) or revlog.iscensored(rev):
788 if revlog.iscensored(base) or revlog.iscensored(rev):
791 try:
789 try:
792 delta = revlog.revision(node, raw=True)
790 delta = revlog.revision(node, raw=True)
793 except error.CensoredNodeError as e:
791 except error.CensoredNodeError as e:
794 delta = e.tombstone
792 delta = e.tombstone
795 if base == nullrev:
793 if base == nullrev:
796 prefix = mdiff.trivialdiffheader(len(delta))
794 prefix = mdiff.trivialdiffheader(len(delta))
797 else:
795 else:
798 baselen = revlog.rawsize(base)
796 baselen = revlog.rawsize(base)
799 prefix = mdiff.replacediffheader(baselen, len(delta))
797 prefix = mdiff.replacediffheader(baselen, len(delta))
800 elif base == nullrev:
798 elif base == nullrev:
801 delta = revlog.revision(node, raw=True)
799 delta = revlog.revision(node, raw=True)
802 prefix = mdiff.trivialdiffheader(len(delta))
800 prefix = mdiff.trivialdiffheader(len(delta))
803 else:
801 else:
804 delta = revlog.revdiff(base, rev)
802 delta = revlog.revdiff(base, rev)
805 p1n, p2n = revlog.parents(node)
803 p1n, p2n = revlog.parents(node)
806 basenode = revlog.node(base)
804 basenode = revlog.node(base)
807 flags = revlog.flags(rev)
805 flags = revlog.flags(rev)
808 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode, flags)
806 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode, flags)
809 meta += prefix
807 meta += prefix
810 l = len(meta) + len(delta)
808 l = len(meta) + len(delta)
811 yield chunkheader(l)
809 yield chunkheader(l)
812 yield meta
810 yield meta
813 yield delta
811 yield delta
814 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
812 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
815 # do nothing with basenode, it is implicitly the previous one in HG10
813 # do nothing with basenode, it is implicitly the previous one in HG10
816 # do nothing with flags, it is implicitly 0 for cg1 and cg2
814 # do nothing with flags, it is implicitly 0 for cg1 and cg2
817 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
815 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
818
816
819 class cg2packer(cg1packer):
817 class cg2packer(cg1packer):
820 version = '02'
818 version = '02'
821 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
819 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
822
820
823 def __init__(self, repo, bundlecaps=None):
821 def __init__(self, repo, bundlecaps=None):
824 super(cg2packer, self).__init__(repo, bundlecaps)
822 super(cg2packer, self).__init__(repo, bundlecaps)
825 if self._reorder is None:
823 if self._reorder is None:
826 # Since generaldelta is directly supported by cg2, reordering
824 # Since generaldelta is directly supported by cg2, reordering
827 # generally doesn't help, so we disable it by default (treating
825 # generally doesn't help, so we disable it by default (treating
828 # bundle.reorder=auto just like bundle.reorder=False).
826 # bundle.reorder=auto just like bundle.reorder=False).
829 self._reorder = False
827 self._reorder = False
830
828
831 def deltaparent(self, revlog, rev, p1, p2, prev):
829 def deltaparent(self, revlog, rev, p1, p2, prev):
832 dp = revlog.deltaparent(rev)
830 dp = revlog.deltaparent(rev)
833 if dp == nullrev and revlog.storedeltachains:
831 if dp == nullrev and revlog.storedeltachains:
834 # Avoid sending full revisions when delta parent is null. Pick prev
832 # Avoid sending full revisions when delta parent is null. Pick prev
835 # in that case. It's tempting to pick p1 in this case, as p1 will
833 # in that case. It's tempting to pick p1 in this case, as p1 will
836 # be smaller in the common case. However, computing a delta against
834 # be smaller in the common case. However, computing a delta against
837 # p1 may require resolving the raw text of p1, which could be
835 # p1 may require resolving the raw text of p1, which could be
838 # expensive. The revlog caches should have prev cached, meaning
836 # expensive. The revlog caches should have prev cached, meaning
839 # less CPU for changegroup generation. There is likely room to add
837 # less CPU for changegroup generation. There is likely room to add
840 # a flag and/or config option to control this behavior.
838 # a flag and/or config option to control this behavior.
841 return prev
839 return prev
842 elif dp == nullrev:
840 elif dp == nullrev:
843 # revlog is configured to use full snapshot for a reason,
841 # revlog is configured to use full snapshot for a reason,
844 # stick to full snapshot.
842 # stick to full snapshot.
845 return nullrev
843 return nullrev
846 elif dp not in (p1, p2, prev):
844 elif dp not in (p1, p2, prev):
847 # Pick prev when we can't be sure remote has the base revision.
845 # Pick prev when we can't be sure remote has the base revision.
848 return prev
846 return prev
849 else:
847 else:
850 return dp
848 return dp
851
849
852 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
850 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
853 # Do nothing with flags, it is implicitly 0 in cg1 and cg2
851 # Do nothing with flags, it is implicitly 0 in cg1 and cg2
854 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
852 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
855
853
856 class cg3packer(cg2packer):
854 class cg3packer(cg2packer):
857 version = '03'
855 version = '03'
858 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
856 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
859
857
860 def _packmanifests(self, dir, mfnodes, lookuplinknode):
858 def _packmanifests(self, dir, mfnodes, lookuplinknode):
861 if dir:
859 if dir:
862 yield self.fileheader(dir)
860 yield self.fileheader(dir)
863
861
864 dirlog = self._repo.manifestlog._revlog.dirlog(dir)
862 dirlog = self._repo.manifestlog._revlog.dirlog(dir)
865 for chunk in self.group(mfnodes, dirlog, lookuplinknode,
863 for chunk in self.group(mfnodes, dirlog, lookuplinknode,
866 units=_('manifests')):
864 units=_('manifests')):
867 yield chunk
865 yield chunk
868
866
869 def _manifestsdone(self):
867 def _manifestsdone(self):
870 return self.close()
868 return self.close()
871
869
872 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
870 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
873 return struct.pack(
871 return struct.pack(
874 self.deltaheader, node, p1n, p2n, basenode, linknode, flags)
872 self.deltaheader, node, p1n, p2n, basenode, linknode, flags)
875
873
876 _packermap = {'01': (cg1packer, cg1unpacker),
874 _packermap = {'01': (cg1packer, cg1unpacker),
877 # cg2 adds support for exchanging generaldelta
875 # cg2 adds support for exchanging generaldelta
878 '02': (cg2packer, cg2unpacker),
876 '02': (cg2packer, cg2unpacker),
879 # cg3 adds support for exchanging revlog flags and treemanifests
877 # cg3 adds support for exchanging revlog flags and treemanifests
880 '03': (cg3packer, cg3unpacker),
878 '03': (cg3packer, cg3unpacker),
881 }
879 }
882
880
883 def allsupportedversions(repo):
881 def allsupportedversions(repo):
884 versions = set(_packermap.keys())
882 versions = set(_packermap.keys())
885 if not (repo.ui.configbool('experimental', 'changegroup3') or
883 if not (repo.ui.configbool('experimental', 'changegroup3') or
886 repo.ui.configbool('experimental', 'treemanifest') or
884 repo.ui.configbool('experimental', 'treemanifest') or
887 'treemanifest' in repo.requirements):
885 'treemanifest' in repo.requirements):
888 versions.discard('03')
886 versions.discard('03')
889 return versions
887 return versions
890
888
891 # Changegroup versions that can be applied to the repo
889 # Changegroup versions that can be applied to the repo
892 def supportedincomingversions(repo):
890 def supportedincomingversions(repo):
893 return allsupportedversions(repo)
891 return allsupportedversions(repo)
894
892
895 # Changegroup versions that can be created from the repo
893 # Changegroup versions that can be created from the repo
896 def supportedoutgoingversions(repo):
894 def supportedoutgoingversions(repo):
897 versions = allsupportedversions(repo)
895 versions = allsupportedversions(repo)
898 if 'treemanifest' in repo.requirements:
896 if 'treemanifest' in repo.requirements:
899 # Versions 01 and 02 support only flat manifests and it's just too
897 # Versions 01 and 02 support only flat manifests and it's just too
900 # expensive to convert between the flat manifest and tree manifest on
898 # expensive to convert between the flat manifest and tree manifest on
901 # the fly. Since tree manifests are hashed differently, all of history
899 # the fly. Since tree manifests are hashed differently, all of history
902 # would have to be converted. Instead, we simply don't even pretend to
900 # would have to be converted. Instead, we simply don't even pretend to
903 # support versions 01 and 02.
901 # support versions 01 and 02.
904 versions.discard('01')
902 versions.discard('01')
905 versions.discard('02')
903 versions.discard('02')
906 return versions
904 return versions
907
905
908 def localversion(repo):
906 def localversion(repo):
909 # Finds the best version to use for bundles that are meant to be used
907 # Finds the best version to use for bundles that are meant to be used
910 # locally, such as those from strip and shelve, and temporary bundles.
908 # locally, such as those from strip and shelve, and temporary bundles.
911 return max(supportedoutgoingversions(repo))
909 return max(supportedoutgoingversions(repo))
912
910
913 def safeversion(repo):
911 def safeversion(repo):
914 # Finds the smallest version that it's safe to assume clients of the repo
912 # Finds the smallest version that it's safe to assume clients of the repo
915 # will support. For example, all hg versions that support generaldelta also
913 # will support. For example, all hg versions that support generaldelta also
916 # support changegroup 02.
914 # support changegroup 02.
917 versions = supportedoutgoingversions(repo)
915 versions = supportedoutgoingversions(repo)
918 if 'generaldelta' in repo.requirements:
916 if 'generaldelta' in repo.requirements:
919 versions.discard('01')
917 versions.discard('01')
920 assert versions
918 assert versions
921 return min(versions)
919 return min(versions)
922
920
923 def getbundler(version, repo, bundlecaps=None):
921 def getbundler(version, repo, bundlecaps=None):
924 assert version in supportedoutgoingversions(repo)
922 assert version in supportedoutgoingversions(repo)
925 return _packermap[version][0](repo, bundlecaps)
923 return _packermap[version][0](repo, bundlecaps)
926
924
927 def getunbundler(version, fh, alg, extras=None):
925 def getunbundler(version, fh, alg, extras=None):
928 return _packermap[version][1](fh, alg, extras=extras)
926 return _packermap[version][1](fh, alg, extras=extras)
929
927
930 def _changegroupinfo(repo, nodes, source):
928 def _changegroupinfo(repo, nodes, source):
931 if repo.ui.verbose or source == 'bundle':
929 if repo.ui.verbose or source == 'bundle':
932 repo.ui.status(_("%d changesets found\n") % len(nodes))
930 repo.ui.status(_("%d changesets found\n") % len(nodes))
933 if repo.ui.debugflag:
931 if repo.ui.debugflag:
934 repo.ui.debug("list of changesets:\n")
932 repo.ui.debug("list of changesets:\n")
935 for node in nodes:
933 for node in nodes:
936 repo.ui.debug("%s\n" % hex(node))
934 repo.ui.debug("%s\n" % hex(node))
937
935
938 def makechangegroup(repo, outgoing, version, source, fastpath=False,
936 def makechangegroup(repo, outgoing, version, source, fastpath=False,
939 bundlecaps=None):
937 bundlecaps=None):
940 cgstream = makestream(repo, outgoing, version, source,
938 cgstream = makestream(repo, outgoing, version, source,
941 fastpath=fastpath, bundlecaps=bundlecaps)
939 fastpath=fastpath, bundlecaps=bundlecaps)
942 return getunbundler(version, util.chunkbuffer(cgstream), None,
940 return getunbundler(version, util.chunkbuffer(cgstream), None,
943 {'clcount': len(outgoing.missing) })
941 {'clcount': len(outgoing.missing) })
944
942
945 def makestream(repo, outgoing, version, source, fastpath=False,
943 def makestream(repo, outgoing, version, source, fastpath=False,
946 bundlecaps=None):
944 bundlecaps=None):
947 bundler = getbundler(version, repo, bundlecaps=bundlecaps)
945 bundler = getbundler(version, repo, bundlecaps=bundlecaps)
948
946
949 repo = repo.unfiltered()
947 repo = repo.unfiltered()
950 commonrevs = outgoing.common
948 commonrevs = outgoing.common
951 csets = outgoing.missing
949 csets = outgoing.missing
952 heads = outgoing.missingheads
950 heads = outgoing.missingheads
953 # We go through the fast path if we get told to, or if all (unfiltered
951 # We go through the fast path if we get told to, or if all (unfiltered
954 # heads have been requested (since we then know there all linkrevs will
952 # heads have been requested (since we then know there all linkrevs will
955 # be pulled by the client).
953 # be pulled by the client).
956 heads.sort()
954 heads.sort()
957 fastpathlinkrev = fastpath or (
955 fastpathlinkrev = fastpath or (
958 repo.filtername is None and heads == sorted(repo.heads()))
956 repo.filtername is None and heads == sorted(repo.heads()))
959
957
960 repo.hook('preoutgoing', throw=True, source=source)
958 repo.hook('preoutgoing', throw=True, source=source)
961 _changegroupinfo(repo, csets, source)
959 _changegroupinfo(repo, csets, source)
962 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
960 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
963
961
964 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
962 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
965 revisions = 0
963 revisions = 0
966 files = 0
964 files = 0
967 for chunkdata in iter(source.filelogheader, {}):
965 for chunkdata in iter(source.filelogheader, {}):
968 files += 1
966 files += 1
969 f = chunkdata["filename"]
967 f = chunkdata["filename"]
970 repo.ui.debug("adding %s revisions\n" % f)
968 repo.ui.debug("adding %s revisions\n" % f)
971 repo.ui.progress(_('files'), files, unit=_('files'),
969 repo.ui.progress(_('files'), files, unit=_('files'),
972 total=expectedfiles)
970 total=expectedfiles)
973 fl = repo.file(f)
971 fl = repo.file(f)
974 o = len(fl)
972 o = len(fl)
975 try:
973 try:
976 deltas = source.deltaiter()
974 deltas = source.deltaiter()
977 if not fl.addgroup(deltas, revmap, trp):
975 if not fl.addgroup(deltas, revmap, trp):
978 raise error.Abort(_("received file revlog group is empty"))
976 raise error.Abort(_("received file revlog group is empty"))
979 except error.CensoredBaseError as e:
977 except error.CensoredBaseError as e:
980 raise error.Abort(_("received delta base is censored: %s") % e)
978 raise error.Abort(_("received delta base is censored: %s") % e)
981 revisions += len(fl) - o
979 revisions += len(fl) - o
982 if f in needfiles:
980 if f in needfiles:
983 needs = needfiles[f]
981 needs = needfiles[f]
984 for new in xrange(o, len(fl)):
982 for new in xrange(o, len(fl)):
985 n = fl.node(new)
983 n = fl.node(new)
986 if n in needs:
984 if n in needs:
987 needs.remove(n)
985 needs.remove(n)
988 else:
986 else:
989 raise error.Abort(
987 raise error.Abort(
990 _("received spurious file revlog entry"))
988 _("received spurious file revlog entry"))
991 if not needs:
989 if not needs:
992 del needfiles[f]
990 del needfiles[f]
993 repo.ui.progress(_('files'), None)
991 repo.ui.progress(_('files'), None)
994
992
995 for f, needs in needfiles.iteritems():
993 for f, needs in needfiles.iteritems():
996 fl = repo.file(f)
994 fl = repo.file(f)
997 for n in needs:
995 for n in needs:
998 try:
996 try:
999 fl.rev(n)
997 fl.rev(n)
1000 except error.LookupError:
998 except error.LookupError:
1001 raise error.Abort(
999 raise error.Abort(
1002 _('missing file data for %s:%s - run hg verify') %
1000 _('missing file data for %s:%s - run hg verify') %
1003 (f, hex(n)))
1001 (f, hex(n)))
1004
1002
1005 return revisions, files
1003 return revisions, files
General Comments 0
You need to be logged in to leave comments. Login now