##// END OF EJS Templates
changegroup: move generatefiles() from narrow...
Gregory Szorc -
r38925:a06aab27 default
parent child Browse files
Show More
@@ -1,180 +1,145 b''
1 # narrowchangegroup.py - narrow clone changegroup creation and consumption
1 # narrowchangegroup.py - narrow clone changegroup creation and consumption
2 #
2 #
3 # Copyright 2017 Google, Inc.
3 # Copyright 2017 Google, Inc.
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 from mercurial.i18n import _
10 from mercurial.i18n import _
11 from mercurial import (
11 from mercurial import (
12 changegroup,
12 changegroup,
13 error,
14 extensions,
13 extensions,
15 node,
14 node,
16 util,
15 util,
17 )
16 )
18
17
19 def setup():
18 def setup():
20 def generatefiles(orig, self, changedfiles, linknodes, commonrevs,
21 source):
22 changedfiles = list(filter(self._filematcher, changedfiles))
23
24 if getattr(self, 'is_shallow', False):
25 # See comment in generate() for why this sadness is a thing.
26 mfdicts = self._mfdicts
27 del self._mfdicts
28 # In a shallow clone, the linknodes callback needs to also include
29 # those file nodes that are in the manifests we sent but weren't
30 # introduced by those manifests.
31 commonctxs = [self._repo[c] for c in commonrevs]
32 oldlinknodes = linknodes
33 clrev = self._repo.changelog.rev
34 def linknodes(flog, fname):
35 for c in commonctxs:
36 try:
37 fnode = c.filenode(fname)
38 self.clrev_to_localrev[c.rev()] = flog.rev(fnode)
39 except error.ManifestLookupError:
40 pass
41 links = oldlinknodes(flog, fname)
42 if len(links) != len(mfdicts):
43 for mf, lr in mfdicts:
44 fnode = mf.get(fname, None)
45 if fnode in links:
46 links[fnode] = min(links[fnode], lr, key=clrev)
47 elif fnode:
48 links[fnode] = lr
49 return links
50 return orig(self, changedfiles, linknodes, commonrevs, source)
51 extensions.wrapfunction(
52 changegroup.cg1packer, 'generatefiles', generatefiles)
53
54 def generate(orig, self, commonrevs, clnodes, fastpathlinkrev, source):
19 def generate(orig, self, commonrevs, clnodes, fastpathlinkrev, source):
55 '''yield a sequence of changegroup chunks (strings)'''
20 '''yield a sequence of changegroup chunks (strings)'''
56 # Note: other than delegating to orig, the only deviation in
21 # Note: other than delegating to orig, the only deviation in
57 # logic from normal hg's generate is marked with BEGIN/END
22 # logic from normal hg's generate is marked with BEGIN/END
58 # NARROW HACK.
23 # NARROW HACK.
59 if not util.safehasattr(self, 'full_nodes'):
24 if not util.safehasattr(self, 'full_nodes'):
60 # not sending a narrow bundle
25 # not sending a narrow bundle
61 for x in orig(self, commonrevs, clnodes, fastpathlinkrev, source):
26 for x in orig(self, commonrevs, clnodes, fastpathlinkrev, source):
62 yield x
27 yield x
63 return
28 return
64
29
65 repo = self._repo
30 repo = self._repo
66 cl = repo.changelog
31 cl = repo.changelog
67 mfl = repo.manifestlog
32 mfl = repo.manifestlog
68 mfrevlog = mfl._revlog
33 mfrevlog = mfl._revlog
69
34
70 clrevorder = {}
35 clrevorder = {}
71 mfs = {} # needed manifests
36 mfs = {} # needed manifests
72 fnodes = {} # needed file nodes
37 fnodes = {} # needed file nodes
73 changedfiles = set()
38 changedfiles = set()
74
39
75 # Callback for the changelog, used to collect changed files and manifest
40 # Callback for the changelog, used to collect changed files and manifest
76 # nodes.
41 # nodes.
77 # Returns the linkrev node (identity in the changelog case).
42 # Returns the linkrev node (identity in the changelog case).
78 def lookupcl(x):
43 def lookupcl(x):
79 c = cl.read(x)
44 c = cl.read(x)
80 clrevorder[x] = len(clrevorder)
45 clrevorder[x] = len(clrevorder)
81 # BEGIN NARROW HACK
46 # BEGIN NARROW HACK
82 #
47 #
83 # Only update mfs if x is going to be sent. Otherwise we
48 # Only update mfs if x is going to be sent. Otherwise we
84 # end up with bogus linkrevs specified for manifests and
49 # end up with bogus linkrevs specified for manifests and
85 # we skip some manifest nodes that we should otherwise
50 # we skip some manifest nodes that we should otherwise
86 # have sent.
51 # have sent.
87 if x in self.full_nodes or cl.rev(x) in self.precomputed_ellipsis:
52 if x in self.full_nodes or cl.rev(x) in self.precomputed_ellipsis:
88 n = c[0]
53 n = c[0]
89 # record the first changeset introducing this manifest version
54 # record the first changeset introducing this manifest version
90 mfs.setdefault(n, x)
55 mfs.setdefault(n, x)
91 # Set this narrow-specific dict so we have the lowest manifest
56 # Set this narrow-specific dict so we have the lowest manifest
92 # revnum to look up for this cl revnum. (Part of mapping
57 # revnum to look up for this cl revnum. (Part of mapping
93 # changelog ellipsis parents to manifest ellipsis parents)
58 # changelog ellipsis parents to manifest ellipsis parents)
94 self.next_clrev_to_localrev.setdefault(cl.rev(x),
59 self.next_clrev_to_localrev.setdefault(cl.rev(x),
95 mfrevlog.rev(n))
60 mfrevlog.rev(n))
96 # We can't trust the changed files list in the changeset if the
61 # We can't trust the changed files list in the changeset if the
97 # client requested a shallow clone.
62 # client requested a shallow clone.
98 if self.is_shallow:
63 if self.is_shallow:
99 changedfiles.update(mfl[c[0]].read().keys())
64 changedfiles.update(mfl[c[0]].read().keys())
100 else:
65 else:
101 changedfiles.update(c[3])
66 changedfiles.update(c[3])
102 # END NARROW HACK
67 # END NARROW HACK
103 # Record a complete list of potentially-changed files in
68 # Record a complete list of potentially-changed files in
104 # this manifest.
69 # this manifest.
105 return x
70 return x
106
71
107 self._verbosenote(_('uncompressed size of bundle content:\n'))
72 self._verbosenote(_('uncompressed size of bundle content:\n'))
108 size = 0
73 size = 0
109 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
74 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
110 size += len(chunk)
75 size += len(chunk)
111 yield chunk
76 yield chunk
112 self._verbosenote(_('%8.i (changelog)\n') % size)
77 self._verbosenote(_('%8.i (changelog)\n') % size)
113
78
114 # We need to make sure that the linkrev in the changegroup refers to
79 # We need to make sure that the linkrev in the changegroup refers to
115 # the first changeset that introduced the manifest or file revision.
80 # the first changeset that introduced the manifest or file revision.
116 # The fastpath is usually safer than the slowpath, because the filelogs
81 # The fastpath is usually safer than the slowpath, because the filelogs
117 # are walked in revlog order.
82 # are walked in revlog order.
118 #
83 #
119 # When taking the slowpath with reorder=None and the manifest revlog
84 # When taking the slowpath with reorder=None and the manifest revlog
120 # uses generaldelta, the manifest may be walked in the "wrong" order.
85 # uses generaldelta, the manifest may be walked in the "wrong" order.
121 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
86 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
122 # cc0ff93d0c0c).
87 # cc0ff93d0c0c).
123 #
88 #
124 # When taking the fastpath, we are only vulnerable to reordering
89 # When taking the fastpath, we are only vulnerable to reordering
125 # of the changelog itself. The changelog never uses generaldelta, so
90 # of the changelog itself. The changelog never uses generaldelta, so
126 # it is only reordered when reorder=True. To handle this case, we
91 # it is only reordered when reorder=True. To handle this case, we
127 # simply take the slowpath, which already has the 'clrevorder' logic.
92 # simply take the slowpath, which already has the 'clrevorder' logic.
128 # This was also fixed in cc0ff93d0c0c.
93 # This was also fixed in cc0ff93d0c0c.
129 fastpathlinkrev = fastpathlinkrev and not self._reorder
94 fastpathlinkrev = fastpathlinkrev and not self._reorder
130 # Treemanifests don't work correctly with fastpathlinkrev
95 # Treemanifests don't work correctly with fastpathlinkrev
131 # either, because we don't discover which directory nodes to
96 # either, because we don't discover which directory nodes to
132 # send along with files. This could probably be fixed.
97 # send along with files. This could probably be fixed.
133 fastpathlinkrev = fastpathlinkrev and (
98 fastpathlinkrev = fastpathlinkrev and (
134 'treemanifest' not in repo.requirements)
99 'treemanifest' not in repo.requirements)
135 # Shallow clones also don't work correctly with fastpathlinkrev
100 # Shallow clones also don't work correctly with fastpathlinkrev
136 # because file nodes may need to be sent for a manifest even if they
101 # because file nodes may need to be sent for a manifest even if they
137 # weren't introduced by that manifest.
102 # weren't introduced by that manifest.
138 fastpathlinkrev = fastpathlinkrev and not self.is_shallow
103 fastpathlinkrev = fastpathlinkrev and not self.is_shallow
139
104
140 for chunk in self.generatemanifests(commonrevs, clrevorder,
105 for chunk in self.generatemanifests(commonrevs, clrevorder,
141 fastpathlinkrev, mfs, fnodes, source):
106 fastpathlinkrev, mfs, fnodes, source):
142 yield chunk
107 yield chunk
143 # BEGIN NARROW HACK
108 # BEGIN NARROW HACK
144 mfdicts = None
109 mfdicts = None
145 if self.is_shallow:
110 if self.is_shallow:
146 mfdicts = [(self._repo.manifestlog[n].read(), lr)
111 mfdicts = [(self._repo.manifestlog[n].read(), lr)
147 for (n, lr) in mfs.iteritems()]
112 for (n, lr) in mfs.iteritems()]
148 # END NARROW HACK
113 # END NARROW HACK
149 mfs.clear()
114 mfs.clear()
150 clrevs = set(cl.rev(x) for x in clnodes)
115 clrevs = set(cl.rev(x) for x in clnodes)
151
116
152 if not fastpathlinkrev:
117 if not fastpathlinkrev:
153 def linknodes(unused, fname):
118 def linknodes(unused, fname):
154 return fnodes.get(fname, {})
119 return fnodes.get(fname, {})
155 else:
120 else:
156 cln = cl.node
121 cln = cl.node
157 def linknodes(filerevlog, fname):
122 def linknodes(filerevlog, fname):
158 llr = filerevlog.linkrev
123 llr = filerevlog.linkrev
159 fln = filerevlog.node
124 fln = filerevlog.node
160 revs = ((r, llr(r)) for r in filerevlog)
125 revs = ((r, llr(r)) for r in filerevlog)
161 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
126 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
162
127
163 # BEGIN NARROW HACK
128 # BEGIN NARROW HACK
164 #
129 #
165 # We need to pass the mfdicts variable down into
130 # We need to pass the mfdicts variable down into
166 # generatefiles(), but more than one command might have
131 # generatefiles(), but more than one command might have
167 # wrapped generatefiles so we can't modify the function
132 # wrapped generatefiles so we can't modify the function
168 # signature. Instead, we pass the data to ourselves using an
133 # signature. Instead, we pass the data to ourselves using an
169 # instance attribute. I'm sorry.
134 # instance attribute. I'm sorry.
170 self._mfdicts = mfdicts
135 self._mfdicts = mfdicts
171 # END NARROW HACK
136 # END NARROW HACK
172 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
137 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
173 source):
138 source):
174 yield chunk
139 yield chunk
175
140
176 yield self.close()
141 yield self.close()
177
142
178 if clnodes:
143 if clnodes:
179 repo.hook('outgoing', node=node.hex(clnodes[0]), source=source)
144 repo.hook('outgoing', node=node.hex(clnodes[0]), source=source)
180 extensions.wrapfunction(changegroup.cg1packer, 'generate', generate)
145 extensions.wrapfunction(changegroup.cg1packer, 'generate', generate)
@@ -1,1248 +1,1284 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 weakref
12 import weakref
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 hex,
16 hex,
17 nullid,
17 nullid,
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 manifest,
25 manifest,
26 match as matchmod,
26 match as matchmod,
27 mdiff,
27 mdiff,
28 phases,
28 phases,
29 pycompat,
29 pycompat,
30 repository,
30 repository,
31 revlog,
31 revlog,
32 util,
32 util,
33 )
33 )
34
34
35 from .utils import (
35 from .utils import (
36 stringutil,
36 stringutil,
37 )
37 )
38
38
39 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
39 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
40 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
40 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
41 _CHANGEGROUPV3_DELTA_HEADER = ">20s20s20s20s20sH"
41 _CHANGEGROUPV3_DELTA_HEADER = ">20s20s20s20s20sH"
42
42
43 LFS_REQUIREMENT = 'lfs'
43 LFS_REQUIREMENT = 'lfs'
44
44
45 readexactly = util.readexactly
45 readexactly = util.readexactly
46
46
47 def getchunk(stream):
47 def getchunk(stream):
48 """return the next chunk from stream as a string"""
48 """return the next chunk from stream as a string"""
49 d = readexactly(stream, 4)
49 d = readexactly(stream, 4)
50 l = struct.unpack(">l", d)[0]
50 l = struct.unpack(">l", d)[0]
51 if l <= 4:
51 if l <= 4:
52 if l:
52 if l:
53 raise error.Abort(_("invalid chunk length %d") % l)
53 raise error.Abort(_("invalid chunk length %d") % l)
54 return ""
54 return ""
55 return readexactly(stream, l - 4)
55 return readexactly(stream, l - 4)
56
56
57 def chunkheader(length):
57 def chunkheader(length):
58 """return a changegroup chunk header (string)"""
58 """return a changegroup chunk header (string)"""
59 return struct.pack(">l", length + 4)
59 return struct.pack(">l", length + 4)
60
60
61 def closechunk():
61 def closechunk():
62 """return a changegroup chunk header (string) for a zero-length chunk"""
62 """return a changegroup chunk header (string) for a zero-length chunk"""
63 return struct.pack(">l", 0)
63 return struct.pack(">l", 0)
64
64
65 def writechunks(ui, chunks, filename, vfs=None):
65 def writechunks(ui, chunks, filename, vfs=None):
66 """Write chunks to a file and return its filename.
66 """Write chunks to a file and return its filename.
67
67
68 The stream is assumed to be a bundle file.
68 The stream is assumed to be a bundle file.
69 Existing files will not be overwritten.
69 Existing files will not be overwritten.
70 If no filename is specified, a temporary file is created.
70 If no filename is specified, a temporary file is created.
71 """
71 """
72 fh = None
72 fh = None
73 cleanup = None
73 cleanup = None
74 try:
74 try:
75 if filename:
75 if filename:
76 if vfs:
76 if vfs:
77 fh = vfs.open(filename, "wb")
77 fh = vfs.open(filename, "wb")
78 else:
78 else:
79 # Increase default buffer size because default is usually
79 # Increase default buffer size because default is usually
80 # small (4k is common on Linux).
80 # small (4k is common on Linux).
81 fh = open(filename, "wb", 131072)
81 fh = open(filename, "wb", 131072)
82 else:
82 else:
83 fd, filename = pycompat.mkstemp(prefix="hg-bundle-", suffix=".hg")
83 fd, filename = pycompat.mkstemp(prefix="hg-bundle-", suffix=".hg")
84 fh = os.fdopen(fd, r"wb")
84 fh = os.fdopen(fd, r"wb")
85 cleanup = filename
85 cleanup = filename
86 for c in chunks:
86 for c in chunks:
87 fh.write(c)
87 fh.write(c)
88 cleanup = None
88 cleanup = None
89 return filename
89 return filename
90 finally:
90 finally:
91 if fh is not None:
91 if fh is not None:
92 fh.close()
92 fh.close()
93 if cleanup is not None:
93 if cleanup is not None:
94 if filename and vfs:
94 if filename and vfs:
95 vfs.unlink(cleanup)
95 vfs.unlink(cleanup)
96 else:
96 else:
97 os.unlink(cleanup)
97 os.unlink(cleanup)
98
98
99 class cg1unpacker(object):
99 class cg1unpacker(object):
100 """Unpacker for cg1 changegroup streams.
100 """Unpacker for cg1 changegroup streams.
101
101
102 A changegroup unpacker handles the framing of the revision data in
102 A changegroup unpacker handles the framing of the revision data in
103 the wire format. Most consumers will want to use the apply()
103 the wire format. Most consumers will want to use the apply()
104 method to add the changes from the changegroup to a repository.
104 method to add the changes from the changegroup to a repository.
105
105
106 If you're forwarding a changegroup unmodified to another consumer,
106 If you're forwarding a changegroup unmodified to another consumer,
107 use getchunks(), which returns an iterator of changegroup
107 use getchunks(), which returns an iterator of changegroup
108 chunks. This is mostly useful for cases where you need to know the
108 chunks. This is mostly useful for cases where you need to know the
109 data stream has ended by observing the end of the changegroup.
109 data stream has ended by observing the end of the changegroup.
110
110
111 deltachunk() is useful only if you're applying delta data. Most
111 deltachunk() is useful only if you're applying delta data. Most
112 consumers should prefer apply() instead.
112 consumers should prefer apply() instead.
113
113
114 A few other public methods exist. Those are used only for
114 A few other public methods exist. Those are used only for
115 bundlerepo and some debug commands - their use is discouraged.
115 bundlerepo and some debug commands - their use is discouraged.
116 """
116 """
117 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
117 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
118 deltaheadersize = struct.calcsize(deltaheader)
118 deltaheadersize = struct.calcsize(deltaheader)
119 version = '01'
119 version = '01'
120 _grouplistcount = 1 # One list of files after the manifests
120 _grouplistcount = 1 # One list of files after the manifests
121
121
122 def __init__(self, fh, alg, extras=None):
122 def __init__(self, fh, alg, extras=None):
123 if alg is None:
123 if alg is None:
124 alg = 'UN'
124 alg = 'UN'
125 if alg not in util.compengines.supportedbundletypes:
125 if alg not in util.compengines.supportedbundletypes:
126 raise error.Abort(_('unknown stream compression type: %s')
126 raise error.Abort(_('unknown stream compression type: %s')
127 % alg)
127 % alg)
128 if alg == 'BZ':
128 if alg == 'BZ':
129 alg = '_truncatedBZ'
129 alg = '_truncatedBZ'
130
130
131 compengine = util.compengines.forbundletype(alg)
131 compengine = util.compengines.forbundletype(alg)
132 self._stream = compengine.decompressorreader(fh)
132 self._stream = compengine.decompressorreader(fh)
133 self._type = alg
133 self._type = alg
134 self.extras = extras or {}
134 self.extras = extras or {}
135 self.callback = None
135 self.callback = None
136
136
137 # These methods (compressed, read, seek, tell) all appear to only
137 # These methods (compressed, read, seek, tell) all appear to only
138 # be used by bundlerepo, but it's a little hard to tell.
138 # be used by bundlerepo, but it's a little hard to tell.
139 def compressed(self):
139 def compressed(self):
140 return self._type is not None and self._type != 'UN'
140 return self._type is not None and self._type != 'UN'
141 def read(self, l):
141 def read(self, l):
142 return self._stream.read(l)
142 return self._stream.read(l)
143 def seek(self, pos):
143 def seek(self, pos):
144 return self._stream.seek(pos)
144 return self._stream.seek(pos)
145 def tell(self):
145 def tell(self):
146 return self._stream.tell()
146 return self._stream.tell()
147 def close(self):
147 def close(self):
148 return self._stream.close()
148 return self._stream.close()
149
149
150 def _chunklength(self):
150 def _chunklength(self):
151 d = readexactly(self._stream, 4)
151 d = readexactly(self._stream, 4)
152 l = struct.unpack(">l", d)[0]
152 l = struct.unpack(">l", d)[0]
153 if l <= 4:
153 if l <= 4:
154 if l:
154 if l:
155 raise error.Abort(_("invalid chunk length %d") % l)
155 raise error.Abort(_("invalid chunk length %d") % l)
156 return 0
156 return 0
157 if self.callback:
157 if self.callback:
158 self.callback()
158 self.callback()
159 return l - 4
159 return l - 4
160
160
161 def changelogheader(self):
161 def changelogheader(self):
162 """v10 does not have a changelog header chunk"""
162 """v10 does not have a changelog header chunk"""
163 return {}
163 return {}
164
164
165 def manifestheader(self):
165 def manifestheader(self):
166 """v10 does not have a manifest header chunk"""
166 """v10 does not have a manifest header chunk"""
167 return {}
167 return {}
168
168
169 def filelogheader(self):
169 def filelogheader(self):
170 """return the header of the filelogs chunk, v10 only has the filename"""
170 """return the header of the filelogs chunk, v10 only has the filename"""
171 l = self._chunklength()
171 l = self._chunklength()
172 if not l:
172 if not l:
173 return {}
173 return {}
174 fname = readexactly(self._stream, l)
174 fname = readexactly(self._stream, l)
175 return {'filename': fname}
175 return {'filename': fname}
176
176
177 def _deltaheader(self, headertuple, prevnode):
177 def _deltaheader(self, headertuple, prevnode):
178 node, p1, p2, cs = headertuple
178 node, p1, p2, cs = headertuple
179 if prevnode is None:
179 if prevnode is None:
180 deltabase = p1
180 deltabase = p1
181 else:
181 else:
182 deltabase = prevnode
182 deltabase = prevnode
183 flags = 0
183 flags = 0
184 return node, p1, p2, deltabase, cs, flags
184 return node, p1, p2, deltabase, cs, flags
185
185
186 def deltachunk(self, prevnode):
186 def deltachunk(self, prevnode):
187 l = self._chunklength()
187 l = self._chunklength()
188 if not l:
188 if not l:
189 return {}
189 return {}
190 headerdata = readexactly(self._stream, self.deltaheadersize)
190 headerdata = readexactly(self._stream, self.deltaheadersize)
191 header = struct.unpack(self.deltaheader, headerdata)
191 header = struct.unpack(self.deltaheader, headerdata)
192 delta = readexactly(self._stream, l - self.deltaheadersize)
192 delta = readexactly(self._stream, l - self.deltaheadersize)
193 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
193 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
194 return (node, p1, p2, cs, deltabase, delta, flags)
194 return (node, p1, p2, cs, deltabase, delta, flags)
195
195
196 def getchunks(self):
196 def getchunks(self):
197 """returns all the chunks contains in the bundle
197 """returns all the chunks contains in the bundle
198
198
199 Used when you need to forward the binary stream to a file or another
199 Used when you need to forward the binary stream to a file or another
200 network API. To do so, it parse the changegroup data, otherwise it will
200 network API. To do so, it parse the changegroup data, otherwise it will
201 block in case of sshrepo because it don't know the end of the stream.
201 block in case of sshrepo because it don't know the end of the stream.
202 """
202 """
203 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
203 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
204 # and a list of filelogs. For changegroup 3, we expect 4 parts:
204 # and a list of filelogs. For changegroup 3, we expect 4 parts:
205 # changelog, manifestlog, a list of tree manifestlogs, and a list of
205 # changelog, manifestlog, a list of tree manifestlogs, and a list of
206 # filelogs.
206 # filelogs.
207 #
207 #
208 # Changelog and manifestlog parts are terminated with empty chunks. The
208 # Changelog and manifestlog parts are terminated with empty chunks. The
209 # tree and file parts are a list of entry sections. Each entry section
209 # tree and file parts are a list of entry sections. Each entry section
210 # is a series of chunks terminating in an empty chunk. The list of these
210 # is a series of chunks terminating in an empty chunk. The list of these
211 # entry sections is terminated in yet another empty chunk, so we know
211 # entry sections is terminated in yet another empty chunk, so we know
212 # we've reached the end of the tree/file list when we reach an empty
212 # we've reached the end of the tree/file list when we reach an empty
213 # chunk that was proceeded by no non-empty chunks.
213 # chunk that was proceeded by no non-empty chunks.
214
214
215 parts = 0
215 parts = 0
216 while parts < 2 + self._grouplistcount:
216 while parts < 2 + self._grouplistcount:
217 noentries = True
217 noentries = True
218 while True:
218 while True:
219 chunk = getchunk(self)
219 chunk = getchunk(self)
220 if not chunk:
220 if not chunk:
221 # The first two empty chunks represent the end of the
221 # The first two empty chunks represent the end of the
222 # changelog and the manifestlog portions. The remaining
222 # changelog and the manifestlog portions. The remaining
223 # empty chunks represent either A) the end of individual
223 # empty chunks represent either A) the end of individual
224 # tree or file entries in the file list, or B) the end of
224 # tree or file entries in the file list, or B) the end of
225 # the entire list. It's the end of the entire list if there
225 # the entire list. It's the end of the entire list if there
226 # were no entries (i.e. noentries is True).
226 # were no entries (i.e. noentries is True).
227 if parts < 2:
227 if parts < 2:
228 parts += 1
228 parts += 1
229 elif noentries:
229 elif noentries:
230 parts += 1
230 parts += 1
231 break
231 break
232 noentries = False
232 noentries = False
233 yield chunkheader(len(chunk))
233 yield chunkheader(len(chunk))
234 pos = 0
234 pos = 0
235 while pos < len(chunk):
235 while pos < len(chunk):
236 next = pos + 2**20
236 next = pos + 2**20
237 yield chunk[pos:next]
237 yield chunk[pos:next]
238 pos = next
238 pos = next
239 yield closechunk()
239 yield closechunk()
240
240
241 def _unpackmanifests(self, repo, revmap, trp, prog):
241 def _unpackmanifests(self, repo, revmap, trp, prog):
242 self.callback = prog.increment
242 self.callback = prog.increment
243 # no need to check for empty manifest group here:
243 # no need to check for empty manifest group here:
244 # if the result of the merge of 1 and 2 is the same in 3 and 4,
244 # if the result of the merge of 1 and 2 is the same in 3 and 4,
245 # no new manifest will be created and the manifest group will
245 # no new manifest will be created and the manifest group will
246 # be empty during the pull
246 # be empty during the pull
247 self.manifestheader()
247 self.manifestheader()
248 deltas = self.deltaiter()
248 deltas = self.deltaiter()
249 repo.manifestlog.addgroup(deltas, revmap, trp)
249 repo.manifestlog.addgroup(deltas, revmap, trp)
250 prog.complete()
250 prog.complete()
251 self.callback = None
251 self.callback = None
252
252
253 def apply(self, repo, tr, srctype, url, targetphase=phases.draft,
253 def apply(self, repo, tr, srctype, url, targetphase=phases.draft,
254 expectedtotal=None):
254 expectedtotal=None):
255 """Add the changegroup returned by source.read() to this repo.
255 """Add the changegroup returned by source.read() to this repo.
256 srctype is a string like 'push', 'pull', or 'unbundle'. url is
256 srctype is a string like 'push', 'pull', or 'unbundle'. url is
257 the URL of the repo where this changegroup is coming from.
257 the URL of the repo where this changegroup is coming from.
258
258
259 Return an integer summarizing the change to this repo:
259 Return an integer summarizing the change to this repo:
260 - nothing changed or no source: 0
260 - nothing changed or no source: 0
261 - more heads than before: 1+added heads (2..n)
261 - more heads than before: 1+added heads (2..n)
262 - fewer heads than before: -1-removed heads (-2..-n)
262 - fewer heads than before: -1-removed heads (-2..-n)
263 - number of heads stays the same: 1
263 - number of heads stays the same: 1
264 """
264 """
265 repo = repo.unfiltered()
265 repo = repo.unfiltered()
266 def csmap(x):
266 def csmap(x):
267 repo.ui.debug("add changeset %s\n" % short(x))
267 repo.ui.debug("add changeset %s\n" % short(x))
268 return len(cl)
268 return len(cl)
269
269
270 def revmap(x):
270 def revmap(x):
271 return cl.rev(x)
271 return cl.rev(x)
272
272
273 changesets = files = revisions = 0
273 changesets = files = revisions = 0
274
274
275 try:
275 try:
276 # The transaction may already carry source information. In this
276 # The transaction may already carry source information. In this
277 # case we use the top level data. We overwrite the argument
277 # case we use the top level data. We overwrite the argument
278 # because we need to use the top level value (if they exist)
278 # because we need to use the top level value (if they exist)
279 # in this function.
279 # in this function.
280 srctype = tr.hookargs.setdefault('source', srctype)
280 srctype = tr.hookargs.setdefault('source', srctype)
281 url = tr.hookargs.setdefault('url', url)
281 url = tr.hookargs.setdefault('url', url)
282 repo.hook('prechangegroup',
282 repo.hook('prechangegroup',
283 throw=True, **pycompat.strkwargs(tr.hookargs))
283 throw=True, **pycompat.strkwargs(tr.hookargs))
284
284
285 # write changelog data to temp files so concurrent readers
285 # write changelog data to temp files so concurrent readers
286 # will not see an inconsistent view
286 # will not see an inconsistent view
287 cl = repo.changelog
287 cl = repo.changelog
288 cl.delayupdate(tr)
288 cl.delayupdate(tr)
289 oldheads = set(cl.heads())
289 oldheads = set(cl.heads())
290
290
291 trp = weakref.proxy(tr)
291 trp = weakref.proxy(tr)
292 # pull off the changeset group
292 # pull off the changeset group
293 repo.ui.status(_("adding changesets\n"))
293 repo.ui.status(_("adding changesets\n"))
294 clstart = len(cl)
294 clstart = len(cl)
295 progress = repo.ui.makeprogress(_('changesets'), unit=_('chunks'),
295 progress = repo.ui.makeprogress(_('changesets'), unit=_('chunks'),
296 total=expectedtotal)
296 total=expectedtotal)
297 self.callback = progress.increment
297 self.callback = progress.increment
298
298
299 efiles = set()
299 efiles = set()
300 def onchangelog(cl, node):
300 def onchangelog(cl, node):
301 efiles.update(cl.readfiles(node))
301 efiles.update(cl.readfiles(node))
302
302
303 self.changelogheader()
303 self.changelogheader()
304 deltas = self.deltaiter()
304 deltas = self.deltaiter()
305 cgnodes = cl.addgroup(deltas, csmap, trp, addrevisioncb=onchangelog)
305 cgnodes = cl.addgroup(deltas, csmap, trp, addrevisioncb=onchangelog)
306 efiles = len(efiles)
306 efiles = len(efiles)
307
307
308 if not cgnodes:
308 if not cgnodes:
309 repo.ui.develwarn('applied empty changegroup',
309 repo.ui.develwarn('applied empty changegroup',
310 config='warn-empty-changegroup')
310 config='warn-empty-changegroup')
311 clend = len(cl)
311 clend = len(cl)
312 changesets = clend - clstart
312 changesets = clend - clstart
313 progress.complete()
313 progress.complete()
314 self.callback = None
314 self.callback = None
315
315
316 # pull off the manifest group
316 # pull off the manifest group
317 repo.ui.status(_("adding manifests\n"))
317 repo.ui.status(_("adding manifests\n"))
318 # We know that we'll never have more manifests than we had
318 # We know that we'll never have more manifests than we had
319 # changesets.
319 # changesets.
320 progress = repo.ui.makeprogress(_('manifests'), unit=_('chunks'),
320 progress = repo.ui.makeprogress(_('manifests'), unit=_('chunks'),
321 total=changesets)
321 total=changesets)
322 self._unpackmanifests(repo, revmap, trp, progress)
322 self._unpackmanifests(repo, revmap, trp, progress)
323
323
324 needfiles = {}
324 needfiles = {}
325 if repo.ui.configbool('server', 'validate'):
325 if repo.ui.configbool('server', 'validate'):
326 cl = repo.changelog
326 cl = repo.changelog
327 ml = repo.manifestlog
327 ml = repo.manifestlog
328 # validate incoming csets have their manifests
328 # validate incoming csets have their manifests
329 for cset in pycompat.xrange(clstart, clend):
329 for cset in pycompat.xrange(clstart, clend):
330 mfnode = cl.changelogrevision(cset).manifest
330 mfnode = cl.changelogrevision(cset).manifest
331 mfest = ml[mfnode].readdelta()
331 mfest = ml[mfnode].readdelta()
332 # store file cgnodes we must see
332 # store file cgnodes we must see
333 for f, n in mfest.iteritems():
333 for f, n in mfest.iteritems():
334 needfiles.setdefault(f, set()).add(n)
334 needfiles.setdefault(f, set()).add(n)
335
335
336 # process the files
336 # process the files
337 repo.ui.status(_("adding file changes\n"))
337 repo.ui.status(_("adding file changes\n"))
338 newrevs, newfiles = _addchangegroupfiles(
338 newrevs, newfiles = _addchangegroupfiles(
339 repo, self, revmap, trp, efiles, needfiles)
339 repo, self, revmap, trp, efiles, needfiles)
340 revisions += newrevs
340 revisions += newrevs
341 files += newfiles
341 files += newfiles
342
342
343 deltaheads = 0
343 deltaheads = 0
344 if oldheads:
344 if oldheads:
345 heads = cl.heads()
345 heads = cl.heads()
346 deltaheads = len(heads) - len(oldheads)
346 deltaheads = len(heads) - len(oldheads)
347 for h in heads:
347 for h in heads:
348 if h not in oldheads and repo[h].closesbranch():
348 if h not in oldheads and repo[h].closesbranch():
349 deltaheads -= 1
349 deltaheads -= 1
350 htext = ""
350 htext = ""
351 if deltaheads:
351 if deltaheads:
352 htext = _(" (%+d heads)") % deltaheads
352 htext = _(" (%+d heads)") % deltaheads
353
353
354 repo.ui.status(_("added %d changesets"
354 repo.ui.status(_("added %d changesets"
355 " with %d changes to %d files%s\n")
355 " with %d changes to %d files%s\n")
356 % (changesets, revisions, files, htext))
356 % (changesets, revisions, files, htext))
357 repo.invalidatevolatilesets()
357 repo.invalidatevolatilesets()
358
358
359 if changesets > 0:
359 if changesets > 0:
360 if 'node' not in tr.hookargs:
360 if 'node' not in tr.hookargs:
361 tr.hookargs['node'] = hex(cl.node(clstart))
361 tr.hookargs['node'] = hex(cl.node(clstart))
362 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
362 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
363 hookargs = dict(tr.hookargs)
363 hookargs = dict(tr.hookargs)
364 else:
364 else:
365 hookargs = dict(tr.hookargs)
365 hookargs = dict(tr.hookargs)
366 hookargs['node'] = hex(cl.node(clstart))
366 hookargs['node'] = hex(cl.node(clstart))
367 hookargs['node_last'] = hex(cl.node(clend - 1))
367 hookargs['node_last'] = hex(cl.node(clend - 1))
368 repo.hook('pretxnchangegroup',
368 repo.hook('pretxnchangegroup',
369 throw=True, **pycompat.strkwargs(hookargs))
369 throw=True, **pycompat.strkwargs(hookargs))
370
370
371 added = [cl.node(r) for r in pycompat.xrange(clstart, clend)]
371 added = [cl.node(r) for r in pycompat.xrange(clstart, clend)]
372 phaseall = None
372 phaseall = None
373 if srctype in ('push', 'serve'):
373 if srctype in ('push', 'serve'):
374 # Old servers can not push the boundary themselves.
374 # Old servers can not push the boundary themselves.
375 # New servers won't push the boundary if changeset already
375 # New servers won't push the boundary if changeset already
376 # exists locally as secret
376 # exists locally as secret
377 #
377 #
378 # We should not use added here but the list of all change in
378 # We should not use added here but the list of all change in
379 # the bundle
379 # the bundle
380 if repo.publishing():
380 if repo.publishing():
381 targetphase = phaseall = phases.public
381 targetphase = phaseall = phases.public
382 else:
382 else:
383 # closer target phase computation
383 # closer target phase computation
384
384
385 # Those changesets have been pushed from the
385 # Those changesets have been pushed from the
386 # outside, their phases are going to be pushed
386 # outside, their phases are going to be pushed
387 # alongside. Therefor `targetphase` is
387 # alongside. Therefor `targetphase` is
388 # ignored.
388 # ignored.
389 targetphase = phaseall = phases.draft
389 targetphase = phaseall = phases.draft
390 if added:
390 if added:
391 phases.registernew(repo, tr, targetphase, added)
391 phases.registernew(repo, tr, targetphase, added)
392 if phaseall is not None:
392 if phaseall is not None:
393 phases.advanceboundary(repo, tr, phaseall, cgnodes)
393 phases.advanceboundary(repo, tr, phaseall, cgnodes)
394
394
395 if changesets > 0:
395 if changesets > 0:
396
396
397 def runhooks():
397 def runhooks():
398 # These hooks run when the lock releases, not when the
398 # These hooks run when the lock releases, not when the
399 # transaction closes. So it's possible for the changelog
399 # transaction closes. So it's possible for the changelog
400 # to have changed since we last saw it.
400 # to have changed since we last saw it.
401 if clstart >= len(repo):
401 if clstart >= len(repo):
402 return
402 return
403
403
404 repo.hook("changegroup", **pycompat.strkwargs(hookargs))
404 repo.hook("changegroup", **pycompat.strkwargs(hookargs))
405
405
406 for n in added:
406 for n in added:
407 args = hookargs.copy()
407 args = hookargs.copy()
408 args['node'] = hex(n)
408 args['node'] = hex(n)
409 del args['node_last']
409 del args['node_last']
410 repo.hook("incoming", **pycompat.strkwargs(args))
410 repo.hook("incoming", **pycompat.strkwargs(args))
411
411
412 newheads = [h for h in repo.heads()
412 newheads = [h for h in repo.heads()
413 if h not in oldheads]
413 if h not in oldheads]
414 repo.ui.log("incoming",
414 repo.ui.log("incoming",
415 "%d incoming changes - new heads: %s\n",
415 "%d incoming changes - new heads: %s\n",
416 len(added),
416 len(added),
417 ', '.join([hex(c[:6]) for c in newheads]))
417 ', '.join([hex(c[:6]) for c in newheads]))
418
418
419 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
419 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
420 lambda tr: repo._afterlock(runhooks))
420 lambda tr: repo._afterlock(runhooks))
421 finally:
421 finally:
422 repo.ui.flush()
422 repo.ui.flush()
423 # never return 0 here:
423 # never return 0 here:
424 if deltaheads < 0:
424 if deltaheads < 0:
425 ret = deltaheads - 1
425 ret = deltaheads - 1
426 else:
426 else:
427 ret = deltaheads + 1
427 ret = deltaheads + 1
428 return ret
428 return ret
429
429
430 def deltaiter(self):
430 def deltaiter(self):
431 """
431 """
432 returns an iterator of the deltas in this changegroup
432 returns an iterator of the deltas in this changegroup
433
433
434 Useful for passing to the underlying storage system to be stored.
434 Useful for passing to the underlying storage system to be stored.
435 """
435 """
436 chain = None
436 chain = None
437 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
437 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
438 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags)
438 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags)
439 yield chunkdata
439 yield chunkdata
440 chain = chunkdata[0]
440 chain = chunkdata[0]
441
441
442 class cg2unpacker(cg1unpacker):
442 class cg2unpacker(cg1unpacker):
443 """Unpacker for cg2 streams.
443 """Unpacker for cg2 streams.
444
444
445 cg2 streams add support for generaldelta, so the delta header
445 cg2 streams add support for generaldelta, so the delta header
446 format is slightly different. All other features about the data
446 format is slightly different. All other features about the data
447 remain the same.
447 remain the same.
448 """
448 """
449 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
449 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
450 deltaheadersize = struct.calcsize(deltaheader)
450 deltaheadersize = struct.calcsize(deltaheader)
451 version = '02'
451 version = '02'
452
452
453 def _deltaheader(self, headertuple, prevnode):
453 def _deltaheader(self, headertuple, prevnode):
454 node, p1, p2, deltabase, cs = headertuple
454 node, p1, p2, deltabase, cs = headertuple
455 flags = 0
455 flags = 0
456 return node, p1, p2, deltabase, cs, flags
456 return node, p1, p2, deltabase, cs, flags
457
457
458 class cg3unpacker(cg2unpacker):
458 class cg3unpacker(cg2unpacker):
459 """Unpacker for cg3 streams.
459 """Unpacker for cg3 streams.
460
460
461 cg3 streams add support for exchanging treemanifests and revlog
461 cg3 streams add support for exchanging treemanifests and revlog
462 flags. It adds the revlog flags to the delta header and an empty chunk
462 flags. It adds the revlog flags to the delta header and an empty chunk
463 separating manifests and files.
463 separating manifests and files.
464 """
464 """
465 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
465 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
466 deltaheadersize = struct.calcsize(deltaheader)
466 deltaheadersize = struct.calcsize(deltaheader)
467 version = '03'
467 version = '03'
468 _grouplistcount = 2 # One list of manifests and one list of files
468 _grouplistcount = 2 # One list of manifests and one list of files
469
469
470 def _deltaheader(self, headertuple, prevnode):
470 def _deltaheader(self, headertuple, prevnode):
471 node, p1, p2, deltabase, cs, flags = headertuple
471 node, p1, p2, deltabase, cs, flags = headertuple
472 return node, p1, p2, deltabase, cs, flags
472 return node, p1, p2, deltabase, cs, flags
473
473
474 def _unpackmanifests(self, repo, revmap, trp, prog):
474 def _unpackmanifests(self, repo, revmap, trp, prog):
475 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog)
475 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog)
476 for chunkdata in iter(self.filelogheader, {}):
476 for chunkdata in iter(self.filelogheader, {}):
477 # If we get here, there are directory manifests in the changegroup
477 # If we get here, there are directory manifests in the changegroup
478 d = chunkdata["filename"]
478 d = chunkdata["filename"]
479 repo.ui.debug("adding %s revisions\n" % d)
479 repo.ui.debug("adding %s revisions\n" % d)
480 dirlog = repo.manifestlog._revlog.dirlog(d)
480 dirlog = repo.manifestlog._revlog.dirlog(d)
481 deltas = self.deltaiter()
481 deltas = self.deltaiter()
482 if not dirlog.addgroup(deltas, revmap, trp):
482 if not dirlog.addgroup(deltas, revmap, trp):
483 raise error.Abort(_("received dir revlog group is empty"))
483 raise error.Abort(_("received dir revlog group is empty"))
484
484
485 class headerlessfixup(object):
485 class headerlessfixup(object):
486 def __init__(self, fh, h):
486 def __init__(self, fh, h):
487 self._h = h
487 self._h = h
488 self._fh = fh
488 self._fh = fh
489 def read(self, n):
489 def read(self, n):
490 if self._h:
490 if self._h:
491 d, self._h = self._h[:n], self._h[n:]
491 d, self._h = self._h[:n], self._h[n:]
492 if len(d) < n:
492 if len(d) < n:
493 d += readexactly(self._fh, n - len(d))
493 d += readexactly(self._fh, n - len(d))
494 return d
494 return d
495 return readexactly(self._fh, n)
495 return readexactly(self._fh, n)
496
496
497 def ellipsisdata(packer, rev, revlog_, p1, p2, data, linknode):
497 def ellipsisdata(packer, rev, revlog_, p1, p2, data, linknode):
498 n = revlog_.node(rev)
498 n = revlog_.node(rev)
499 p1n, p2n = revlog_.node(p1), revlog_.node(p2)
499 p1n, p2n = revlog_.node(p1), revlog_.node(p2)
500 flags = revlog_.flags(rev)
500 flags = revlog_.flags(rev)
501 flags |= revlog.REVIDX_ELLIPSIS
501 flags |= revlog.REVIDX_ELLIPSIS
502 meta = packer.builddeltaheader(
502 meta = packer.builddeltaheader(
503 n, p1n, p2n, nullid, linknode, flags)
503 n, p1n, p2n, nullid, linknode, flags)
504 # TODO: try and actually send deltas for ellipsis data blocks
504 # TODO: try and actually send deltas for ellipsis data blocks
505 diffheader = mdiff.trivialdiffheader(len(data))
505 diffheader = mdiff.trivialdiffheader(len(data))
506 l = len(meta) + len(diffheader) + len(data)
506 l = len(meta) + len(diffheader) + len(data)
507 return ''.join((chunkheader(l),
507 return ''.join((chunkheader(l),
508 meta,
508 meta,
509 diffheader,
509 diffheader,
510 data))
510 data))
511
511
512 class cg1packer(object):
512 class cg1packer(object):
513 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
513 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
514 version = '01'
514 version = '01'
515 def __init__(self, repo, filematcher, bundlecaps=None):
515 def __init__(self, repo, filematcher, bundlecaps=None):
516 """Given a source repo, construct a bundler.
516 """Given a source repo, construct a bundler.
517
517
518 filematcher is a matcher that matches on files to include in the
518 filematcher is a matcher that matches on files to include in the
519 changegroup. Used to facilitate sparse changegroups.
519 changegroup. Used to facilitate sparse changegroups.
520
520
521 bundlecaps is optional and can be used to specify the set of
521 bundlecaps is optional and can be used to specify the set of
522 capabilities which can be used to build the bundle. While bundlecaps is
522 capabilities which can be used to build the bundle. While bundlecaps is
523 unused in core Mercurial, extensions rely on this feature to communicate
523 unused in core Mercurial, extensions rely on this feature to communicate
524 capabilities to customize the changegroup packer.
524 capabilities to customize the changegroup packer.
525 """
525 """
526 assert filematcher
526 assert filematcher
527 self._filematcher = filematcher
527 self._filematcher = filematcher
528
528
529 # Set of capabilities we can use to build the bundle.
529 # Set of capabilities we can use to build the bundle.
530 if bundlecaps is None:
530 if bundlecaps is None:
531 bundlecaps = set()
531 bundlecaps = set()
532 self._bundlecaps = bundlecaps
532 self._bundlecaps = bundlecaps
533 # experimental config: bundle.reorder
533 # experimental config: bundle.reorder
534 reorder = repo.ui.config('bundle', 'reorder')
534 reorder = repo.ui.config('bundle', 'reorder')
535 if reorder == 'auto':
535 if reorder == 'auto':
536 reorder = None
536 reorder = None
537 else:
537 else:
538 reorder = stringutil.parsebool(reorder)
538 reorder = stringutil.parsebool(reorder)
539 self._repo = repo
539 self._repo = repo
540 self._reorder = reorder
540 self._reorder = reorder
541 if self._repo.ui.verbose and not self._repo.ui.debugflag:
541 if self._repo.ui.verbose and not self._repo.ui.debugflag:
542 self._verbosenote = self._repo.ui.note
542 self._verbosenote = self._repo.ui.note
543 else:
543 else:
544 self._verbosenote = lambda s: None
544 self._verbosenote = lambda s: None
545
545
546 def close(self):
546 def close(self):
547 # Ellipses serving mode.
547 # Ellipses serving mode.
548 getattr(self, 'clrev_to_localrev', {}).clear()
548 getattr(self, 'clrev_to_localrev', {}).clear()
549 if getattr(self, 'next_clrev_to_localrev', {}):
549 if getattr(self, 'next_clrev_to_localrev', {}):
550 self.clrev_to_localrev = self.next_clrev_to_localrev
550 self.clrev_to_localrev = self.next_clrev_to_localrev
551 del self.next_clrev_to_localrev
551 del self.next_clrev_to_localrev
552 self.changelog_done = True
552 self.changelog_done = True
553
553
554 return closechunk()
554 return closechunk()
555
555
556 def fileheader(self, fname):
556 def fileheader(self, fname):
557 return chunkheader(len(fname)) + fname
557 return chunkheader(len(fname)) + fname
558
558
559 # Extracted both for clarity and for overriding in extensions.
559 # Extracted both for clarity and for overriding in extensions.
560 def _sortgroup(self, revlog, nodelist, lookup):
560 def _sortgroup(self, revlog, nodelist, lookup):
561 """Sort nodes for change group and turn them into revnums."""
561 """Sort nodes for change group and turn them into revnums."""
562 # Ellipses serving mode.
562 # Ellipses serving mode.
563 #
563 #
564 # In a perfect world, we'd generate better ellipsis-ified graphs
564 # In a perfect world, we'd generate better ellipsis-ified graphs
565 # for non-changelog revlogs. In practice, we haven't started doing
565 # for non-changelog revlogs. In practice, we haven't started doing
566 # that yet, so the resulting DAGs for the manifestlog and filelogs
566 # that yet, so the resulting DAGs for the manifestlog and filelogs
567 # are actually full of bogus parentage on all the ellipsis
567 # are actually full of bogus parentage on all the ellipsis
568 # nodes. This has the side effect that, while the contents are
568 # nodes. This has the side effect that, while the contents are
569 # correct, the individual DAGs might be completely out of whack in
569 # correct, the individual DAGs might be completely out of whack in
570 # a case like 882681bc3166 and its ancestors (back about 10
570 # a case like 882681bc3166 and its ancestors (back about 10
571 # revisions or so) in the main hg repo.
571 # revisions or so) in the main hg repo.
572 #
572 #
573 # The one invariant we *know* holds is that the new (potentially
573 # The one invariant we *know* holds is that the new (potentially
574 # bogus) DAG shape will be valid if we order the nodes in the
574 # bogus) DAG shape will be valid if we order the nodes in the
575 # order that they're introduced in dramatis personae by the
575 # order that they're introduced in dramatis personae by the
576 # changelog, so what we do is we sort the non-changelog histories
576 # changelog, so what we do is we sort the non-changelog histories
577 # by the order in which they are used by the changelog.
577 # by the order in which they are used by the changelog.
578 if util.safehasattr(self, 'full_nodes') and self.clnode_to_rev:
578 if util.safehasattr(self, 'full_nodes') and self.clnode_to_rev:
579 key = lambda n: self.clnode_to_rev[lookup(n)]
579 key = lambda n: self.clnode_to_rev[lookup(n)]
580 return [revlog.rev(n) for n in sorted(nodelist, key=key)]
580 return [revlog.rev(n) for n in sorted(nodelist, key=key)]
581
581
582 # for generaldelta revlogs, we linearize the revs; this will both be
582 # for generaldelta revlogs, we linearize the revs; this will both be
583 # much quicker and generate a much smaller bundle
583 # much quicker and generate a much smaller bundle
584 if (revlog._generaldelta and self._reorder is None) or self._reorder:
584 if (revlog._generaldelta and self._reorder is None) or self._reorder:
585 dag = dagutil.revlogdag(revlog)
585 dag = dagutil.revlogdag(revlog)
586 return dag.linearize(set(revlog.rev(n) for n in nodelist))
586 return dag.linearize(set(revlog.rev(n) for n in nodelist))
587 else:
587 else:
588 return sorted([revlog.rev(n) for n in nodelist])
588 return sorted([revlog.rev(n) for n in nodelist])
589
589
590 def group(self, nodelist, revlog, lookup, units=None):
590 def group(self, nodelist, revlog, lookup, units=None):
591 """Calculate a delta group, yielding a sequence of changegroup chunks
591 """Calculate a delta group, yielding a sequence of changegroup chunks
592 (strings).
592 (strings).
593
593
594 Given a list of changeset revs, return a set of deltas and
594 Given a list of changeset revs, return a set of deltas and
595 metadata corresponding to nodes. The first delta is
595 metadata corresponding to nodes. The first delta is
596 first parent(nodelist[0]) -> nodelist[0], the receiver is
596 first parent(nodelist[0]) -> nodelist[0], the receiver is
597 guaranteed to have this parent as it has all history before
597 guaranteed to have this parent as it has all history before
598 these changesets. In the case firstparent is nullrev the
598 these changesets. In the case firstparent is nullrev the
599 changegroup starts with a full revision.
599 changegroup starts with a full revision.
600
600
601 If units is not None, progress detail will be generated, units specifies
601 If units is not None, progress detail will be generated, units specifies
602 the type of revlog that is touched (changelog, manifest, etc.).
602 the type of revlog that is touched (changelog, manifest, etc.).
603 """
603 """
604 # if we don't have any revisions touched by these changesets, bail
604 # if we don't have any revisions touched by these changesets, bail
605 if len(nodelist) == 0:
605 if len(nodelist) == 0:
606 yield self.close()
606 yield self.close()
607 return
607 return
608
608
609 revs = self._sortgroup(revlog, nodelist, lookup)
609 revs = self._sortgroup(revlog, nodelist, lookup)
610
610
611 # add the parent of the first rev
611 # add the parent of the first rev
612 p = revlog.parentrevs(revs[0])[0]
612 p = revlog.parentrevs(revs[0])[0]
613 revs.insert(0, p)
613 revs.insert(0, p)
614
614
615 # build deltas
615 # build deltas
616 progress = None
616 progress = None
617 if units is not None:
617 if units is not None:
618 progress = self._repo.ui.makeprogress(_('bundling'), unit=units,
618 progress = self._repo.ui.makeprogress(_('bundling'), unit=units,
619 total=(len(revs) - 1))
619 total=(len(revs) - 1))
620 for r in pycompat.xrange(len(revs) - 1):
620 for r in pycompat.xrange(len(revs) - 1):
621 if progress:
621 if progress:
622 progress.update(r + 1)
622 progress.update(r + 1)
623 prev, curr = revs[r], revs[r + 1]
623 prev, curr = revs[r], revs[r + 1]
624 linknode = lookup(revlog.node(curr))
624 linknode = lookup(revlog.node(curr))
625 for c in self.revchunk(revlog, curr, prev, linknode):
625 for c in self.revchunk(revlog, curr, prev, linknode):
626 yield c
626 yield c
627
627
628 if progress:
628 if progress:
629 progress.complete()
629 progress.complete()
630 yield self.close()
630 yield self.close()
631
631
632 # filter any nodes that claim to be part of the known set
632 # filter any nodes that claim to be part of the known set
633 def prune(self, revlog, missing, commonrevs):
633 def prune(self, revlog, missing, commonrevs):
634 # TODO this violates storage abstraction for manifests.
634 # TODO this violates storage abstraction for manifests.
635 if isinstance(revlog, manifest.manifestrevlog):
635 if isinstance(revlog, manifest.manifestrevlog):
636 if not self._filematcher.visitdir(revlog._dir[:-1] or '.'):
636 if not self._filematcher.visitdir(revlog._dir[:-1] or '.'):
637 return []
637 return []
638
638
639 rr, rl = revlog.rev, revlog.linkrev
639 rr, rl = revlog.rev, revlog.linkrev
640 return [n for n in missing if rl(rr(n)) not in commonrevs]
640 return [n for n in missing if rl(rr(n)) not in commonrevs]
641
641
642 def _packmanifests(self, dir, mfnodes, lookuplinknode):
642 def _packmanifests(self, dir, mfnodes, lookuplinknode):
643 """Pack flat manifests into a changegroup stream."""
643 """Pack flat manifests into a changegroup stream."""
644 assert not dir
644 assert not dir
645 for chunk in self.group(mfnodes, self._repo.manifestlog._revlog,
645 for chunk in self.group(mfnodes, self._repo.manifestlog._revlog,
646 lookuplinknode, units=_('manifests')):
646 lookuplinknode, units=_('manifests')):
647 yield chunk
647 yield chunk
648
648
649 def _manifestsdone(self):
649 def _manifestsdone(self):
650 return ''
650 return ''
651
651
652 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
652 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
653 '''yield a sequence of changegroup chunks (strings)'''
653 '''yield a sequence of changegroup chunks (strings)'''
654 repo = self._repo
654 repo = self._repo
655 cl = repo.changelog
655 cl = repo.changelog
656
656
657 clrevorder = {}
657 clrevorder = {}
658 mfs = {} # needed manifests
658 mfs = {} # needed manifests
659 fnodes = {} # needed file nodes
659 fnodes = {} # needed file nodes
660 changedfiles = set()
660 changedfiles = set()
661
661
662 # Callback for the changelog, used to collect changed files and manifest
662 # Callback for the changelog, used to collect changed files and manifest
663 # nodes.
663 # nodes.
664 # Returns the linkrev node (identity in the changelog case).
664 # Returns the linkrev node (identity in the changelog case).
665 def lookupcl(x):
665 def lookupcl(x):
666 c = cl.read(x)
666 c = cl.read(x)
667 clrevorder[x] = len(clrevorder)
667 clrevorder[x] = len(clrevorder)
668 n = c[0]
668 n = c[0]
669 # record the first changeset introducing this manifest version
669 # record the first changeset introducing this manifest version
670 mfs.setdefault(n, x)
670 mfs.setdefault(n, x)
671 # Record a complete list of potentially-changed files in
671 # Record a complete list of potentially-changed files in
672 # this manifest.
672 # this manifest.
673 changedfiles.update(c[3])
673 changedfiles.update(c[3])
674 return x
674 return x
675
675
676 self._verbosenote(_('uncompressed size of bundle content:\n'))
676 self._verbosenote(_('uncompressed size of bundle content:\n'))
677 size = 0
677 size = 0
678 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
678 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
679 size += len(chunk)
679 size += len(chunk)
680 yield chunk
680 yield chunk
681 self._verbosenote(_('%8.i (changelog)\n') % size)
681 self._verbosenote(_('%8.i (changelog)\n') % size)
682
682
683 # We need to make sure that the linkrev in the changegroup refers to
683 # We need to make sure that the linkrev in the changegroup refers to
684 # the first changeset that introduced the manifest or file revision.
684 # the first changeset that introduced the manifest or file revision.
685 # The fastpath is usually safer than the slowpath, because the filelogs
685 # The fastpath is usually safer than the slowpath, because the filelogs
686 # are walked in revlog order.
686 # are walked in revlog order.
687 #
687 #
688 # When taking the slowpath with reorder=None and the manifest revlog
688 # When taking the slowpath with reorder=None and the manifest revlog
689 # uses generaldelta, the manifest may be walked in the "wrong" order.
689 # uses generaldelta, the manifest may be walked in the "wrong" order.
690 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
690 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
691 # cc0ff93d0c0c).
691 # cc0ff93d0c0c).
692 #
692 #
693 # When taking the fastpath, we are only vulnerable to reordering
693 # When taking the fastpath, we are only vulnerable to reordering
694 # of the changelog itself. The changelog never uses generaldelta, so
694 # of the changelog itself. The changelog never uses generaldelta, so
695 # it is only reordered when reorder=True. To handle this case, we
695 # it is only reordered when reorder=True. To handle this case, we
696 # simply take the slowpath, which already has the 'clrevorder' logic.
696 # simply take the slowpath, which already has the 'clrevorder' logic.
697 # This was also fixed in cc0ff93d0c0c.
697 # This was also fixed in cc0ff93d0c0c.
698 fastpathlinkrev = fastpathlinkrev and not self._reorder
698 fastpathlinkrev = fastpathlinkrev and not self._reorder
699 # Treemanifests don't work correctly with fastpathlinkrev
699 # Treemanifests don't work correctly with fastpathlinkrev
700 # either, because we don't discover which directory nodes to
700 # either, because we don't discover which directory nodes to
701 # send along with files. This could probably be fixed.
701 # send along with files. This could probably be fixed.
702 fastpathlinkrev = fastpathlinkrev and (
702 fastpathlinkrev = fastpathlinkrev and (
703 'treemanifest' not in repo.requirements)
703 'treemanifest' not in repo.requirements)
704
704
705 for chunk in self.generatemanifests(commonrevs, clrevorder,
705 for chunk in self.generatemanifests(commonrevs, clrevorder,
706 fastpathlinkrev, mfs, fnodes, source):
706 fastpathlinkrev, mfs, fnodes, source):
707 yield chunk
707 yield chunk
708 mfs.clear()
708 mfs.clear()
709 clrevs = set(cl.rev(x) for x in clnodes)
709 clrevs = set(cl.rev(x) for x in clnodes)
710
710
711 if not fastpathlinkrev:
711 if not fastpathlinkrev:
712 def linknodes(unused, fname):
712 def linknodes(unused, fname):
713 return fnodes.get(fname, {})
713 return fnodes.get(fname, {})
714 else:
714 else:
715 cln = cl.node
715 cln = cl.node
716 def linknodes(filerevlog, fname):
716 def linknodes(filerevlog, fname):
717 llr = filerevlog.linkrev
717 llr = filerevlog.linkrev
718 fln = filerevlog.node
718 fln = filerevlog.node
719 revs = ((r, llr(r)) for r in filerevlog)
719 revs = ((r, llr(r)) for r in filerevlog)
720 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
720 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
721
721
722 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
722 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
723 source):
723 source):
724 yield chunk
724 yield chunk
725
725
726 yield self.close()
726 yield self.close()
727
727
728 if clnodes:
728 if clnodes:
729 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
729 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
730
730
731 def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev, mfs,
731 def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev, mfs,
732 fnodes, source):
732 fnodes, source):
733 """Returns an iterator of changegroup chunks containing manifests.
733 """Returns an iterator of changegroup chunks containing manifests.
734
734
735 `source` is unused here, but is used by extensions like remotefilelog to
735 `source` is unused here, but is used by extensions like remotefilelog to
736 change what is sent based in pulls vs pushes, etc.
736 change what is sent based in pulls vs pushes, etc.
737 """
737 """
738 repo = self._repo
738 repo = self._repo
739 mfl = repo.manifestlog
739 mfl = repo.manifestlog
740 dirlog = mfl._revlog.dirlog
740 dirlog = mfl._revlog.dirlog
741 tmfnodes = {'': mfs}
741 tmfnodes = {'': mfs}
742
742
743 # Callback for the manifest, used to collect linkrevs for filelog
743 # Callback for the manifest, used to collect linkrevs for filelog
744 # revisions.
744 # revisions.
745 # Returns the linkrev node (collected in lookupcl).
745 # Returns the linkrev node (collected in lookupcl).
746 def makelookupmflinknode(dir, nodes):
746 def makelookupmflinknode(dir, nodes):
747 if fastpathlinkrev:
747 if fastpathlinkrev:
748 assert not dir
748 assert not dir
749 return mfs.__getitem__
749 return mfs.__getitem__
750
750
751 def lookupmflinknode(x):
751 def lookupmflinknode(x):
752 """Callback for looking up the linknode for manifests.
752 """Callback for looking up the linknode for manifests.
753
753
754 Returns the linkrev node for the specified manifest.
754 Returns the linkrev node for the specified manifest.
755
755
756 SIDE EFFECT:
756 SIDE EFFECT:
757
757
758 1) fclnodes gets populated with the list of relevant
758 1) fclnodes gets populated with the list of relevant
759 file nodes if we're not using fastpathlinkrev
759 file nodes if we're not using fastpathlinkrev
760 2) When treemanifests are in use, collects treemanifest nodes
760 2) When treemanifests are in use, collects treemanifest nodes
761 to send
761 to send
762
762
763 Note that this means manifests must be completely sent to
763 Note that this means manifests must be completely sent to
764 the client before you can trust the list of files and
764 the client before you can trust the list of files and
765 treemanifests to send.
765 treemanifests to send.
766 """
766 """
767 clnode = nodes[x]
767 clnode = nodes[x]
768 mdata = mfl.get(dir, x).readfast(shallow=True)
768 mdata = mfl.get(dir, x).readfast(shallow=True)
769 for p, n, fl in mdata.iterentries():
769 for p, n, fl in mdata.iterentries():
770 if fl == 't': # subdirectory manifest
770 if fl == 't': # subdirectory manifest
771 subdir = dir + p + '/'
771 subdir = dir + p + '/'
772 tmfclnodes = tmfnodes.setdefault(subdir, {})
772 tmfclnodes = tmfnodes.setdefault(subdir, {})
773 tmfclnode = tmfclnodes.setdefault(n, clnode)
773 tmfclnode = tmfclnodes.setdefault(n, clnode)
774 if clrevorder[clnode] < clrevorder[tmfclnode]:
774 if clrevorder[clnode] < clrevorder[tmfclnode]:
775 tmfclnodes[n] = clnode
775 tmfclnodes[n] = clnode
776 else:
776 else:
777 f = dir + p
777 f = dir + p
778 fclnodes = fnodes.setdefault(f, {})
778 fclnodes = fnodes.setdefault(f, {})
779 fclnode = fclnodes.setdefault(n, clnode)
779 fclnode = fclnodes.setdefault(n, clnode)
780 if clrevorder[clnode] < clrevorder[fclnode]:
780 if clrevorder[clnode] < clrevorder[fclnode]:
781 fclnodes[n] = clnode
781 fclnodes[n] = clnode
782 return clnode
782 return clnode
783 return lookupmflinknode
783 return lookupmflinknode
784
784
785 size = 0
785 size = 0
786 while tmfnodes:
786 while tmfnodes:
787 dir, nodes = tmfnodes.popitem()
787 dir, nodes = tmfnodes.popitem()
788 prunednodes = self.prune(dirlog(dir), nodes, commonrevs)
788 prunednodes = self.prune(dirlog(dir), nodes, commonrevs)
789 if not dir or prunednodes:
789 if not dir or prunednodes:
790 for x in self._packmanifests(dir, prunednodes,
790 for x in self._packmanifests(dir, prunednodes,
791 makelookupmflinknode(dir, nodes)):
791 makelookupmflinknode(dir, nodes)):
792 size += len(x)
792 size += len(x)
793 yield x
793 yield x
794 self._verbosenote(_('%8.i (manifests)\n') % size)
794 self._verbosenote(_('%8.i (manifests)\n') % size)
795 yield self._manifestsdone()
795 yield self._manifestsdone()
796
796
797 # The 'source' parameter is useful for extensions
797 # The 'source' parameter is useful for extensions
798 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
798 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
799 changedfiles = list(filter(self._filematcher, changedfiles))
800
801 if getattr(self, 'is_shallow', False):
802 # See comment in generate() for why this sadness is a thing.
803 mfdicts = self._mfdicts
804 del self._mfdicts
805 # In a shallow clone, the linknodes callback needs to also include
806 # those file nodes that are in the manifests we sent but weren't
807 # introduced by those manifests.
808 commonctxs = [self._repo[c] for c in commonrevs]
809 oldlinknodes = linknodes
810 clrev = self._repo.changelog.rev
811
812 # Defining this function has a side-effect of overriding the
813 # function of the same name that was passed in as an argument.
814 # TODO have caller pass in appropriate function.
815 def linknodes(flog, fname):
816 for c in commonctxs:
817 try:
818 fnode = c.filenode(fname)
819 self.clrev_to_localrev[c.rev()] = flog.rev(fnode)
820 except error.ManifestLookupError:
821 pass
822 links = oldlinknodes(flog, fname)
823 if len(links) != len(mfdicts):
824 for mf, lr in mfdicts:
825 fnode = mf.get(fname, None)
826 if fnode in links:
827 links[fnode] = min(links[fnode], lr, key=clrev)
828 elif fnode:
829 links[fnode] = lr
830 return links
831
832 return self._generatefiles(changedfiles, linknodes, commonrevs, source)
833
834 def _generatefiles(self, changedfiles, linknodes, commonrevs, source):
799 repo = self._repo
835 repo = self._repo
800 progress = repo.ui.makeprogress(_('bundling'), unit=_('files'),
836 progress = repo.ui.makeprogress(_('bundling'), unit=_('files'),
801 total=len(changedfiles))
837 total=len(changedfiles))
802 for i, fname in enumerate(sorted(changedfiles)):
838 for i, fname in enumerate(sorted(changedfiles)):
803 filerevlog = repo.file(fname)
839 filerevlog = repo.file(fname)
804 if not filerevlog:
840 if not filerevlog:
805 raise error.Abort(_("empty or missing file data for %s") %
841 raise error.Abort(_("empty or missing file data for %s") %
806 fname)
842 fname)
807
843
808 linkrevnodes = linknodes(filerevlog, fname)
844 linkrevnodes = linknodes(filerevlog, fname)
809 # Lookup for filenodes, we collected the linkrev nodes above in the
845 # Lookup for filenodes, we collected the linkrev nodes above in the
810 # fastpath case and with lookupmf in the slowpath case.
846 # fastpath case and with lookupmf in the slowpath case.
811 def lookupfilelog(x):
847 def lookupfilelog(x):
812 return linkrevnodes[x]
848 return linkrevnodes[x]
813
849
814 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
850 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
815 if filenodes:
851 if filenodes:
816 progress.update(i + 1, item=fname)
852 progress.update(i + 1, item=fname)
817 h = self.fileheader(fname)
853 h = self.fileheader(fname)
818 size = len(h)
854 size = len(h)
819 yield h
855 yield h
820 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
856 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
821 size += len(chunk)
857 size += len(chunk)
822 yield chunk
858 yield chunk
823 self._verbosenote(_('%8.i %s\n') % (size, fname))
859 self._verbosenote(_('%8.i %s\n') % (size, fname))
824 progress.complete()
860 progress.complete()
825
861
826 def deltaparent(self, revlog, rev, p1, p2, prev):
862 def deltaparent(self, revlog, rev, p1, p2, prev):
827 if not revlog.candelta(prev, rev):
863 if not revlog.candelta(prev, rev):
828 raise error.ProgrammingError('cg1 should not be used in this case')
864 raise error.ProgrammingError('cg1 should not be used in this case')
829 return prev
865 return prev
830
866
831 def revchunk(self, revlog, rev, prev, linknode):
867 def revchunk(self, revlog, rev, prev, linknode):
832 if util.safehasattr(self, 'full_nodes'):
868 if util.safehasattr(self, 'full_nodes'):
833 fn = self._revchunknarrow
869 fn = self._revchunknarrow
834 else:
870 else:
835 fn = self._revchunknormal
871 fn = self._revchunknormal
836
872
837 return fn(revlog, rev, prev, linknode)
873 return fn(revlog, rev, prev, linknode)
838
874
839 def _revchunknormal(self, revlog, rev, prev, linknode):
875 def _revchunknormal(self, revlog, rev, prev, linknode):
840 node = revlog.node(rev)
876 node = revlog.node(rev)
841 p1, p2 = revlog.parentrevs(rev)
877 p1, p2 = revlog.parentrevs(rev)
842 base = self.deltaparent(revlog, rev, p1, p2, prev)
878 base = self.deltaparent(revlog, rev, p1, p2, prev)
843
879
844 prefix = ''
880 prefix = ''
845 if revlog.iscensored(base) or revlog.iscensored(rev):
881 if revlog.iscensored(base) or revlog.iscensored(rev):
846 try:
882 try:
847 delta = revlog.revision(node, raw=True)
883 delta = revlog.revision(node, raw=True)
848 except error.CensoredNodeError as e:
884 except error.CensoredNodeError as e:
849 delta = e.tombstone
885 delta = e.tombstone
850 if base == nullrev:
886 if base == nullrev:
851 prefix = mdiff.trivialdiffheader(len(delta))
887 prefix = mdiff.trivialdiffheader(len(delta))
852 else:
888 else:
853 baselen = revlog.rawsize(base)
889 baselen = revlog.rawsize(base)
854 prefix = mdiff.replacediffheader(baselen, len(delta))
890 prefix = mdiff.replacediffheader(baselen, len(delta))
855 elif base == nullrev:
891 elif base == nullrev:
856 delta = revlog.revision(node, raw=True)
892 delta = revlog.revision(node, raw=True)
857 prefix = mdiff.trivialdiffheader(len(delta))
893 prefix = mdiff.trivialdiffheader(len(delta))
858 else:
894 else:
859 delta = revlog.revdiff(base, rev)
895 delta = revlog.revdiff(base, rev)
860 p1n, p2n = revlog.parents(node)
896 p1n, p2n = revlog.parents(node)
861 basenode = revlog.node(base)
897 basenode = revlog.node(base)
862 flags = revlog.flags(rev)
898 flags = revlog.flags(rev)
863 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode, flags)
899 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode, flags)
864 meta += prefix
900 meta += prefix
865 l = len(meta) + len(delta)
901 l = len(meta) + len(delta)
866 yield chunkheader(l)
902 yield chunkheader(l)
867 yield meta
903 yield meta
868 yield delta
904 yield delta
869
905
870 def _revchunknarrow(self, revlog, rev, prev, linknode):
906 def _revchunknarrow(self, revlog, rev, prev, linknode):
871 # build up some mapping information that's useful later. See
907 # build up some mapping information that's useful later. See
872 # the local() nested function below.
908 # the local() nested function below.
873 if not self.changelog_done:
909 if not self.changelog_done:
874 self.clnode_to_rev[linknode] = rev
910 self.clnode_to_rev[linknode] = rev
875 linkrev = rev
911 linkrev = rev
876 self.clrev_to_localrev[linkrev] = rev
912 self.clrev_to_localrev[linkrev] = rev
877 else:
913 else:
878 linkrev = self.clnode_to_rev[linknode]
914 linkrev = self.clnode_to_rev[linknode]
879 self.clrev_to_localrev[linkrev] = rev
915 self.clrev_to_localrev[linkrev] = rev
880
916
881 # This is a node to send in full, because the changeset it
917 # This is a node to send in full, because the changeset it
882 # corresponds to was a full changeset.
918 # corresponds to was a full changeset.
883 if linknode in self.full_nodes:
919 if linknode in self.full_nodes:
884 for x in self._revchunknormal(revlog, rev, prev, linknode):
920 for x in self._revchunknormal(revlog, rev, prev, linknode):
885 yield x
921 yield x
886 return
922 return
887
923
888 # At this point, a node can either be one we should skip or an
924 # At this point, a node can either be one we should skip or an
889 # ellipsis. If it's not an ellipsis, bail immediately.
925 # ellipsis. If it's not an ellipsis, bail immediately.
890 if linkrev not in self.precomputed_ellipsis:
926 if linkrev not in self.precomputed_ellipsis:
891 return
927 return
892
928
893 linkparents = self.precomputed_ellipsis[linkrev]
929 linkparents = self.precomputed_ellipsis[linkrev]
894 def local(clrev):
930 def local(clrev):
895 """Turn a changelog revnum into a local revnum.
931 """Turn a changelog revnum into a local revnum.
896
932
897 The ellipsis dag is stored as revnums on the changelog,
933 The ellipsis dag is stored as revnums on the changelog,
898 but when we're producing ellipsis entries for
934 but when we're producing ellipsis entries for
899 non-changelog revlogs, we need to turn those numbers into
935 non-changelog revlogs, we need to turn those numbers into
900 something local. This does that for us, and during the
936 something local. This does that for us, and during the
901 changelog sending phase will also expand the stored
937 changelog sending phase will also expand the stored
902 mappings as needed.
938 mappings as needed.
903 """
939 """
904 if clrev == nullrev:
940 if clrev == nullrev:
905 return nullrev
941 return nullrev
906
942
907 if not self.changelog_done:
943 if not self.changelog_done:
908 # If we're doing the changelog, it's possible that we
944 # If we're doing the changelog, it's possible that we
909 # have a parent that is already on the client, and we
945 # have a parent that is already on the client, and we
910 # need to store some extra mapping information so that
946 # need to store some extra mapping information so that
911 # our contained ellipsis nodes will be able to resolve
947 # our contained ellipsis nodes will be able to resolve
912 # their parents.
948 # their parents.
913 if clrev not in self.clrev_to_localrev:
949 if clrev not in self.clrev_to_localrev:
914 clnode = revlog.node(clrev)
950 clnode = revlog.node(clrev)
915 self.clnode_to_rev[clnode] = clrev
951 self.clnode_to_rev[clnode] = clrev
916 return clrev
952 return clrev
917
953
918 # Walk the ellipsis-ized changelog breadth-first looking for a
954 # Walk the ellipsis-ized changelog breadth-first looking for a
919 # change that has been linked from the current revlog.
955 # change that has been linked from the current revlog.
920 #
956 #
921 # For a flat manifest revlog only a single step should be necessary
957 # For a flat manifest revlog only a single step should be necessary
922 # as all relevant changelog entries are relevant to the flat
958 # as all relevant changelog entries are relevant to the flat
923 # manifest.
959 # manifest.
924 #
960 #
925 # For a filelog or tree manifest dirlog however not every changelog
961 # For a filelog or tree manifest dirlog however not every changelog
926 # entry will have been relevant, so we need to skip some changelog
962 # entry will have been relevant, so we need to skip some changelog
927 # nodes even after ellipsis-izing.
963 # nodes even after ellipsis-izing.
928 walk = [clrev]
964 walk = [clrev]
929 while walk:
965 while walk:
930 p = walk[0]
966 p = walk[0]
931 walk = walk[1:]
967 walk = walk[1:]
932 if p in self.clrev_to_localrev:
968 if p in self.clrev_to_localrev:
933 return self.clrev_to_localrev[p]
969 return self.clrev_to_localrev[p]
934 elif p in self.full_nodes:
970 elif p in self.full_nodes:
935 walk.extend([pp for pp in self._repo.changelog.parentrevs(p)
971 walk.extend([pp for pp in self._repo.changelog.parentrevs(p)
936 if pp != nullrev])
972 if pp != nullrev])
937 elif p in self.precomputed_ellipsis:
973 elif p in self.precomputed_ellipsis:
938 walk.extend([pp for pp in self.precomputed_ellipsis[p]
974 walk.extend([pp for pp in self.precomputed_ellipsis[p]
939 if pp != nullrev])
975 if pp != nullrev])
940 else:
976 else:
941 # In this case, we've got an ellipsis with parents
977 # In this case, we've got an ellipsis with parents
942 # outside the current bundle (likely an
978 # outside the current bundle (likely an
943 # incremental pull). We "know" that we can use the
979 # incremental pull). We "know" that we can use the
944 # value of this same revlog at whatever revision
980 # value of this same revlog at whatever revision
945 # is pointed to by linknode. "Know" is in scare
981 # is pointed to by linknode. "Know" is in scare
946 # quotes because I haven't done enough examination
982 # quotes because I haven't done enough examination
947 # of edge cases to convince myself this is really
983 # of edge cases to convince myself this is really
948 # a fact - it works for all the (admittedly
984 # a fact - it works for all the (admittedly
949 # thorough) cases in our testsuite, but I would be
985 # thorough) cases in our testsuite, but I would be
950 # somewhat unsurprised to find a case in the wild
986 # somewhat unsurprised to find a case in the wild
951 # where this breaks down a bit. That said, I don't
987 # where this breaks down a bit. That said, I don't
952 # know if it would hurt anything.
988 # know if it would hurt anything.
953 for i in pycompat.xrange(rev, 0, -1):
989 for i in pycompat.xrange(rev, 0, -1):
954 if revlog.linkrev(i) == clrev:
990 if revlog.linkrev(i) == clrev:
955 return i
991 return i
956 # We failed to resolve a parent for this node, so
992 # We failed to resolve a parent for this node, so
957 # we crash the changegroup construction.
993 # we crash the changegroup construction.
958 raise error.Abort(
994 raise error.Abort(
959 'unable to resolve parent while packing %r %r'
995 'unable to resolve parent while packing %r %r'
960 ' for changeset %r' % (revlog.indexfile, rev, clrev))
996 ' for changeset %r' % (revlog.indexfile, rev, clrev))
961
997
962 return nullrev
998 return nullrev
963
999
964 if not linkparents or (
1000 if not linkparents or (
965 revlog.parentrevs(rev) == (nullrev, nullrev)):
1001 revlog.parentrevs(rev) == (nullrev, nullrev)):
966 p1, p2 = nullrev, nullrev
1002 p1, p2 = nullrev, nullrev
967 elif len(linkparents) == 1:
1003 elif len(linkparents) == 1:
968 p1, = sorted(local(p) for p in linkparents)
1004 p1, = sorted(local(p) for p in linkparents)
969 p2 = nullrev
1005 p2 = nullrev
970 else:
1006 else:
971 p1, p2 = sorted(local(p) for p in linkparents)
1007 p1, p2 = sorted(local(p) for p in linkparents)
972 n = revlog.node(rev)
1008 n = revlog.node(rev)
973
1009
974 yield ellipsisdata(
1010 yield ellipsisdata(
975 self, rev, revlog, p1, p2, revlog.revision(n), linknode)
1011 self, rev, revlog, p1, p2, revlog.revision(n), linknode)
976
1012
977 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
1013 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
978 # do nothing with basenode, it is implicitly the previous one in HG10
1014 # do nothing with basenode, it is implicitly the previous one in HG10
979 # do nothing with flags, it is implicitly 0 for cg1 and cg2
1015 # do nothing with flags, it is implicitly 0 for cg1 and cg2
980 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
1016 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
981
1017
982 class cg2packer(cg1packer):
1018 class cg2packer(cg1packer):
983 version = '02'
1019 version = '02'
984 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
1020 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
985
1021
986 def __init__(self, repo, filematcher, bundlecaps=None):
1022 def __init__(self, repo, filematcher, bundlecaps=None):
987 super(cg2packer, self).__init__(repo, filematcher,
1023 super(cg2packer, self).__init__(repo, filematcher,
988 bundlecaps=bundlecaps)
1024 bundlecaps=bundlecaps)
989
1025
990 if self._reorder is None:
1026 if self._reorder is None:
991 # Since generaldelta is directly supported by cg2, reordering
1027 # Since generaldelta is directly supported by cg2, reordering
992 # generally doesn't help, so we disable it by default (treating
1028 # generally doesn't help, so we disable it by default (treating
993 # bundle.reorder=auto just like bundle.reorder=False).
1029 # bundle.reorder=auto just like bundle.reorder=False).
994 self._reorder = False
1030 self._reorder = False
995
1031
996 def deltaparent(self, revlog, rev, p1, p2, prev):
1032 def deltaparent(self, revlog, rev, p1, p2, prev):
997 # Narrow ellipses mode.
1033 # Narrow ellipses mode.
998 if util.safehasattr(self, 'full_nodes'):
1034 if util.safehasattr(self, 'full_nodes'):
999 # TODO: send better deltas when in narrow mode.
1035 # TODO: send better deltas when in narrow mode.
1000 #
1036 #
1001 # changegroup.group() loops over revisions to send,
1037 # changegroup.group() loops over revisions to send,
1002 # including revisions we'll skip. What this means is that
1038 # including revisions we'll skip. What this means is that
1003 # `prev` will be a potentially useless delta base for all
1039 # `prev` will be a potentially useless delta base for all
1004 # ellipsis nodes, as the client likely won't have it. In
1040 # ellipsis nodes, as the client likely won't have it. In
1005 # the future we should do bookkeeping about which nodes
1041 # the future we should do bookkeeping about which nodes
1006 # have been sent to the client, and try to be
1042 # have been sent to the client, and try to be
1007 # significantly smarter about delta bases. This is
1043 # significantly smarter about delta bases. This is
1008 # slightly tricky because this same code has to work for
1044 # slightly tricky because this same code has to work for
1009 # all revlogs, and we don't have the linkrev/linknode here.
1045 # all revlogs, and we don't have the linkrev/linknode here.
1010 return p1
1046 return p1
1011
1047
1012 dp = revlog.deltaparent(rev)
1048 dp = revlog.deltaparent(rev)
1013 if dp == nullrev and revlog.storedeltachains:
1049 if dp == nullrev and revlog.storedeltachains:
1014 # Avoid sending full revisions when delta parent is null. Pick prev
1050 # Avoid sending full revisions when delta parent is null. Pick prev
1015 # in that case. It's tempting to pick p1 in this case, as p1 will
1051 # in that case. It's tempting to pick p1 in this case, as p1 will
1016 # be smaller in the common case. However, computing a delta against
1052 # be smaller in the common case. However, computing a delta against
1017 # p1 may require resolving the raw text of p1, which could be
1053 # p1 may require resolving the raw text of p1, which could be
1018 # expensive. The revlog caches should have prev cached, meaning
1054 # expensive. The revlog caches should have prev cached, meaning
1019 # less CPU for changegroup generation. There is likely room to add
1055 # less CPU for changegroup generation. There is likely room to add
1020 # a flag and/or config option to control this behavior.
1056 # a flag and/or config option to control this behavior.
1021 base = prev
1057 base = prev
1022 elif dp == nullrev:
1058 elif dp == nullrev:
1023 # revlog is configured to use full snapshot for a reason,
1059 # revlog is configured to use full snapshot for a reason,
1024 # stick to full snapshot.
1060 # stick to full snapshot.
1025 base = nullrev
1061 base = nullrev
1026 elif dp not in (p1, p2, prev):
1062 elif dp not in (p1, p2, prev):
1027 # Pick prev when we can't be sure remote has the base revision.
1063 # Pick prev when we can't be sure remote has the base revision.
1028 return prev
1064 return prev
1029 else:
1065 else:
1030 base = dp
1066 base = dp
1031 if base != nullrev and not revlog.candelta(base, rev):
1067 if base != nullrev and not revlog.candelta(base, rev):
1032 base = nullrev
1068 base = nullrev
1033 return base
1069 return base
1034
1070
1035 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
1071 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
1036 # Do nothing with flags, it is implicitly 0 in cg1 and cg2
1072 # Do nothing with flags, it is implicitly 0 in cg1 and cg2
1037 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
1073 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
1038
1074
1039 class cg3packer(cg2packer):
1075 class cg3packer(cg2packer):
1040 version = '03'
1076 version = '03'
1041 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
1077 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
1042
1078
1043 def _packmanifests(self, dir, mfnodes, lookuplinknode):
1079 def _packmanifests(self, dir, mfnodes, lookuplinknode):
1044 if dir:
1080 if dir:
1045 yield self.fileheader(dir)
1081 yield self.fileheader(dir)
1046
1082
1047 dirlog = self._repo.manifestlog._revlog.dirlog(dir)
1083 dirlog = self._repo.manifestlog._revlog.dirlog(dir)
1048 for chunk in self.group(mfnodes, dirlog, lookuplinknode,
1084 for chunk in self.group(mfnodes, dirlog, lookuplinknode,
1049 units=_('manifests')):
1085 units=_('manifests')):
1050 yield chunk
1086 yield chunk
1051
1087
1052 def _manifestsdone(self):
1088 def _manifestsdone(self):
1053 return self.close()
1089 return self.close()
1054
1090
1055 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
1091 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
1056 return struct.pack(
1092 return struct.pack(
1057 self.deltaheader, node, p1n, p2n, basenode, linknode, flags)
1093 self.deltaheader, node, p1n, p2n, basenode, linknode, flags)
1058
1094
1059 _packermap = {'01': (cg1packer, cg1unpacker),
1095 _packermap = {'01': (cg1packer, cg1unpacker),
1060 # cg2 adds support for exchanging generaldelta
1096 # cg2 adds support for exchanging generaldelta
1061 '02': (cg2packer, cg2unpacker),
1097 '02': (cg2packer, cg2unpacker),
1062 # cg3 adds support for exchanging revlog flags and treemanifests
1098 # cg3 adds support for exchanging revlog flags and treemanifests
1063 '03': (cg3packer, cg3unpacker),
1099 '03': (cg3packer, cg3unpacker),
1064 }
1100 }
1065
1101
1066 def allsupportedversions(repo):
1102 def allsupportedversions(repo):
1067 versions = set(_packermap.keys())
1103 versions = set(_packermap.keys())
1068 if not (repo.ui.configbool('experimental', 'changegroup3') or
1104 if not (repo.ui.configbool('experimental', 'changegroup3') or
1069 repo.ui.configbool('experimental', 'treemanifest') or
1105 repo.ui.configbool('experimental', 'treemanifest') or
1070 'treemanifest' in repo.requirements):
1106 'treemanifest' in repo.requirements):
1071 versions.discard('03')
1107 versions.discard('03')
1072 return versions
1108 return versions
1073
1109
1074 # Changegroup versions that can be applied to the repo
1110 # Changegroup versions that can be applied to the repo
1075 def supportedincomingversions(repo):
1111 def supportedincomingversions(repo):
1076 return allsupportedversions(repo)
1112 return allsupportedversions(repo)
1077
1113
1078 # Changegroup versions that can be created from the repo
1114 # Changegroup versions that can be created from the repo
1079 def supportedoutgoingversions(repo):
1115 def supportedoutgoingversions(repo):
1080 versions = allsupportedversions(repo)
1116 versions = allsupportedversions(repo)
1081 if 'treemanifest' in repo.requirements:
1117 if 'treemanifest' in repo.requirements:
1082 # Versions 01 and 02 support only flat manifests and it's just too
1118 # Versions 01 and 02 support only flat manifests and it's just too
1083 # expensive to convert between the flat manifest and tree manifest on
1119 # expensive to convert between the flat manifest and tree manifest on
1084 # the fly. Since tree manifests are hashed differently, all of history
1120 # the fly. Since tree manifests are hashed differently, all of history
1085 # would have to be converted. Instead, we simply don't even pretend to
1121 # would have to be converted. Instead, we simply don't even pretend to
1086 # support versions 01 and 02.
1122 # support versions 01 and 02.
1087 versions.discard('01')
1123 versions.discard('01')
1088 versions.discard('02')
1124 versions.discard('02')
1089 if repository.NARROW_REQUIREMENT in repo.requirements:
1125 if repository.NARROW_REQUIREMENT in repo.requirements:
1090 # Versions 01 and 02 don't support revlog flags, and we need to
1126 # Versions 01 and 02 don't support revlog flags, and we need to
1091 # support that for stripping and unbundling to work.
1127 # support that for stripping and unbundling to work.
1092 versions.discard('01')
1128 versions.discard('01')
1093 versions.discard('02')
1129 versions.discard('02')
1094 if LFS_REQUIREMENT in repo.requirements:
1130 if LFS_REQUIREMENT in repo.requirements:
1095 # Versions 01 and 02 don't support revlog flags, and we need to
1131 # Versions 01 and 02 don't support revlog flags, and we need to
1096 # mark LFS entries with REVIDX_EXTSTORED.
1132 # mark LFS entries with REVIDX_EXTSTORED.
1097 versions.discard('01')
1133 versions.discard('01')
1098 versions.discard('02')
1134 versions.discard('02')
1099
1135
1100 return versions
1136 return versions
1101
1137
1102 def localversion(repo):
1138 def localversion(repo):
1103 # Finds the best version to use for bundles that are meant to be used
1139 # Finds the best version to use for bundles that are meant to be used
1104 # locally, such as those from strip and shelve, and temporary bundles.
1140 # locally, such as those from strip and shelve, and temporary bundles.
1105 return max(supportedoutgoingversions(repo))
1141 return max(supportedoutgoingversions(repo))
1106
1142
1107 def safeversion(repo):
1143 def safeversion(repo):
1108 # Finds the smallest version that it's safe to assume clients of the repo
1144 # Finds the smallest version that it's safe to assume clients of the repo
1109 # will support. For example, all hg versions that support generaldelta also
1145 # will support. For example, all hg versions that support generaldelta also
1110 # support changegroup 02.
1146 # support changegroup 02.
1111 versions = supportedoutgoingversions(repo)
1147 versions = supportedoutgoingversions(repo)
1112 if 'generaldelta' in repo.requirements:
1148 if 'generaldelta' in repo.requirements:
1113 versions.discard('01')
1149 versions.discard('01')
1114 assert versions
1150 assert versions
1115 return min(versions)
1151 return min(versions)
1116
1152
1117 def getbundler(version, repo, bundlecaps=None, filematcher=None):
1153 def getbundler(version, repo, bundlecaps=None, filematcher=None):
1118 assert version in supportedoutgoingversions(repo)
1154 assert version in supportedoutgoingversions(repo)
1119
1155
1120 if filematcher is None:
1156 if filematcher is None:
1121 filematcher = matchmod.alwaysmatcher(repo.root, '')
1157 filematcher = matchmod.alwaysmatcher(repo.root, '')
1122
1158
1123 if version == '01' and not filematcher.always():
1159 if version == '01' and not filematcher.always():
1124 raise error.ProgrammingError('version 01 changegroups do not support '
1160 raise error.ProgrammingError('version 01 changegroups do not support '
1125 'sparse file matchers')
1161 'sparse file matchers')
1126
1162
1127 # Requested files could include files not in the local store. So
1163 # Requested files could include files not in the local store. So
1128 # filter those out.
1164 # filter those out.
1129 filematcher = matchmod.intersectmatchers(repo.narrowmatch(),
1165 filematcher = matchmod.intersectmatchers(repo.narrowmatch(),
1130 filematcher)
1166 filematcher)
1131
1167
1132 return _packermap[version][0](repo, filematcher=filematcher,
1168 return _packermap[version][0](repo, filematcher=filematcher,
1133 bundlecaps=bundlecaps)
1169 bundlecaps=bundlecaps)
1134
1170
1135 def getunbundler(version, fh, alg, extras=None):
1171 def getunbundler(version, fh, alg, extras=None):
1136 return _packermap[version][1](fh, alg, extras=extras)
1172 return _packermap[version][1](fh, alg, extras=extras)
1137
1173
1138 def _changegroupinfo(repo, nodes, source):
1174 def _changegroupinfo(repo, nodes, source):
1139 if repo.ui.verbose or source == 'bundle':
1175 if repo.ui.verbose or source == 'bundle':
1140 repo.ui.status(_("%d changesets found\n") % len(nodes))
1176 repo.ui.status(_("%d changesets found\n") % len(nodes))
1141 if repo.ui.debugflag:
1177 if repo.ui.debugflag:
1142 repo.ui.debug("list of changesets:\n")
1178 repo.ui.debug("list of changesets:\n")
1143 for node in nodes:
1179 for node in nodes:
1144 repo.ui.debug("%s\n" % hex(node))
1180 repo.ui.debug("%s\n" % hex(node))
1145
1181
1146 def makechangegroup(repo, outgoing, version, source, fastpath=False,
1182 def makechangegroup(repo, outgoing, version, source, fastpath=False,
1147 bundlecaps=None):
1183 bundlecaps=None):
1148 cgstream = makestream(repo, outgoing, version, source,
1184 cgstream = makestream(repo, outgoing, version, source,
1149 fastpath=fastpath, bundlecaps=bundlecaps)
1185 fastpath=fastpath, bundlecaps=bundlecaps)
1150 return getunbundler(version, util.chunkbuffer(cgstream), None,
1186 return getunbundler(version, util.chunkbuffer(cgstream), None,
1151 {'clcount': len(outgoing.missing) })
1187 {'clcount': len(outgoing.missing) })
1152
1188
1153 def makestream(repo, outgoing, version, source, fastpath=False,
1189 def makestream(repo, outgoing, version, source, fastpath=False,
1154 bundlecaps=None, filematcher=None):
1190 bundlecaps=None, filematcher=None):
1155 bundler = getbundler(version, repo, bundlecaps=bundlecaps,
1191 bundler = getbundler(version, repo, bundlecaps=bundlecaps,
1156 filematcher=filematcher)
1192 filematcher=filematcher)
1157
1193
1158 repo = repo.unfiltered()
1194 repo = repo.unfiltered()
1159 commonrevs = outgoing.common
1195 commonrevs = outgoing.common
1160 csets = outgoing.missing
1196 csets = outgoing.missing
1161 heads = outgoing.missingheads
1197 heads = outgoing.missingheads
1162 # We go through the fast path if we get told to, or if all (unfiltered
1198 # We go through the fast path if we get told to, or if all (unfiltered
1163 # heads have been requested (since we then know there all linkrevs will
1199 # heads have been requested (since we then know there all linkrevs will
1164 # be pulled by the client).
1200 # be pulled by the client).
1165 heads.sort()
1201 heads.sort()
1166 fastpathlinkrev = fastpath or (
1202 fastpathlinkrev = fastpath or (
1167 repo.filtername is None and heads == sorted(repo.heads()))
1203 repo.filtername is None and heads == sorted(repo.heads()))
1168
1204
1169 repo.hook('preoutgoing', throw=True, source=source)
1205 repo.hook('preoutgoing', throw=True, source=source)
1170 _changegroupinfo(repo, csets, source)
1206 _changegroupinfo(repo, csets, source)
1171 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1207 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1172
1208
1173 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
1209 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
1174 revisions = 0
1210 revisions = 0
1175 files = 0
1211 files = 0
1176 progress = repo.ui.makeprogress(_('files'), unit=_('files'),
1212 progress = repo.ui.makeprogress(_('files'), unit=_('files'),
1177 total=expectedfiles)
1213 total=expectedfiles)
1178 for chunkdata in iter(source.filelogheader, {}):
1214 for chunkdata in iter(source.filelogheader, {}):
1179 files += 1
1215 files += 1
1180 f = chunkdata["filename"]
1216 f = chunkdata["filename"]
1181 repo.ui.debug("adding %s revisions\n" % f)
1217 repo.ui.debug("adding %s revisions\n" % f)
1182 progress.increment()
1218 progress.increment()
1183 fl = repo.file(f)
1219 fl = repo.file(f)
1184 o = len(fl)
1220 o = len(fl)
1185 try:
1221 try:
1186 deltas = source.deltaiter()
1222 deltas = source.deltaiter()
1187 if not fl.addgroup(deltas, revmap, trp):
1223 if not fl.addgroup(deltas, revmap, trp):
1188 raise error.Abort(_("received file revlog group is empty"))
1224 raise error.Abort(_("received file revlog group is empty"))
1189 except error.CensoredBaseError as e:
1225 except error.CensoredBaseError as e:
1190 raise error.Abort(_("received delta base is censored: %s") % e)
1226 raise error.Abort(_("received delta base is censored: %s") % e)
1191 revisions += len(fl) - o
1227 revisions += len(fl) - o
1192 if f in needfiles:
1228 if f in needfiles:
1193 needs = needfiles[f]
1229 needs = needfiles[f]
1194 for new in pycompat.xrange(o, len(fl)):
1230 for new in pycompat.xrange(o, len(fl)):
1195 n = fl.node(new)
1231 n = fl.node(new)
1196 if n in needs:
1232 if n in needs:
1197 needs.remove(n)
1233 needs.remove(n)
1198 else:
1234 else:
1199 raise error.Abort(
1235 raise error.Abort(
1200 _("received spurious file revlog entry"))
1236 _("received spurious file revlog entry"))
1201 if not needs:
1237 if not needs:
1202 del needfiles[f]
1238 del needfiles[f]
1203 progress.complete()
1239 progress.complete()
1204
1240
1205 for f, needs in needfiles.iteritems():
1241 for f, needs in needfiles.iteritems():
1206 fl = repo.file(f)
1242 fl = repo.file(f)
1207 for n in needs:
1243 for n in needs:
1208 try:
1244 try:
1209 fl.rev(n)
1245 fl.rev(n)
1210 except error.LookupError:
1246 except error.LookupError:
1211 raise error.Abort(
1247 raise error.Abort(
1212 _('missing file data for %s:%s - run hg verify') %
1248 _('missing file data for %s:%s - run hg verify') %
1213 (f, hex(n)))
1249 (f, hex(n)))
1214
1250
1215 return revisions, files
1251 return revisions, files
1216
1252
1217 def _packellipsischangegroup(repo, common, match, relevant_nodes,
1253 def _packellipsischangegroup(repo, common, match, relevant_nodes,
1218 ellipsisroots, visitnodes, depth, source, version):
1254 ellipsisroots, visitnodes, depth, source, version):
1219 if version in ('01', '02'):
1255 if version in ('01', '02'):
1220 raise error.Abort(
1256 raise error.Abort(
1221 'ellipsis nodes require at least cg3 on client and server, '
1257 'ellipsis nodes require at least cg3 on client and server, '
1222 'but negotiated version %s' % version)
1258 'but negotiated version %s' % version)
1223 # We wrap cg1packer.revchunk, using a side channel to pass
1259 # We wrap cg1packer.revchunk, using a side channel to pass
1224 # relevant_nodes into that area. Then if linknode isn't in the
1260 # relevant_nodes into that area. Then if linknode isn't in the
1225 # set, we know we have an ellipsis node and we should defer
1261 # set, we know we have an ellipsis node and we should defer
1226 # sending that node's data. We override close() to detect
1262 # sending that node's data. We override close() to detect
1227 # pending ellipsis nodes and flush them.
1263 # pending ellipsis nodes and flush them.
1228 packer = getbundler(version, repo, filematcher=match)
1264 packer = getbundler(version, repo, filematcher=match)
1229 # Give the packer the list of nodes which should not be
1265 # Give the packer the list of nodes which should not be
1230 # ellipsis nodes. We store this rather than the set of nodes
1266 # ellipsis nodes. We store this rather than the set of nodes
1231 # that should be an ellipsis because for very large histories
1267 # that should be an ellipsis because for very large histories
1232 # we expect this to be significantly smaller.
1268 # we expect this to be significantly smaller.
1233 packer.full_nodes = relevant_nodes
1269 packer.full_nodes = relevant_nodes
1234 # Maps ellipsis revs to their roots at the changelog level.
1270 # Maps ellipsis revs to their roots at the changelog level.
1235 packer.precomputed_ellipsis = ellipsisroots
1271 packer.precomputed_ellipsis = ellipsisroots
1236 # Maps CL revs to per-revlog revisions. Cleared in close() at
1272 # Maps CL revs to per-revlog revisions. Cleared in close() at
1237 # the end of each group.
1273 # the end of each group.
1238 packer.clrev_to_localrev = {}
1274 packer.clrev_to_localrev = {}
1239 packer.next_clrev_to_localrev = {}
1275 packer.next_clrev_to_localrev = {}
1240 # Maps changelog nodes to changelog revs. Filled in once
1276 # Maps changelog nodes to changelog revs. Filled in once
1241 # during changelog stage and then left unmodified.
1277 # during changelog stage and then left unmodified.
1242 packer.clnode_to_rev = {}
1278 packer.clnode_to_rev = {}
1243 packer.changelog_done = False
1279 packer.changelog_done = False
1244 # If true, informs the packer that it is serving shallow content and might
1280 # If true, informs the packer that it is serving shallow content and might
1245 # need to pack file contents not introduced by the changes being packed.
1281 # need to pack file contents not introduced by the changes being packed.
1246 packer.is_shallow = depth is not None
1282 packer.is_shallow = depth is not None
1247
1283
1248 return packer.generate(common, visitnodes, False, source)
1284 return packer.generate(common, visitnodes, False, source)
General Comments 0
You need to be logged in to leave comments. Login now