##// END OF EJS Templates
narrow: move from ELLIPSIS_NODE_FLAG to revlog.REVIDX_ELLIPSIS...
Augie Fackler -
r36107:9445a314 default
parent child Browse files
Show More
@@ -1,385 +1,385
1 1 # narrowchangegroup.py - narrow clone changegroup creation and consumption
2 2 #
3 3 # Copyright 2017 Google, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 from mercurial.i18n import _
11 11 from mercurial import (
12 12 changegroup,
13 13 error,
14 14 extensions,
15 15 manifest,
16 16 mdiff,
17 17 node,
18 revlog,
18 19 util,
19 20 )
20 21
21 22 from . import (
22 23 narrowrepo,
23 narrowrevlog,
24 24 )
25 25
26 26 def setup():
27 27
28 28 def supportedoutgoingversions(orig, repo):
29 29 versions = orig(repo)
30 30 if narrowrepo.REQUIREMENT in repo.requirements:
31 31 versions.discard('01')
32 32 versions.discard('02')
33 33 return versions
34 34
35 35 extensions.wrapfunction(changegroup, 'supportedoutgoingversions',
36 36 supportedoutgoingversions)
37 37
38 38 def prune(orig, self, revlog, missing, commonrevs):
39 39 if isinstance(revlog, manifest.manifestrevlog):
40 40 matcher = getattr(self._repo, 'narrowmatch',
41 41 getattr(self, '_narrow_matcher', None))
42 42 if (matcher is not None and
43 43 not matcher().visitdir(revlog._dir[:-1] or '.')):
44 44 return []
45 45 return orig(self, revlog, missing, commonrevs)
46 46
47 47 extensions.wrapfunction(changegroup.cg1packer, 'prune', prune)
48 48
49 49 def generatefiles(orig, self, changedfiles, linknodes, commonrevs,
50 50 source):
51 51 matcher = getattr(self._repo, 'narrowmatch',
52 52 getattr(self, '_narrow_matcher', None))
53 53 if matcher is not None:
54 54 narrowmatch = matcher()
55 55 changedfiles = filter(narrowmatch, changedfiles)
56 56 if getattr(self, 'is_shallow', False):
57 57 # See comment in generate() for why this sadness is a thing.
58 58 mfdicts = self._mfdicts
59 59 del self._mfdicts
60 60 # In a shallow clone, the linknodes callback needs to also include
61 61 # those file nodes that are in the manifests we sent but weren't
62 62 # introduced by those manifests.
63 63 commonctxs = [self._repo[c] for c in commonrevs]
64 64 oldlinknodes = linknodes
65 65 clrev = self._repo.changelog.rev
66 66 def linknodes(flog, fname):
67 67 for c in commonctxs:
68 68 try:
69 69 fnode = c.filenode(fname)
70 70 self.clrev_to_localrev[c.rev()] = flog.rev(fnode)
71 71 except error.ManifestLookupError:
72 72 pass
73 73 links = oldlinknodes(flog, fname)
74 74 if len(links) != len(mfdicts):
75 75 for mf, lr in mfdicts:
76 76 fnode = mf.get(fname, None)
77 77 if fnode in links:
78 78 links[fnode] = min(links[fnode], lr, key=clrev)
79 79 elif fnode:
80 80 links[fnode] = lr
81 81 return links
82 82 return orig(self, changedfiles, linknodes, commonrevs, source)
83 83 extensions.wrapfunction(
84 84 changegroup.cg1packer, 'generatefiles', generatefiles)
85 85
86 def ellipsisdata(packer, rev, revlog, p1, p2, data, linknode):
87 n = revlog.node(rev)
88 p1n, p2n = revlog.node(p1), revlog.node(p2)
89 flags = revlog.flags(rev)
90 flags |= narrowrevlog.ELLIPSIS_NODE_FLAG
86 def ellipsisdata(packer, rev, revlog_, p1, p2, data, linknode):
87 n = revlog_.node(rev)
88 p1n, p2n = revlog_.node(p1), revlog_.node(p2)
89 flags = revlog_.flags(rev)
90 flags |= revlog.REVIDX_ELLIPSIS
91 91 meta = packer.builddeltaheader(
92 92 n, p1n, p2n, node.nullid, linknode, flags)
93 93 # TODO: try and actually send deltas for ellipsis data blocks
94 94 diffheader = mdiff.trivialdiffheader(len(data))
95 95 l = len(meta) + len(diffheader) + len(data)
96 96 return ''.join((changegroup.chunkheader(l),
97 97 meta,
98 98 diffheader,
99 99 data))
100 100
101 101 def close(orig, self):
102 102 getattr(self, 'clrev_to_localrev', {}).clear()
103 103 if getattr(self, 'next_clrev_to_localrev', {}):
104 104 self.clrev_to_localrev = self.next_clrev_to_localrev
105 105 del self.next_clrev_to_localrev
106 106 self.changelog_done = True
107 107 return orig(self)
108 108 extensions.wrapfunction(changegroup.cg1packer, 'close', close)
109 109
110 110 # In a perfect world, we'd generate better ellipsis-ified graphs
111 111 # for non-changelog revlogs. In practice, we haven't started doing
112 112 # that yet, so the resulting DAGs for the manifestlog and filelogs
113 113 # are actually full of bogus parentage on all the ellipsis
114 114 # nodes. This has the side effect that, while the contents are
115 115 # correct, the individual DAGs might be completely out of whack in
116 116 # a case like 882681bc3166 and its ancestors (back about 10
117 117 # revisions or so) in the main hg repo.
118 118 #
119 119 # The one invariant we *know* holds is that the new (potentially
120 120 # bogus) DAG shape will be valid if we order the nodes in the
121 121 # order that they're introduced in dramatis personae by the
122 122 # changelog, so what we do is we sort the non-changelog histories
123 123 # by the order in which they are used by the changelog.
124 124 def _sortgroup(orig, self, revlog, nodelist, lookup):
125 125 if not util.safehasattr(self, 'full_nodes') or not self.clnode_to_rev:
126 126 return orig(self, revlog, nodelist, lookup)
127 127 key = lambda n: self.clnode_to_rev[lookup(n)]
128 128 return [revlog.rev(n) for n in sorted(nodelist, key=key)]
129 129
130 130 extensions.wrapfunction(changegroup.cg1packer, '_sortgroup', _sortgroup)
131 131
132 132 def generate(orig, self, commonrevs, clnodes, fastpathlinkrev, source):
133 133 '''yield a sequence of changegroup chunks (strings)'''
134 134 # Note: other than delegating to orig, the only deviation in
135 135 # logic from normal hg's generate is marked with BEGIN/END
136 136 # NARROW HACK.
137 137 if not util.safehasattr(self, 'full_nodes'):
138 138 # not sending a narrow bundle
139 139 for x in orig(self, commonrevs, clnodes, fastpathlinkrev, source):
140 140 yield x
141 141 return
142 142
143 143 repo = self._repo
144 144 cl = repo.changelog
145 145 mfl = repo.manifestlog
146 146 mfrevlog = mfl._revlog
147 147
148 148 clrevorder = {}
149 149 mfs = {} # needed manifests
150 150 fnodes = {} # needed file nodes
151 151 changedfiles = set()
152 152
153 153 # Callback for the changelog, used to collect changed files and manifest
154 154 # nodes.
155 155 # Returns the linkrev node (identity in the changelog case).
156 156 def lookupcl(x):
157 157 c = cl.read(x)
158 158 clrevorder[x] = len(clrevorder)
159 159 # BEGIN NARROW HACK
160 160 #
161 161 # Only update mfs if x is going to be sent. Otherwise we
162 162 # end up with bogus linkrevs specified for manifests and
163 163 # we skip some manifest nodes that we should otherwise
164 164 # have sent.
165 165 if x in self.full_nodes or cl.rev(x) in self.precomputed_ellipsis:
166 166 n = c[0]
167 167 # record the first changeset introducing this manifest version
168 168 mfs.setdefault(n, x)
169 169 # Set this narrow-specific dict so we have the lowest manifest
170 170 # revnum to look up for this cl revnum. (Part of mapping
171 171 # changelog ellipsis parents to manifest ellipsis parents)
172 172 self.next_clrev_to_localrev.setdefault(cl.rev(x),
173 173 mfrevlog.rev(n))
174 174 # We can't trust the changed files list in the changeset if the
175 175 # client requested a shallow clone.
176 176 if self.is_shallow:
177 177 changedfiles.update(mfl[c[0]].read().keys())
178 178 else:
179 179 changedfiles.update(c[3])
180 180 # END NARROW HACK
181 181 # Record a complete list of potentially-changed files in
182 182 # this manifest.
183 183 return x
184 184
185 185 self._verbosenote(_('uncompressed size of bundle content:\n'))
186 186 size = 0
187 187 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
188 188 size += len(chunk)
189 189 yield chunk
190 190 self._verbosenote(_('%8.i (changelog)\n') % size)
191 191
192 192 # We need to make sure that the linkrev in the changegroup refers to
193 193 # the first changeset that introduced the manifest or file revision.
194 194 # The fastpath is usually safer than the slowpath, because the filelogs
195 195 # are walked in revlog order.
196 196 #
197 197 # When taking the slowpath with reorder=None and the manifest revlog
198 198 # uses generaldelta, the manifest may be walked in the "wrong" order.
199 199 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
200 200 # cc0ff93d0c0c).
201 201 #
202 202 # When taking the fastpath, we are only vulnerable to reordering
203 203 # of the changelog itself. The changelog never uses generaldelta, so
204 204 # it is only reordered when reorder=True. To handle this case, we
205 205 # simply take the slowpath, which already has the 'clrevorder' logic.
206 206 # This was also fixed in cc0ff93d0c0c.
207 207 fastpathlinkrev = fastpathlinkrev and not self._reorder
208 208 # Treemanifests don't work correctly with fastpathlinkrev
209 209 # either, because we don't discover which directory nodes to
210 210 # send along with files. This could probably be fixed.
211 211 fastpathlinkrev = fastpathlinkrev and (
212 212 'treemanifest' not in repo.requirements)
213 213 # Shallow clones also don't work correctly with fastpathlinkrev
214 214 # because file nodes may need to be sent for a manifest even if they
215 215 # weren't introduced by that manifest.
216 216 fastpathlinkrev = fastpathlinkrev and not self.is_shallow
217 217
218 218 moreargs = []
219 219 if self.generatemanifests.func_code.co_argcount == 7:
220 220 # The source argument was added to generatemanifests in hg in
221 221 # 75cc1f1e11f2 (2017/09/11).
222 222 moreargs.append(source)
223 223 for chunk in self.generatemanifests(commonrevs, clrevorder,
224 224 fastpathlinkrev, mfs, fnodes, *moreargs):
225 225 yield chunk
226 226 # BEGIN NARROW HACK
227 227 mfdicts = None
228 228 if self.is_shallow:
229 229 mfdicts = [(self._repo.manifestlog[n].read(), lr)
230 230 for (n, lr) in mfs.iteritems()]
231 231 # END NARROW HACK
232 232 mfs.clear()
233 233 clrevs = set(cl.rev(x) for x in clnodes)
234 234
235 235 if not fastpathlinkrev:
236 236 def linknodes(unused, fname):
237 237 return fnodes.get(fname, {})
238 238 else:
239 239 cln = cl.node
240 240 def linknodes(filerevlog, fname):
241 241 llr = filerevlog.linkrev
242 242 fln = filerevlog.node
243 243 revs = ((r, llr(r)) for r in filerevlog)
244 244 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
245 245
246 246 # BEGIN NARROW HACK
247 247 #
248 248 # We need to pass the mfdicts variable down into
249 249 # generatefiles(), but more than one command might have
250 250 # wrapped generatefiles so we can't modify the function
251 251 # signature. Instead, we pass the data to ourselves using an
252 252 # instance attribute. I'm sorry.
253 253 self._mfdicts = mfdicts
254 254 # END NARROW HACK
255 255 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
256 256 source):
257 257 yield chunk
258 258
259 259 yield self.close()
260 260
261 261 if clnodes:
262 262 repo.hook('outgoing', node=node.hex(clnodes[0]), source=source)
263 263 extensions.wrapfunction(changegroup.cg1packer, 'generate', generate)
264 264
265 265 def revchunk(orig, self, revlog, rev, prev, linknode):
266 266 if not util.safehasattr(self, 'full_nodes'):
267 267 # not sending a narrow changegroup
268 268 for x in orig(self, revlog, rev, prev, linknode):
269 269 yield x
270 270 return
271 271 # build up some mapping information that's useful later. See
272 272 # the local() nested function below.
273 273 if not self.changelog_done:
274 274 self.clnode_to_rev[linknode] = rev
275 275 linkrev = rev
276 276 self.clrev_to_localrev[linkrev] = rev
277 277 else:
278 278 linkrev = self.clnode_to_rev[linknode]
279 279 self.clrev_to_localrev[linkrev] = rev
280 280 # This is a node to send in full, because the changeset it
281 281 # corresponds to was a full changeset.
282 282 if linknode in self.full_nodes:
283 283 for x in orig(self, revlog, rev, prev, linknode):
284 284 yield x
285 285 return
286 286 # At this point, a node can either be one we should skip or an
287 287 # ellipsis. If it's not an ellipsis, bail immediately.
288 288 if linkrev not in self.precomputed_ellipsis:
289 289 return
290 290 linkparents = self.precomputed_ellipsis[linkrev]
291 291 def local(clrev):
292 292 """Turn a changelog revnum into a local revnum.
293 293
294 294 The ellipsis dag is stored as revnums on the changelog,
295 295 but when we're producing ellipsis entries for
296 296 non-changelog revlogs, we need to turn those numbers into
297 297 something local. This does that for us, and during the
298 298 changelog sending phase will also expand the stored
299 299 mappings as needed.
300 300 """
301 301 if clrev == node.nullrev:
302 302 return node.nullrev
303 303 if not self.changelog_done:
304 304 # If we're doing the changelog, it's possible that we
305 305 # have a parent that is already on the client, and we
306 306 # need to store some extra mapping information so that
307 307 # our contained ellipsis nodes will be able to resolve
308 308 # their parents.
309 309 if clrev not in self.clrev_to_localrev:
310 310 clnode = revlog.node(clrev)
311 311 self.clnode_to_rev[clnode] = clrev
312 312 return clrev
313 313 # Walk the ellipsis-ized changelog breadth-first looking for a
314 314 # change that has been linked from the current revlog.
315 315 #
316 316 # For a flat manifest revlog only a single step should be necessary
317 317 # as all relevant changelog entries are relevant to the flat
318 318 # manifest.
319 319 #
320 320 # For a filelog or tree manifest dirlog however not every changelog
321 321 # entry will have been relevant, so we need to skip some changelog
322 322 # nodes even after ellipsis-izing.
323 323 walk = [clrev]
324 324 while walk:
325 325 p = walk[0]
326 326 walk = walk[1:]
327 327 if p in self.clrev_to_localrev:
328 328 return self.clrev_to_localrev[p]
329 329 elif p in self.full_nodes:
330 330 walk.extend([pp for pp in self._repo.changelog.parentrevs(p)
331 331 if pp != node.nullrev])
332 332 elif p in self.precomputed_ellipsis:
333 333 walk.extend([pp for pp in self.precomputed_ellipsis[p]
334 334 if pp != node.nullrev])
335 335 else:
336 336 # In this case, we've got an ellipsis with parents
337 337 # outside the current bundle (likely an
338 338 # incremental pull). We "know" that we can use the
339 339 # value of this same revlog at whatever revision
340 340 # is pointed to by linknode. "Know" is in scare
341 341 # quotes because I haven't done enough examination
342 342 # of edge cases to convince myself this is really
343 343 # a fact - it works for all the (admittedly
344 344 # thorough) cases in our testsuite, but I would be
345 345 # somewhat unsurprised to find a case in the wild
346 346 # where this breaks down a bit. That said, I don't
347 347 # know if it would hurt anything.
348 348 for i in xrange(rev, 0, -1):
349 349 if revlog.linkrev(i) == clrev:
350 350 return i
351 351 # We failed to resolve a parent for this node, so
352 352 # we crash the changegroup construction.
353 353 raise error.Abort(
354 354 'unable to resolve parent while packing %r %r'
355 355 ' for changeset %r' % (revlog.indexfile, rev, clrev))
356 356 return node.nullrev
357 357
358 358 if not linkparents or (
359 359 revlog.parentrevs(rev) == (node.nullrev, node.nullrev)):
360 360 p1, p2 = node.nullrev, node.nullrev
361 361 elif len(linkparents) == 1:
362 362 p1, = sorted(local(p) for p in linkparents)
363 363 p2 = node.nullrev
364 364 else:
365 365 p1, p2 = sorted(local(p) for p in linkparents)
366 366 yield ellipsisdata(
367 367 self, rev, revlog, p1, p2, revlog.revision(rev), linknode)
368 368 extensions.wrapfunction(changegroup.cg1packer, 'revchunk', revchunk)
369 369
370 370 def deltaparent(orig, self, revlog, rev, p1, p2, prev):
371 371 if util.safehasattr(self, 'full_nodes'):
372 372 # TODO: send better deltas when in narrow mode.
373 373 #
374 374 # changegroup.group() loops over revisions to send,
375 375 # including revisions we'll skip. What this means is that
376 376 # `prev` will be a potentially useless delta base for all
377 377 # ellipsis nodes, as the client likely won't have it. In
378 378 # the future we should do bookkeeping about which nodes
379 379 # have been sent to the client, and try to be
380 380 # significantly smarter about delta bases. This is
381 381 # slightly tricky because this same code has to work for
382 382 # all revlogs, and we don't have the linkrev/linknode here.
383 383 return p1
384 384 return orig(self, revlog, rev, p1, p2, prev)
385 385 extensions.wrapfunction(changegroup.cg2packer, 'deltaparent', deltaparent)
@@ -1,161 +1,156
1 1 # narrowrevlog.py - revlog storing irrelevant nodes as "ellipsis" nodes
2 2 #
3 3 # Copyright 2017 Google, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 from mercurial import (
11 11 manifest,
12 12 revlog,
13 13 util,
14 14 )
15 15
16 ELLIPSIS_NODE_FLAG = 1 << 14
17 revlog.REVIDX_KNOWN_FLAGS |= ELLIPSIS_NODE_FLAG
18 if ELLIPSIS_NODE_FLAG not in revlog.REVIDX_FLAGS_ORDER:
19 revlog.REVIDX_FLAGS_ORDER.append(ELLIPSIS_NODE_FLAG)
20
21 16 def readtransform(self, text):
22 17 return text, False
23 18
24 19 def writetransform(self, text):
25 20 return text, False
26 21
27 22 def rawtransform(self, text):
28 23 return False
29 24
30 revlog.addflagprocessor(ELLIPSIS_NODE_FLAG,
25 revlog.addflagprocessor(revlog.REVIDX_ELLIPSIS,
31 26 (readtransform, writetransform, rawtransform))
32 27
33 28 def setup():
34 29 # We just wanted to add the flag processor, which is done at module
35 30 # load time.
36 31 pass
37 32
38 33 class excludeddir(manifest.treemanifest):
39 34 def __init__(self, dir, node):
40 35 super(excludeddir, self).__init__(dir)
41 36 self._node = node
42 37 # Add an empty file, which will be included by iterators and such,
43 38 # appearing as the directory itself (i.e. something like "dir/")
44 39 self._files[''] = node
45 40 self._flags[''] = 't'
46 41
47 42 # Manifests outside the narrowspec should never be modified, so avoid
48 43 # copying. This makes a noticeable difference when there are very many
49 44 # directories outside the narrowspec. Also, it makes sense for the copy to
50 45 # be of the same type as the original, which would not happen with the
51 46 # super type's copy().
52 47 def copy(self):
53 48 return self
54 49
55 50 class excludeddirmanifestctx(manifest.treemanifestctx):
56 51 def __init__(self, dir, node):
57 52 self._dir = dir
58 53 self._node = node
59 54
60 55 def read(self):
61 56 return excludeddir(self._dir, self._node)
62 57
63 58 def write(self, *args):
64 59 raise AssertionError('Attempt to write manifest from excluded dir %s' %
65 60 self._dir)
66 61
67 62 class excludedmanifestrevlog(manifest.manifestrevlog):
68 63 def __init__(self, dir):
69 64 self._dir = dir
70 65
71 66 def __len__(self):
72 67 raise AssertionError('Attempt to get length of excluded dir %s' %
73 68 self._dir)
74 69
75 70 def rev(self, node):
76 71 raise AssertionError('Attempt to get rev from excluded dir %s' %
77 72 self._dir)
78 73
79 74 def linkrev(self, node):
80 75 raise AssertionError('Attempt to get linkrev from excluded dir %s' %
81 76 self._dir)
82 77
83 78 def node(self, rev):
84 79 raise AssertionError('Attempt to get node from excluded dir %s' %
85 80 self._dir)
86 81
87 82 def add(self, *args, **kwargs):
88 83 # We should never write entries in dirlogs outside the narrow clone.
89 84 # However, the method still gets called from writesubtree() in
90 85 # _addtree(), so we need to handle it. We should possibly make that
91 86 # avoid calling add() with a clean manifest (_dirty is always False
92 87 # in excludeddir instances).
93 88 pass
94 89
95 90 def makenarrowmanifestrevlog(mfrevlog, repo):
96 91 if util.safehasattr(mfrevlog, '_narrowed'):
97 92 return
98 93
99 94 class narrowmanifestrevlog(mfrevlog.__class__):
100 95 # This function is called via debug{revlog,index,data}, but also during
101 96 # at least some push operations. This will be used to wrap/exclude the
102 97 # child directories when using treemanifests.
103 98 def dirlog(self, dir):
104 99 if dir and not dir.endswith('/'):
105 100 dir = dir + '/'
106 101 if not repo.narrowmatch().visitdir(dir[:-1] or '.'):
107 102 return excludedmanifestrevlog(dir)
108 103 result = super(narrowmanifestrevlog, self).dirlog(dir)
109 104 makenarrowmanifestrevlog(result, repo)
110 105 return result
111 106
112 107 mfrevlog.__class__ = narrowmanifestrevlog
113 108 mfrevlog._narrowed = True
114 109
115 110 def makenarrowmanifestlog(mfl, repo):
116 111 class narrowmanifestlog(mfl.__class__):
117 112 def get(self, dir, node, verify=True):
118 113 if not repo.narrowmatch().visitdir(dir[:-1] or '.'):
119 114 return excludeddirmanifestctx(dir, node)
120 115 return super(narrowmanifestlog, self).get(dir, node, verify=verify)
121 116 mfl.__class__ = narrowmanifestlog
122 117
123 118 def makenarrowfilelog(fl, narrowmatch):
124 119 class narrowfilelog(fl.__class__):
125 120 def renamed(self, node):
126 121 m = super(narrowfilelog, self).renamed(node)
127 122 if m and not narrowmatch(m[0]):
128 123 return None
129 124 return m
130 125
131 126 def size(self, rev):
132 127 # We take advantage of the fact that remotefilelog
133 128 # lacks a node() method to just skip the
134 129 # rename-checking logic when on remotefilelog. This
135 130 # might be incorrect on other non-revlog-based storage
136 131 # engines, but for now this seems to be fine.
137 132 if util.safehasattr(self, 'node'):
138 133 node = self.node(rev)
139 134 # Because renamed() is overridden above to
140 135 # sometimes return None even if there is metadata
141 136 # in the revlog, size can be incorrect for
142 137 # copies/renames, so we need to make sure we call
143 138 # the super class's implementation of renamed()
144 139 # for the purpose of size calculation.
145 140 if super(narrowfilelog, self).renamed(node):
146 141 return len(self.read(node))
147 142 return super(narrowfilelog, self).size(rev)
148 143
149 144 def cmp(self, node, text):
150 145 different = super(narrowfilelog, self).cmp(node, text)
151 146 if different:
152 147 # Similar to size() above, if the file was copied from
153 148 # a file outside the narrowspec, the super class's
154 149 # would have returned True because we tricked it into
155 150 # thinking that the file was not renamed.
156 151 if super(narrowfilelog, self).renamed(node):
157 152 t2 = self.read(node)
158 153 return t2 != text
159 154 return different
160 155
161 156 fl.__class__ = narrowfilelog
@@ -1,50 +1,49
1 1 # narrowtemplates.py - added template keywords for narrow clones
2 2 #
3 3 # Copyright 2017 Google, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 from mercurial import (
11 revlog,
11 12 revset,
12 13 templatekw,
13 14 util,
14 15 )
15 16
16 from . import narrowrevlog
17
18 17 def _isellipsis(repo, rev):
19 if repo.changelog.flags(rev) & narrowrevlog.ELLIPSIS_NODE_FLAG:
18 if repo.changelog.flags(rev) & revlog.REVIDX_ELLIPSIS:
20 19 return True
21 20 return False
22 21
23 22 def ellipsis(repo, ctx, templ, **args):
24 23 """:ellipsis: String. 'ellipsis' if the change is an ellipsis node,
25 24 else ''."""
26 25 if _isellipsis(repo, ctx.rev()):
27 26 return 'ellipsis'
28 27 return ''
29 28
30 29 def outsidenarrow(repo, ctx, templ, **args):
31 30 """:outsidenarrow: String. 'outsidenarrow' if the change affects no
32 31 tracked files, else ''."""
33 32 if util.safehasattr(repo, 'narrowmatch'):
34 33 m = repo.narrowmatch()
35 34 if not any(m(f) for f in ctx.files()):
36 35 return 'outsidenarrow'
37 36 return ''
38 37
39 38 def ellipsisrevset(repo, subset, x):
40 39 """``ellipsis()``
41 40 Changesets that are ellipsis nodes.
42 41 """
43 42 return subset.filter(lambda r: _isellipsis(repo, r))
44 43
45 44 def setup():
46 45 templatekw.keywords['ellipsis'] = ellipsis
47 46 templatekw.keywords['outsidenarrow'] = outsidenarrow
48 47
49 48 revset.symbols['ellipsis'] = ellipsisrevset
50 49 revset.safesymbols.add('ellipsis')
General Comments 0
You need to be logged in to leave comments. Login now