##// END OF EJS Templates
fileset: rewrite predicates to return matcher not closed to subset (API) (BC)...
Yuya Nishihara -
r38948:ff5b6fca default
parent child Browse files
Show More
@@ -1,399 +1,401 b''
1 1 # lfs - hash-preserving large file support using Git-LFS protocol
2 2 #
3 3 # Copyright 2017 Facebook, 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 """lfs - large file support (EXPERIMENTAL)
9 9
10 10 This extension allows large files to be tracked outside of the normal
11 11 repository storage and stored on a centralized server, similar to the
12 12 ``largefiles`` extension. The ``git-lfs`` protocol is used when
13 13 communicating with the server, so existing git infrastructure can be
14 14 harnessed. Even though the files are stored outside of the repository,
15 15 they are still integrity checked in the same manner as normal files.
16 16
17 17 The files stored outside of the repository are downloaded on demand,
18 18 which reduces the time to clone, and possibly the local disk usage.
19 19 This changes fundamental workflows in a DVCS, so careful thought
20 20 should be given before deploying it. :hg:`convert` can be used to
21 21 convert LFS repositories to normal repositories that no longer
22 22 require this extension, and do so without changing the commit hashes.
23 23 This allows the extension to be disabled if the centralized workflow
24 24 becomes burdensome. However, the pre and post convert clones will
25 25 not be able to communicate with each other unless the extension is
26 26 enabled on both.
27 27
28 28 To start a new repository, or to add LFS files to an existing one, just
29 29 create an ``.hglfs`` file as described below in the root directory of
30 30 the repository. Typically, this file should be put under version
31 31 control, so that the settings will propagate to other repositories with
32 32 push and pull. During any commit, Mercurial will consult this file to
33 33 determine if an added or modified file should be stored externally. The
34 34 type of storage depends on the characteristics of the file at each
35 35 commit. A file that is near a size threshold may switch back and forth
36 36 between LFS and normal storage, as needed.
37 37
38 38 Alternately, both normal repositories and largefile controlled
39 39 repositories can be converted to LFS by using :hg:`convert` and the
40 40 ``lfs.track`` config option described below. The ``.hglfs`` file
41 41 should then be created and added, to control subsequent LFS selection.
42 42 The hashes are also unchanged in this case. The LFS and non-LFS
43 43 repositories can be distinguished because the LFS repository will
44 44 abort any command if this extension is disabled.
45 45
46 46 Committed LFS files are held locally, until the repository is pushed.
47 47 Prior to pushing the normal repository data, the LFS files that are
48 48 tracked by the outgoing commits are automatically uploaded to the
49 49 configured central server. No LFS files are transferred on
50 50 :hg:`pull` or :hg:`clone`. Instead, the files are downloaded on
51 51 demand as they need to be read, if a cached copy cannot be found
52 52 locally. Both committing and downloading an LFS file will link the
53 53 file to a usercache, to speed up future access. See the `usercache`
54 54 config setting described below.
55 55
56 56 .hglfs::
57 57
58 58 The extension reads its configuration from a versioned ``.hglfs``
59 59 configuration file found in the root of the working directory. The
60 60 ``.hglfs`` file uses the same syntax as all other Mercurial
61 61 configuration files. It uses a single section, ``[track]``.
62 62
63 63 The ``[track]`` section specifies which files are stored as LFS (or
64 64 not). Each line is keyed by a file pattern, with a predicate value.
65 65 The first file pattern match is used, so put more specific patterns
66 66 first. The available predicates are ``all()``, ``none()``, and
67 67 ``size()``. See "hg help filesets.size" for the latter.
68 68
69 69 Example versioned ``.hglfs`` file::
70 70
71 71 [track]
72 72 # No Makefile or python file, anywhere, will be LFS
73 73 **Makefile = none()
74 74 **.py = none()
75 75
76 76 **.zip = all()
77 77 **.exe = size(">1MB")
78 78
79 79 # Catchall for everything not matched above
80 80 ** = size(">10MB")
81 81
82 82 Configs::
83 83
84 84 [lfs]
85 85 # Remote endpoint. Multiple protocols are supported:
86 86 # - http(s)://user:pass@example.com/path
87 87 # git-lfs endpoint
88 88 # - file:///tmp/path
89 89 # local filesystem, usually for testing
90 90 # if unset, lfs will assume the remote repository also handles blob storage
91 91 # for http(s) URLs. Otherwise, lfs will prompt to set this when it must
92 92 # use this value.
93 93 # (default: unset)
94 94 url = https://example.com/repo.git/info/lfs
95 95
96 96 # Which files to track in LFS. Path tests are "**.extname" for file
97 97 # extensions, and "path:under/some/directory" for path prefix. Both
98 98 # are relative to the repository root.
99 99 # File size can be tested with the "size()" fileset, and tests can be
100 100 # joined with fileset operators. (See "hg help filesets.operators".)
101 101 #
102 102 # Some examples:
103 103 # - all() # everything
104 104 # - none() # nothing
105 105 # - size(">20MB") # larger than 20MB
106 106 # - !**.txt # anything not a *.txt file
107 107 # - **.zip | **.tar.gz | **.7z # some types of compressed files
108 108 # - path:bin # files under "bin" in the project root
109 109 # - (**.php & size(">2MB")) | (**.js & size(">5MB")) | **.tar.gz
110 110 # | (path:bin & !path:/bin/README) | size(">1GB")
111 111 # (default: none())
112 112 #
113 113 # This is ignored if there is a tracked '.hglfs' file, and this setting
114 114 # will eventually be deprecated and removed.
115 115 track = size(">10M")
116 116
117 117 # how many times to retry before giving up on transferring an object
118 118 retry = 5
119 119
120 120 # the local directory to store lfs files for sharing across local clones.
121 121 # If not set, the cache is located in an OS specific cache location.
122 122 usercache = /path/to/global/cache
123 123 """
124 124
125 125 from __future__ import absolute_import
126 126
127 127 from mercurial.i18n import _
128 128
129 129 from mercurial import (
130 130 bundle2,
131 131 changegroup,
132 132 cmdutil,
133 133 config,
134 134 context,
135 135 error,
136 136 exchange,
137 137 extensions,
138 138 filelog,
139 139 fileset,
140 140 hg,
141 141 localrepo,
142 142 minifileset,
143 143 node,
144 144 pycompat,
145 145 registrar,
146 146 revlog,
147 147 scmutil,
148 148 templateutil,
149 149 upgrade,
150 150 util,
151 151 vfs as vfsmod,
152 152 wireprotoserver,
153 153 wireprotov1server,
154 154 )
155 155
156 156 from . import (
157 157 blobstore,
158 158 wireprotolfsserver,
159 159 wrapper,
160 160 )
161 161
162 162 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
163 163 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
164 164 # be specifying the version(s) of Mercurial they are tested with, or
165 165 # leave the attribute unspecified.
166 166 testedwith = 'ships-with-hg-core'
167 167
168 168 configtable = {}
169 169 configitem = registrar.configitem(configtable)
170 170
171 171 configitem('experimental', 'lfs.serve',
172 172 default=True,
173 173 )
174 174 configitem('experimental', 'lfs.user-agent',
175 175 default=None,
176 176 )
177 177 configitem('experimental', 'lfs.disableusercache',
178 178 default=False,
179 179 )
180 180 configitem('experimental', 'lfs.worker-enable',
181 181 default=False,
182 182 )
183 183
184 184 configitem('lfs', 'url',
185 185 default=None,
186 186 )
187 187 configitem('lfs', 'usercache',
188 188 default=None,
189 189 )
190 190 # Deprecated
191 191 configitem('lfs', 'threshold',
192 192 default=None,
193 193 )
194 194 configitem('lfs', 'track',
195 195 default='none()',
196 196 )
197 197 configitem('lfs', 'retry',
198 198 default=5,
199 199 )
200 200
201 201 cmdtable = {}
202 202 command = registrar.command(cmdtable)
203 203
204 204 templatekeyword = registrar.templatekeyword()
205 205 filesetpredicate = registrar.filesetpredicate()
206 206
207 207 def featuresetup(ui, supported):
208 208 # don't die on seeing a repo with the lfs requirement
209 209 supported |= {'lfs'}
210 210
211 211 def uisetup(ui):
212 212 localrepo.featuresetupfuncs.add(featuresetup)
213 213
214 214 def reposetup(ui, repo):
215 215 # Nothing to do with a remote repo
216 216 if not repo.local():
217 217 return
218 218
219 219 repo.svfs.lfslocalblobstore = blobstore.local(repo)
220 220 repo.svfs.lfsremoteblobstore = blobstore.remote(repo)
221 221
222 222 class lfsrepo(repo.__class__):
223 223 @localrepo.unfilteredmethod
224 224 def commitctx(self, ctx, error=False):
225 225 repo.svfs.options['lfstrack'] = _trackedmatcher(self)
226 226 return super(lfsrepo, self).commitctx(ctx, error)
227 227
228 228 repo.__class__ = lfsrepo
229 229
230 230 if 'lfs' not in repo.requirements:
231 231 def checkrequireslfs(ui, repo, **kwargs):
232 232 if 'lfs' not in repo.requirements:
233 233 last = kwargs.get(r'node_last')
234 234 _bin = node.bin
235 235 if last:
236 236 s = repo.set('%n:%n', _bin(kwargs[r'node']), _bin(last))
237 237 else:
238 238 s = repo.set('%n', _bin(kwargs[r'node']))
239 239 match = repo.narrowmatch()
240 240 for ctx in s:
241 241 # TODO: is there a way to just walk the files in the commit?
242 242 if any(ctx[f].islfs() for f in ctx.files()
243 243 if f in ctx and match(f)):
244 244 repo.requirements.add('lfs')
245 245 repo._writerequirements()
246 246 repo.prepushoutgoinghooks.add('lfs', wrapper.prepush)
247 247 break
248 248
249 249 ui.setconfig('hooks', 'commit.lfs', checkrequireslfs, 'lfs')
250 250 ui.setconfig('hooks', 'pretxnchangegroup.lfs', checkrequireslfs, 'lfs')
251 251 else:
252 252 repo.prepushoutgoinghooks.add('lfs', wrapper.prepush)
253 253
254 254 def _trackedmatcher(repo):
255 255 """Return a function (path, size) -> bool indicating whether or not to
256 256 track a given file with lfs."""
257 257 if not repo.wvfs.exists('.hglfs'):
258 258 # No '.hglfs' in wdir. Fallback to config for now.
259 259 trackspec = repo.ui.config('lfs', 'track')
260 260
261 261 # deprecated config: lfs.threshold
262 262 threshold = repo.ui.configbytes('lfs', 'threshold')
263 263 if threshold:
264 264 fileset.parse(trackspec) # make sure syntax errors are confined
265 265 trackspec = "(%s) | size('>%d')" % (trackspec, threshold)
266 266
267 267 return minifileset.compile(trackspec)
268 268
269 269 data = repo.wvfs.tryread('.hglfs')
270 270 if not data:
271 271 return lambda p, s: False
272 272
273 273 # Parse errors here will abort with a message that points to the .hglfs file
274 274 # and line number.
275 275 cfg = config.config()
276 276 cfg.parse('.hglfs', data)
277 277
278 278 try:
279 279 rules = [(minifileset.compile(pattern), minifileset.compile(rule))
280 280 for pattern, rule in cfg.items('track')]
281 281 except error.ParseError as e:
282 282 # The original exception gives no indicator that the error is in the
283 283 # .hglfs file, so add that.
284 284
285 285 # TODO: See if the line number of the file can be made available.
286 286 raise error.Abort(_('parse error in .hglfs: %s') % e)
287 287
288 288 def _match(path, size):
289 289 for pat, rule in rules:
290 290 if pat(path, size):
291 291 return rule(path, size)
292 292
293 293 return False
294 294
295 295 return _match
296 296
297 297 def wrapfilelog(filelog):
298 298 wrapfunction = extensions.wrapfunction
299 299
300 300 wrapfunction(filelog, 'addrevision', wrapper.filelogaddrevision)
301 301 wrapfunction(filelog, 'renamed', wrapper.filelogrenamed)
302 302 wrapfunction(filelog, 'size', wrapper.filelogsize)
303 303
304 304 def extsetup(ui):
305 305 wrapfilelog(filelog.filelog)
306 306
307 307 wrapfunction = extensions.wrapfunction
308 308
309 309 wrapfunction(cmdutil, '_updatecatformatter', wrapper._updatecatformatter)
310 310 wrapfunction(scmutil, 'wrapconvertsink', wrapper.convertsink)
311 311
312 312 wrapfunction(upgrade, '_finishdatamigration',
313 313 wrapper.upgradefinishdatamigration)
314 314
315 315 wrapfunction(upgrade, 'preservedrequirements',
316 316 wrapper.upgraderequirements)
317 317
318 318 wrapfunction(upgrade, 'supporteddestrequirements',
319 319 wrapper.upgraderequirements)
320 320
321 321 wrapfunction(changegroup,
322 322 'allsupportedversions',
323 323 wrapper.allsupportedversions)
324 324
325 325 wrapfunction(exchange, 'push', wrapper.push)
326 326 wrapfunction(wireprotov1server, '_capabilities', wrapper._capabilities)
327 327 wrapfunction(wireprotoserver, 'handlewsgirequest',
328 328 wireprotolfsserver.handlewsgirequest)
329 329
330 330 wrapfunction(context.basefilectx, 'cmp', wrapper.filectxcmp)
331 331 wrapfunction(context.basefilectx, 'isbinary', wrapper.filectxisbinary)
332 332 context.basefilectx.islfs = wrapper.filectxislfs
333 333
334 334 revlog.addflagprocessor(
335 335 revlog.REVIDX_EXTSTORED,
336 336 (
337 337 wrapper.readfromstore,
338 338 wrapper.writetostore,
339 339 wrapper.bypasscheckhash,
340 340 ),
341 341 )
342 342
343 343 wrapfunction(hg, 'clone', wrapper.hgclone)
344 344 wrapfunction(hg, 'postshare', wrapper.hgpostshare)
345 345
346 346 scmutil.fileprefetchhooks.add('lfs', wrapper._prefetchfiles)
347 347
348 348 # Make bundle choose changegroup3 instead of changegroup2. This affects
349 349 # "hg bundle" command. Note: it does not cover all bundle formats like
350 350 # "packed1". Using "packed1" with lfs will likely cause trouble.
351 351 exchange._bundlespeccontentopts["v2"]["cg.version"] = "03"
352 352
353 353 # bundlerepo uses "vfsmod.readonlyvfs(othervfs)", we need to make sure lfs
354 354 # options and blob stores are passed from othervfs to the new readonlyvfs.
355 355 wrapfunction(vfsmod.readonlyvfs, '__init__', wrapper.vfsinit)
356 356
357 357 # when writing a bundle via "hg bundle" command, upload related LFS blobs
358 358 wrapfunction(bundle2, 'writenewbundle', wrapper.writenewbundle)
359 359
360 360 @filesetpredicate('lfs()', callstatus=True)
361 361 def lfsfileset(mctx, x):
362 362 """File that uses LFS storage."""
363 363 # i18n: "lfs" is a keyword
364 364 fileset.getargs(x, 0, 0, _("lfs takes no arguments"))
365 return [f for f in mctx.subset
366 if wrapper.pointerfromctx(mctx.ctx, f, removed=True) is not None]
365 ctx = mctx.ctx
366 def lfsfilep(f):
367 return wrapper.pointerfromctx(ctx, f, removed=True) is not None
368 return mctx.predicate(lfsfilep, predrepr='<lfs>')
367 369
368 370 @templatekeyword('lfs_files', requires={'ctx'})
369 371 def lfsfiles(context, mapping):
370 372 """List of strings. All files modified, added, or removed by this
371 373 changeset."""
372 374 ctx = context.resource(mapping, 'ctx')
373 375
374 376 pointers = wrapper.pointersfromctx(ctx, removed=True) # {path: pointer}
375 377 files = sorted(pointers.keys())
376 378
377 379 def pointer(v):
378 380 # In the file spec, version is first and the other keys are sorted.
379 381 sortkeyfunc = lambda x: (x[0] != 'version', x)
380 382 items = sorted(pointers[v].iteritems(), key=sortkeyfunc)
381 383 return util.sortdict(items)
382 384
383 385 makemap = lambda v: {
384 386 'file': v,
385 387 'lfsoid': pointers[v].oid() if pointers[v] else None,
386 388 'lfspointer': templateutil.hybriddict(pointer(v)),
387 389 }
388 390
389 391 # TODO: make the separator ', '?
390 392 f = templateutil._showcompatlist(context, mapping, 'lfs_file', files)
391 393 return templateutil.hybrid(f, files, makemap, pycompat.identity)
392 394
393 395 @command('debuglfsupload',
394 396 [('r', 'rev', [], _('upload large files introduced by REV'))])
395 397 def debuglfsupload(ui, repo, **opts):
396 398 """upload lfs blobs added by the working copy parent or given revisions"""
397 399 revs = opts.get(r'rev', [])
398 400 pointers = wrapper.extractpointers(repo, scmutil.revrange(repo, revs))
399 401 wrapper.uploadblobs(repo, pointers)
@@ -1,728 +1,721 b''
1 1 # fileset.py - file set queries for mercurial
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
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 import errno
11 11 import re
12 12
13 13 from .i18n import _
14 14 from . import (
15 15 error,
16 16 match as matchmod,
17 17 merge,
18 18 parser,
19 19 pycompat,
20 20 registrar,
21 21 scmutil,
22 22 util,
23 23 )
24 24 from .utils import (
25 25 stringutil,
26 26 )
27 27
28 28 elements = {
29 29 # token-type: binding-strength, primary, prefix, infix, suffix
30 30 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
31 31 ":": (15, None, None, ("kindpat", 15), None),
32 32 "-": (5, None, ("negate", 19), ("minus", 5), None),
33 33 "not": (10, None, ("not", 10), None, None),
34 34 "!": (10, None, ("not", 10), None, None),
35 35 "and": (5, None, None, ("and", 5), None),
36 36 "&": (5, None, None, ("and", 5), None),
37 37 "or": (4, None, None, ("or", 4), None),
38 38 "|": (4, None, None, ("or", 4), None),
39 39 "+": (4, None, None, ("or", 4), None),
40 40 ",": (2, None, None, ("list", 2), None),
41 41 ")": (0, None, None, None, None),
42 42 "symbol": (0, "symbol", None, None, None),
43 43 "string": (0, "string", None, None, None),
44 44 "end": (0, None, None, None, None),
45 45 }
46 46
47 47 keywords = {'and', 'or', 'not'}
48 48
49 49 globchars = ".*{}[]?/\\_"
50 50
51 51 def tokenize(program):
52 52 pos, l = 0, len(program)
53 53 program = pycompat.bytestr(program)
54 54 while pos < l:
55 55 c = program[pos]
56 56 if c.isspace(): # skip inter-token whitespace
57 57 pass
58 58 elif c in "(),-:|&+!": # handle simple operators
59 59 yield (c, None, pos)
60 60 elif (c in '"\'' or c == 'r' and
61 61 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
62 62 if c == 'r':
63 63 pos += 1
64 64 c = program[pos]
65 65 decode = lambda x: x
66 66 else:
67 67 decode = parser.unescapestr
68 68 pos += 1
69 69 s = pos
70 70 while pos < l: # find closing quote
71 71 d = program[pos]
72 72 if d == '\\': # skip over escaped characters
73 73 pos += 2
74 74 continue
75 75 if d == c:
76 76 yield ('string', decode(program[s:pos]), s)
77 77 break
78 78 pos += 1
79 79 else:
80 80 raise error.ParseError(_("unterminated string"), s)
81 81 elif c.isalnum() or c in globchars or ord(c) > 127:
82 82 # gather up a symbol/keyword
83 83 s = pos
84 84 pos += 1
85 85 while pos < l: # find end of symbol
86 86 d = program[pos]
87 87 if not (d.isalnum() or d in globchars or ord(d) > 127):
88 88 break
89 89 pos += 1
90 90 sym = program[s:pos]
91 91 if sym in keywords: # operator keywords
92 92 yield (sym, None, s)
93 93 else:
94 94 yield ('symbol', sym, s)
95 95 pos -= 1
96 96 else:
97 97 raise error.ParseError(_("syntax error"), pos)
98 98 pos += 1
99 99 yield ('end', None, pos)
100 100
101 101 def parse(expr):
102 102 p = parser.parser(elements)
103 103 tree, pos = p.parse(tokenize(expr))
104 104 if pos != len(expr):
105 105 raise error.ParseError(_("invalid token"), pos)
106 106 return tree
107 107
108 108 def getsymbol(x):
109 109 if x and x[0] == 'symbol':
110 110 return x[1]
111 111 raise error.ParseError(_('not a symbol'))
112 112
113 113 def getstring(x, err):
114 114 if x and (x[0] == 'string' or x[0] == 'symbol'):
115 115 return x[1]
116 116 raise error.ParseError(err)
117 117
118 118 def _getkindpat(x, y, allkinds, err):
119 119 kind = getsymbol(x)
120 120 pat = getstring(y, err)
121 121 if kind not in allkinds:
122 122 raise error.ParseError(_("invalid pattern kind: %s") % kind)
123 123 return '%s:%s' % (kind, pat)
124 124
125 125 def getpattern(x, allkinds, err):
126 126 if x and x[0] == 'kindpat':
127 127 return _getkindpat(x[1], x[2], allkinds, err)
128 128 return getstring(x, err)
129 129
130 130 def getlist(x):
131 131 if not x:
132 132 return []
133 133 if x[0] == 'list':
134 134 return getlist(x[1]) + [x[2]]
135 135 return [x]
136 136
137 137 def getargs(x, min, max, err):
138 138 l = getlist(x)
139 139 if len(l) < min or len(l) > max:
140 140 raise error.ParseError(err)
141 141 return l
142 142
143 def getset(mctx, x):
143 def getmatch(mctx, x):
144 144 if not x:
145 145 raise error.ParseError(_("missing argument"))
146 146 return methods[x[0]](mctx, *x[1:])
147 147
148 def stringset(mctx, x):
149 m = mctx.matcher([x])
150 return [f for f in mctx.subset if m(f)]
148 def stringmatch(mctx, x):
149 return mctx.matcher([x])
151 150
152 def kindpatset(mctx, x, y):
153 return stringset(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
151 def kindpatmatch(mctx, x, y):
152 return stringmatch(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
154 153 _("pattern must be a string")))
155 154
156 def andset(mctx, x, y):
157 xl = set(getset(mctx, x))
158 yl = getset(mctx, y)
159 return [f for f in yl if f in xl]
155 def andmatch(mctx, x, y):
156 xm = getmatch(mctx, x)
157 ym = getmatch(mctx, y)
158 return matchmod.intersectmatchers(xm, ym)
160 159
161 def orset(mctx, x, y):
162 # needs optimizing
163 xl = getset(mctx, x)
164 yl = getset(mctx, y)
165 return xl + [f for f in yl if f not in xl]
160 def ormatch(mctx, x, y):
161 xm = getmatch(mctx, x)
162 ym = getmatch(mctx, y)
163 return matchmod.unionmatcher([xm, ym])
166 164
167 def notset(mctx, x):
168 s = set(getset(mctx, x))
169 return [r for r in mctx.subset if r not in s]
165 def notmatch(mctx, x):
166 m = getmatch(mctx, x)
167 return mctx.predicate(lambda f: not m(f), predrepr=('<not %r>', m))
170 168
171 def minusset(mctx, x, y):
172 xl = getset(mctx, x)
173 yl = set(getset(mctx, y))
174 return [f for f in xl if f not in yl]
169 def minusmatch(mctx, x, y):
170 xm = getmatch(mctx, x)
171 ym = getmatch(mctx, y)
172 return matchmod.differencematcher(xm, ym)
175 173
176 def negateset(mctx, x):
174 def negatematch(mctx, x):
177 175 raise error.ParseError(_("can't use negate operator in this context"))
178 176
179 def listset(mctx, a, b):
177 def listmatch(mctx, x, y):
180 178 raise error.ParseError(_("can't use a list in this context"),
181 179 hint=_('see hg help "filesets.x or y"'))
182 180
183 181 def func(mctx, a, b):
184 182 funcname = getsymbol(a)
185 183 if funcname in symbols:
186 184 enabled = mctx._existingenabled
187 185 mctx._existingenabled = funcname in _existingcallers
188 186 try:
189 187 return symbols[funcname](mctx, b)
190 188 finally:
191 189 mctx._existingenabled = enabled
192 190
193 191 keep = lambda fn: getattr(fn, '__doc__', None) is not None
194 192
195 193 syms = [s for (s, fn) in symbols.items() if keep(fn)]
196 194 raise error.UnknownIdentifier(funcname, syms)
197 195
198 196 # symbols are callable like:
199 197 # fun(mctx, x)
200 198 # with:
201 199 # mctx - current matchctx instance
202 200 # x - argument in tree form
203 201 symbols = {}
204 202
205 203 # filesets using matchctx.status()
206 204 _statuscallers = set()
207 205
208 206 # filesets using matchctx.existing()
209 207 _existingcallers = set()
210 208
211 209 predicate = registrar.filesetpredicate()
212 210
213 211 @predicate('modified()', callstatus=True)
214 212 def modified(mctx, x):
215 213 """File that is modified according to :hg:`status`.
216 214 """
217 215 # i18n: "modified" is a keyword
218 216 getargs(x, 0, 0, _("modified takes no arguments"))
219 217 s = set(mctx.status().modified)
220 return [f for f in mctx.subset if f in s]
218 return mctx.predicate(s.__contains__, predrepr='modified')
221 219
222 220 @predicate('added()', callstatus=True)
223 221 def added(mctx, x):
224 222 """File that is added according to :hg:`status`.
225 223 """
226 224 # i18n: "added" is a keyword
227 225 getargs(x, 0, 0, _("added takes no arguments"))
228 226 s = set(mctx.status().added)
229 return [f for f in mctx.subset if f in s]
227 return mctx.predicate(s.__contains__, predrepr='added')
230 228
231 229 @predicate('removed()', callstatus=True)
232 230 def removed(mctx, x):
233 231 """File that is removed according to :hg:`status`.
234 232 """
235 233 # i18n: "removed" is a keyword
236 234 getargs(x, 0, 0, _("removed takes no arguments"))
237 235 s = set(mctx.status().removed)
238 return [f for f in mctx.subset if f in s]
236 return mctx.predicate(s.__contains__, predrepr='removed')
239 237
240 238 @predicate('deleted()', callstatus=True)
241 239 def deleted(mctx, x):
242 240 """Alias for ``missing()``.
243 241 """
244 242 # i18n: "deleted" is a keyword
245 243 getargs(x, 0, 0, _("deleted takes no arguments"))
246 244 s = set(mctx.status().deleted)
247 return [f for f in mctx.subset if f in s]
245 return mctx.predicate(s.__contains__, predrepr='deleted')
248 246
249 247 @predicate('missing()', callstatus=True)
250 248 def missing(mctx, x):
251 249 """File that is missing according to :hg:`status`.
252 250 """
253 251 # i18n: "missing" is a keyword
254 252 getargs(x, 0, 0, _("missing takes no arguments"))
255 253 s = set(mctx.status().deleted)
256 return [f for f in mctx.subset if f in s]
254 return mctx.predicate(s.__contains__, predrepr='deleted')
257 255
258 256 @predicate('unknown()', callstatus=True)
259 257 def unknown(mctx, x):
260 """File that is unknown according to :hg:`status`. These files will only be
261 considered if this predicate is used.
262 """
258 """File that is unknown according to :hg:`status`."""
263 259 # i18n: "unknown" is a keyword
264 260 getargs(x, 0, 0, _("unknown takes no arguments"))
265 261 s = set(mctx.status().unknown)
266 return [f for f in mctx.subset if f in s]
262 return mctx.predicate(s.__contains__, predrepr='unknown')
267 263
268 264 @predicate('ignored()', callstatus=True)
269 265 def ignored(mctx, x):
270 """File that is ignored according to :hg:`status`. These files will only be
271 considered if this predicate is used.
272 """
266 """File that is ignored according to :hg:`status`."""
273 267 # i18n: "ignored" is a keyword
274 268 getargs(x, 0, 0, _("ignored takes no arguments"))
275 269 s = set(mctx.status().ignored)
276 return [f for f in mctx.subset if f in s]
270 return mctx.predicate(s.__contains__, predrepr='ignored')
277 271
278 272 @predicate('clean()', callstatus=True)
279 273 def clean(mctx, x):
280 274 """File that is clean according to :hg:`status`.
281 275 """
282 276 # i18n: "clean" is a keyword
283 277 getargs(x, 0, 0, _("clean takes no arguments"))
284 278 s = set(mctx.status().clean)
285 return [f for f in mctx.subset if f in s]
279 return mctx.predicate(s.__contains__, predrepr='clean')
286 280
287 281 @predicate('tracked()')
288 282 def tracked(mctx, x):
289 283 """File that is under Mercurial control."""
290 284 # i18n: "tracked" is a keyword
291 285 getargs(x, 0, 0, _("tracked takes no arguments"))
292 return [f for f in mctx.subset if f in mctx.ctx]
286 return mctx.predicate(mctx.ctx.__contains__, predrepr='tracked')
293 287
294 288 @predicate('binary()', callexisting=True)
295 289 def binary(mctx, x):
296 290 """File that appears to be binary (contains NUL bytes).
297 291 """
298 292 # i18n: "binary" is a keyword
299 293 getargs(x, 0, 0, _("binary takes no arguments"))
300 return [f for f in mctx.existing() if mctx.ctx[f].isbinary()]
294 return mctx.fpredicate(lambda fctx: fctx.isbinary(),
295 predrepr='binary', cache=True)
301 296
302 297 @predicate('exec()', callexisting=True)
303 298 def exec_(mctx, x):
304 299 """File that is marked as executable.
305 300 """
306 301 # i18n: "exec" is a keyword
307 302 getargs(x, 0, 0, _("exec takes no arguments"))
308 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
303 ctx = mctx.ctx
304 return mctx.predicate(lambda f: ctx.flags(f) == 'x', predrepr='exec')
309 305
310 306 @predicate('symlink()', callexisting=True)
311 307 def symlink(mctx, x):
312 308 """File that is marked as a symlink.
313 309 """
314 310 # i18n: "symlink" is a keyword
315 311 getargs(x, 0, 0, _("symlink takes no arguments"))
316 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
312 ctx = mctx.ctx
313 return mctx.predicate(lambda f: ctx.flags(f) == 'l', predrepr='symlink')
317 314
318 315 @predicate('resolved()')
319 316 def resolved(mctx, x):
320 317 """File that is marked resolved according to :hg:`resolve -l`.
321 318 """
322 319 # i18n: "resolved" is a keyword
323 320 getargs(x, 0, 0, _("resolved takes no arguments"))
324 321 if mctx.ctx.rev() is not None:
325 return []
322 return mctx.never()
326 323 ms = merge.mergestate.read(mctx.ctx.repo())
327 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
324 return mctx.predicate(lambda f: f in ms and ms[f] == 'r',
325 predrepr='resolved')
328 326
329 327 @predicate('unresolved()')
330 328 def unresolved(mctx, x):
331 329 """File that is marked unresolved according to :hg:`resolve -l`.
332 330 """
333 331 # i18n: "unresolved" is a keyword
334 332 getargs(x, 0, 0, _("unresolved takes no arguments"))
335 333 if mctx.ctx.rev() is not None:
336 return []
334 return mctx.never()
337 335 ms = merge.mergestate.read(mctx.ctx.repo())
338 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
336 return mctx.predicate(lambda f: f in ms and ms[f] == 'u',
337 predrepr='unresolved')
339 338
340 339 @predicate('hgignore()')
341 340 def hgignore(mctx, x):
342 341 """File that matches the active .hgignore pattern.
343 342 """
344 343 # i18n: "hgignore" is a keyword
345 344 getargs(x, 0, 0, _("hgignore takes no arguments"))
346 ignore = mctx.ctx.repo().dirstate._ignore
347 return [f for f in mctx.subset if ignore(f)]
345 return mctx.ctx.repo().dirstate._ignore
348 346
349 347 @predicate('portable()')
350 348 def portable(mctx, x):
351 349 """File that has a portable name. (This doesn't include filenames with case
352 350 collisions.)
353 351 """
354 352 # i18n: "portable" is a keyword
355 353 getargs(x, 0, 0, _("portable takes no arguments"))
356 checkwinfilename = util.checkwinfilename
357 return [f for f in mctx.subset if checkwinfilename(f) is None]
354 return mctx.predicate(lambda f: util.checkwinfilename(f) is None,
355 predrepr='portable')
358 356
359 357 @predicate('grep(regex)', callexisting=True)
360 358 def grep(mctx, x):
361 359 """File contains the given regular expression.
362 360 """
363 361 try:
364 362 # i18n: "grep" is a keyword
365 363 r = re.compile(getstring(x, _("grep requires a pattern")))
366 364 except re.error as e:
367 365 raise error.ParseError(_('invalid match pattern: %s') %
368 366 stringutil.forcebytestr(e))
369 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
367 return mctx.fpredicate(lambda fctx: r.search(fctx.data()),
368 predrepr=('grep(%r)', r.pattern), cache=True)
370 369
371 370 def _sizetomax(s):
372 371 try:
373 372 s = s.strip().lower()
374 373 for k, v in util._sizeunits:
375 374 if s.endswith(k):
376 375 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
377 376 n = s[:-len(k)]
378 377 inc = 1.0
379 378 if "." in n:
380 379 inc /= 10 ** len(n.split(".")[1])
381 380 return int((float(n) + inc) * v) - 1
382 381 # no extension, this is a precise value
383 382 return int(s)
384 383 except ValueError:
385 384 raise error.ParseError(_("couldn't parse size: %s") % s)
386 385
387 386 def sizematcher(expr):
388 387 """Return a function(size) -> bool from the ``size()`` expression"""
389 388 expr = expr.strip()
390 389 if '-' in expr: # do we have a range?
391 390 a, b = expr.split('-', 1)
392 391 a = util.sizetoint(a)
393 392 b = util.sizetoint(b)
394 393 return lambda x: x >= a and x <= b
395 394 elif expr.startswith("<="):
396 395 a = util.sizetoint(expr[2:])
397 396 return lambda x: x <= a
398 397 elif expr.startswith("<"):
399 398 a = util.sizetoint(expr[1:])
400 399 return lambda x: x < a
401 400 elif expr.startswith(">="):
402 401 a = util.sizetoint(expr[2:])
403 402 return lambda x: x >= a
404 403 elif expr.startswith(">"):
405 404 a = util.sizetoint(expr[1:])
406 405 return lambda x: x > a
407 406 else:
408 407 a = util.sizetoint(expr)
409 408 b = _sizetomax(expr)
410 409 return lambda x: x >= a and x <= b
411 410
412 411 @predicate('size(expression)', callexisting=True)
413 412 def size(mctx, x):
414 413 """File size matches the given expression. Examples:
415 414
416 415 - size('1k') - files from 1024 to 2047 bytes
417 416 - size('< 20k') - files less than 20480 bytes
418 417 - size('>= .5MB') - files at least 524288 bytes
419 418 - size('4k - 1MB') - files from 4096 bytes to 1048576 bytes
420 419 """
421 420 # i18n: "size" is a keyword
422 421 expr = getstring(x, _("size requires an expression"))
423 422 m = sizematcher(expr)
424 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
423 return mctx.fpredicate(lambda fctx: m(fctx.size()),
424 predrepr=('size(%r)', expr), cache=True)
425 425
426 426 @predicate('encoding(name)', callexisting=True)
427 427 def encoding(mctx, x):
428 428 """File can be successfully decoded with the given character
429 429 encoding. May not be useful for encodings other than ASCII and
430 430 UTF-8.
431 431 """
432 432
433 433 # i18n: "encoding" is a keyword
434 434 enc = getstring(x, _("encoding requires an encoding name"))
435 435
436 s = []
437 for f in mctx.existing():
438 d = mctx.ctx[f].data()
436 def encp(fctx):
437 d = fctx.data()
439 438 try:
440 439 d.decode(pycompat.sysstr(enc))
440 return True
441 441 except LookupError:
442 442 raise error.Abort(_("unknown encoding '%s'") % enc)
443 443 except UnicodeDecodeError:
444 continue
445 s.append(f)
444 return False
446 445
447 return s
446 return mctx.fpredicate(encp, predrepr=('encoding(%r)', enc), cache=True)
448 447
449 448 @predicate('eol(style)', callexisting=True)
450 449 def eol(mctx, x):
451 450 """File contains newlines of the given style (dos, unix, mac). Binary
452 451 files are excluded, files with mixed line endings match multiple
453 452 styles.
454 453 """
455 454
456 455 # i18n: "eol" is a keyword
457 456 enc = getstring(x, _("eol requires a style name"))
458 457
459 s = []
460 for f in mctx.existing():
461 fctx = mctx.ctx[f]
458 def eolp(fctx):
462 459 if fctx.isbinary():
463 continue
460 return False
464 461 d = fctx.data()
465 462 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
466 s.append(f)
463 return True
467 464 elif enc == 'unix' and re.search('(?<!\r)\n', d):
468 s.append(f)
465 return True
469 466 elif enc == 'mac' and re.search('\r(?!\n)', d):
470 s.append(f)
471 return s
467 return True
468 return False
469 return mctx.fpredicate(eolp, predrepr=('eol(%r)', enc), cache=True)
472 470
473 471 @predicate('copied()')
474 472 def copied(mctx, x):
475 473 """File that is recorded as being copied.
476 474 """
477 475 # i18n: "copied" is a keyword
478 476 getargs(x, 0, 0, _("copied takes no arguments"))
479 s = []
480 for f in mctx.subset:
481 if f in mctx.ctx:
482 p = mctx.ctx[f].parents()
483 if p and p[0].path() != f:
484 s.append(f)
485 return s
477 def copiedp(fctx):
478 p = fctx.parents()
479 return p and p[0].path() != fctx.path()
480 return mctx.fpredicate(copiedp, predrepr='copied', cache=True)
486 481
487 482 @predicate('revs(revs, pattern)')
488 483 def revs(mctx, x):
489 484 """Evaluate set in the specified revisions. If the revset match multiple
490 485 revs, this will return file matching pattern in any of the revision.
491 486 """
492 487 # i18n: "revs" is a keyword
493 488 r, x = getargs(x, 2, 2, _("revs takes two arguments"))
494 489 # i18n: "revs" is a keyword
495 490 revspec = getstring(r, _("first argument to revs must be a revision"))
496 491 repo = mctx.ctx.repo()
497 492 revs = scmutil.revrange(repo, [revspec])
498 493
499 found = set()
500 result = []
494 matchers = []
501 495 for r in revs:
502 496 ctx = repo[r]
503 for f in getset(mctx.switch(ctx, _buildstatus(ctx, x)), x):
504 if f not in found:
505 found.add(f)
506 result.append(f)
507 return result
497 matchers.append(getmatch(mctx.switch(ctx, _buildstatus(ctx, x)), x))
498 if not matchers:
499 return mctx.never()
500 if len(matchers) == 1:
501 return matchers[0]
502 return matchmod.unionmatcher(matchers)
508 503
509 504 @predicate('status(base, rev, pattern)')
510 505 def status(mctx, x):
511 506 """Evaluate predicate using status change between ``base`` and
512 507 ``rev``. Examples:
513 508
514 509 - ``status(3, 7, added())`` - matches files added from "3" to "7"
515 510 """
516 511 repo = mctx.ctx.repo()
517 512 # i18n: "status" is a keyword
518 513 b, r, x = getargs(x, 3, 3, _("status takes three arguments"))
519 514 # i18n: "status" is a keyword
520 515 baseerr = _("first argument to status must be a revision")
521 516 baserevspec = getstring(b, baseerr)
522 517 if not baserevspec:
523 518 raise error.ParseError(baseerr)
524 519 reverr = _("second argument to status must be a revision")
525 520 revspec = getstring(r, reverr)
526 521 if not revspec:
527 522 raise error.ParseError(reverr)
528 523 basectx, ctx = scmutil.revpair(repo, [baserevspec, revspec])
529 return getset(mctx.switch(ctx, _buildstatus(ctx, x, basectx=basectx)), x)
524 return getmatch(mctx.switch(ctx, _buildstatus(ctx, x, basectx=basectx)), x)
530 525
531 526 @predicate('subrepo([pattern])')
532 527 def subrepo(mctx, x):
533 528 """Subrepositories whose paths match the given pattern.
534 529 """
535 530 # i18n: "subrepo" is a keyword
536 531 getargs(x, 0, 1, _("subrepo takes at most one argument"))
537 532 ctx = mctx.ctx
538 sstate = sorted(ctx.substate)
533 sstate = ctx.substate
539 534 if x:
540 535 pat = getpattern(x, matchmod.allpatternkinds,
541 536 # i18n: "subrepo" is a keyword
542 537 _("subrepo requires a pattern or no arguments"))
543 538 fast = not matchmod.patkind(pat)
544 539 if fast:
545 540 def m(s):
546 541 return (s == pat)
547 542 else:
548 543 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
549 return [sub for sub in sstate if m(sub)]
544 return mctx.predicate(lambda f: f in sstate and m(f),
545 predrepr=('subrepo(%r)', pat))
550 546 else:
551 return [sub for sub in sstate]
547 return mctx.predicate(sstate.__contains__, predrepr='subrepo')
552 548
553 549 methods = {
554 'string': stringset,
555 'symbol': stringset,
556 'kindpat': kindpatset,
557 'and': andset,
558 'or': orset,
559 'minus': minusset,
560 'negate': negateset,
561 'list': listset,
562 'group': getset,
563 'not': notset,
550 'string': stringmatch,
551 'symbol': stringmatch,
552 'kindpat': kindpatmatch,
553 'and': andmatch,
554 'or': ormatch,
555 'minus': minusmatch,
556 'negate': negatematch,
557 'list': listmatch,
558 'group': getmatch,
559 'not': notmatch,
564 560 'func': func,
565 561 }
566 562
567 563 class matchctx(object):
568 564 def __init__(self, ctx, subset, status=None, badfn=None):
569 565 self.ctx = ctx
570 566 self.subset = subset
571 567 self._status = status
572 568 self._badfn = badfn
573 569 self._existingenabled = False
574 570 def status(self):
575 571 return self._status
576 572
577 573 def matcher(self, patterns):
578 574 return self.ctx.match(patterns, badfn=self._badfn)
579 575
580 576 def predicate(self, predfn, predrepr=None, cache=False):
581 577 """Create a matcher to select files by predfn(filename)"""
582 578 if cache:
583 579 predfn = util.cachefunc(predfn)
584 580 repo = self.ctx.repo()
585 581 return matchmod.predicatematcher(repo.root, repo.getcwd(), predfn,
586 582 predrepr=predrepr, badfn=self._badfn)
587 583
588 584 def fpredicate(self, predfn, predrepr=None, cache=False):
589 585 """Create a matcher to select files by predfn(fctx) at the current
590 586 revision
591 587
592 588 Missing files are ignored.
593 589 """
594 590 ctx = self.ctx
595 591 if ctx.rev() is None:
596 592 def fctxpredfn(f):
597 593 try:
598 594 fctx = ctx[f]
599 595 except error.LookupError:
600 596 return False
601 597 try:
602 598 fctx.audit()
603 599 except error.Abort:
604 600 return False
605 601 try:
606 602 return predfn(fctx)
607 603 except (IOError, OSError) as e:
608 604 if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EISDIR):
609 605 return False
610 606 raise
611 607 else:
612 608 def fctxpredfn(f):
613 609 try:
614 610 fctx = ctx[f]
615 611 except error.LookupError:
616 612 return False
617 613 return predfn(fctx)
618 614 return self.predicate(fctxpredfn, predrepr=predrepr, cache=cache)
619 615
620 616 def never(self):
621 617 """Create a matcher to select nothing"""
622 618 repo = self.ctx.repo()
623 619 return matchmod.nevermatcher(repo.root, repo.getcwd(),
624 620 badfn=self._badfn)
625 621
626 622 def filter(self, files):
627 623 return [f for f in files if f in self.subset]
628 624 def existing(self):
629 625 if not self._existingenabled:
630 626 raise error.ProgrammingError('unexpected existing() invocation')
631 627 if self._status is not None:
632 628 removed = set(self._status[3])
633 629 unknown = set(self._status[4] + self._status[5])
634 630 else:
635 631 removed = set()
636 632 unknown = set()
637 633 return (f for f in self.subset
638 634 if (f in self.ctx and f not in removed) or f in unknown)
639 635
640 636 def switch(self, ctx, status=None):
641 637 subset = self.filter(_buildsubset(ctx, status))
642 638 return matchctx(ctx, subset, status, self._badfn)
643 639
644 640 class fullmatchctx(matchctx):
645 641 """A match context where any files in any revisions should be valid"""
646 642
647 643 def __init__(self, ctx, status=None, badfn=None):
648 644 subset = _buildsubset(ctx, status)
649 645 super(fullmatchctx, self).__init__(ctx, subset, status, badfn)
650 646 def switch(self, ctx, status=None):
651 647 return fullmatchctx(ctx, status, self._badfn)
652 648
653 649 # filesets using matchctx.switch()
654 650 _switchcallers = [
655 651 'revs',
656 652 'status',
657 653 ]
658 654
659 655 def _intree(funcs, tree):
660 656 if isinstance(tree, tuple):
661 657 if tree[0] == 'func' and tree[1][0] == 'symbol':
662 658 if tree[1][1] in funcs:
663 659 return True
664 660 if tree[1][1] in _switchcallers:
665 661 # arguments won't be evaluated in the current context
666 662 return False
667 663 for s in tree[1:]:
668 664 if _intree(funcs, s):
669 665 return True
670 666 return False
671 667
672 668 def _buildsubset(ctx, status):
673 669 if status:
674 670 subset = []
675 671 for c in status:
676 672 subset.extend(c)
677 673 return subset
678 674 else:
679 675 return list(ctx.walk(ctx.match([])))
680 676
681 677 def match(ctx, expr, badfn=None):
682 678 """Create a matcher for a single fileset expression"""
683 repo = ctx.repo()
684 679 tree = parse(expr)
685 fset = getset(fullmatchctx(ctx, _buildstatus(ctx, tree), badfn=badfn), tree)
686 return matchmod.predicatematcher(repo.root, repo.getcwd(),
687 fset.__contains__,
688 predrepr='fileset', badfn=badfn)
680 mctx = fullmatchctx(ctx, _buildstatus(ctx, tree), badfn=badfn)
681 return getmatch(mctx, tree)
689 682
690 683 def _buildstatus(ctx, tree, basectx=None):
691 684 # do we need status info?
692 685
693 686 # temporaty boolean to simplify the next conditional
694 687 purewdir = ctx.rev() is None and basectx is None
695 688
696 689 if (_intree(_statuscallers, tree) or
697 690 # Using matchctx.existing() on a workingctx requires us to check
698 691 # for deleted files.
699 692 (purewdir and _intree(_existingcallers, tree))):
700 693 unknown = _intree(['unknown'], tree)
701 694 ignored = _intree(['ignored'], tree)
702 695
703 696 r = ctx.repo()
704 697 if basectx is None:
705 698 basectx = ctx.p1()
706 699 return r.status(basectx, ctx,
707 700 unknown=unknown, ignored=ignored, clean=True)
708 701 else:
709 702 return None
710 703
711 704 def prettyformat(tree):
712 705 return parser.prettyformat(tree, ('string', 'symbol'))
713 706
714 707 def loadpredicate(ui, extname, registrarobj):
715 708 """Load fileset predicates from specified registrarobj
716 709 """
717 710 for name, func in registrarobj._table.iteritems():
718 711 symbols[name] = func
719 712 if func._callstatus:
720 713 _statuscallers.add(name)
721 714 if func._callexisting:
722 715 _existingcallers.add(name)
723 716
724 717 # load built-in predicates explicitly to setup _statuscallers/_existingcallers
725 718 loadpredicate(None, None, predicate)
726 719
727 720 # tell hggettext to extract docstrings from these functions:
728 721 i18nfunctions = symbols.values()
@@ -1,676 +1,697 b''
1 1 $ fileset() {
2 2 > hg debugfileset --all-files "$@"
3 3 > }
4 4
5 5 $ hg init repo
6 6 $ cd repo
7 7 $ echo a > a1
8 8 $ echo a > a2
9 9 $ echo b > b1
10 10 $ echo b > b2
11 11 $ hg ci -Am addfiles
12 12 adding a1
13 13 adding a2
14 14 adding b1
15 15 adding b2
16 16
17 17 Test operators and basic patterns
18 18
19 19 $ fileset -v a1
20 20 (symbol 'a1')
21 21 a1
22 22 $ fileset -v 'a*'
23 23 (symbol 'a*')
24 24 a1
25 25 a2
26 26 $ fileset -v '"re:a\d"'
27 27 (string 're:a\\d')
28 28 a1
29 29 a2
30 30 $ fileset -v '!re:"a\d"'
31 31 (not
32 32 (kindpat
33 33 (symbol 're')
34 34 (string 'a\\d')))
35 35 b1
36 36 b2
37 37 $ fileset -v 'path:a1 or glob:b?'
38 38 (or
39 39 (kindpat
40 40 (symbol 'path')
41 41 (symbol 'a1'))
42 42 (kindpat
43 43 (symbol 'glob')
44 44 (symbol 'b?')))
45 45 a1
46 46 b1
47 47 b2
48 48 $ fileset -v 'a1 or a2'
49 49 (or
50 50 (symbol 'a1')
51 51 (symbol 'a2'))
52 52 a1
53 53 a2
54 54 $ fileset 'a1 | a2'
55 55 a1
56 56 a2
57 57 $ fileset 'a* and "*1"'
58 58 a1
59 59 $ fileset 'a* & "*1"'
60 60 a1
61 61 $ fileset 'not (r"a*")'
62 62 b1
63 63 b2
64 64 $ fileset '! ("a*")'
65 65 b1
66 66 b2
67 67 $ fileset 'a* - a1'
68 68 a2
69 69 $ fileset 'a_b'
70 70 $ fileset '"\xy"'
71 71 hg: parse error: invalid \x escape* (glob)
72 72 [255]
73 73
74 74 Test invalid syntax
75 75
76 76 $ fileset -v '"added"()'
77 77 (func
78 78 (string 'added')
79 79 None)
80 80 hg: parse error: not a symbol
81 81 [255]
82 82 $ fileset -v '()()'
83 83 (func
84 84 (group
85 85 None)
86 86 None)
87 87 hg: parse error: not a symbol
88 88 [255]
89 89 $ fileset -v -- '-x'
90 90 (negate
91 91 (symbol 'x'))
92 92 hg: parse error: can't use negate operator in this context
93 93 [255]
94 94 $ fileset -v -- '-()'
95 95 (negate
96 96 (group
97 97 None))
98 98 hg: parse error: can't use negate operator in this context
99 99 [255]
100 100
101 101 $ fileset '"path":.'
102 102 hg: parse error: not a symbol
103 103 [255]
104 104 $ fileset 'path:foo bar'
105 105 hg: parse error at 9: invalid token
106 106 [255]
107 107 $ fileset 'foo:bar:baz'
108 108 hg: parse error: not a symbol
109 109 [255]
110 110 $ fileset 'foo:bar()'
111 111 hg: parse error: pattern must be a string
112 112 [255]
113 113 $ fileset 'foo:bar'
114 114 hg: parse error: invalid pattern kind: foo
115 115 [255]
116 116
117 117 Test files status
118 118
119 119 $ rm a1
120 120 $ hg rm a2
121 121 $ echo b >> b2
122 122 $ hg cp b1 c1
123 123 $ echo c > c2
124 124 $ echo c > c3
125 125 $ cat > .hgignore <<EOF
126 126 > \.hgignore
127 127 > 2$
128 128 > EOF
129 129 $ fileset 'modified()'
130 130 b2
131 131 $ fileset 'added()'
132 132 c1
133 133 $ fileset 'removed()'
134 134 a2
135 135 $ fileset 'deleted()'
136 136 a1
137 137 $ fileset 'missing()'
138 138 a1
139 139 $ fileset 'unknown()'
140 140 c3
141 141 $ fileset 'ignored()'
142 142 .hgignore
143 143 c2
144 144 $ fileset 'hgignore()'
145 .hgignore
145 146 a2
146 147 b2
148 c2
147 149 $ fileset 'clean()'
148 150 b1
149 151 $ fileset 'copied()'
150 152 c1
151 153
152 154 Test files status in different revisions
153 155
154 156 $ hg status -m
155 157 M b2
156 158 $ fileset -r0 'revs("wdir()", modified())' --traceback
157 159 b2
158 160 $ hg status -a
159 161 A c1
160 162 $ fileset -r0 'revs("wdir()", added())'
161 163 c1
162 164 $ hg status --change 0 -a
163 165 A a1
164 166 A a2
165 167 A b1
166 168 A b2
167 169 $ hg status -mru
168 170 M b2
169 171 R a2
170 172 ? c3
171 173 $ fileset -r0 'added() and revs("wdir()", modified() or removed() or unknown())'
172 174 a2
173 175 b2
174 176 $ fileset -r0 'added() or revs("wdir()", added())'
175 177 a1
176 178 a2
177 179 b1
178 180 b2
179 181 c1
180 182
181 183 Test files properties
182 184
183 185 >>> open('bin', 'wb').write(b'\0a') and None
184 186 $ fileset 'binary()'
187 bin
185 188 $ fileset 'binary() and unknown()'
186 189 bin
187 190 $ echo '^bin$' >> .hgignore
188 191 $ fileset 'binary() and ignored()'
189 192 bin
190 193 $ hg add bin
191 194 $ fileset 'binary()'
192 195 bin
193 196
194 197 $ fileset 'grep("b{1}")'
198 .hgignore
195 199 b1
196 200 b2
197 201 c1
198 202 $ fileset 'grep("missingparens(")'
199 203 hg: parse error: invalid match pattern: (unbalanced parenthesis|missing \)).* (re)
200 204 [255]
201 205
202 206 #if execbit
203 207 $ chmod +x b2
204 208 $ fileset 'exec()'
205 209 b2
206 210 #endif
207 211
208 212 #if symlink
209 213 $ ln -s b2 b2link
210 214 $ fileset 'symlink() and unknown()'
211 215 b2link
212 216 $ hg add b2link
213 217 #endif
214 218
215 219 #if no-windows
216 220 $ echo foo > con.xml
217 221 $ fileset 'not portable()'
218 222 con.xml
219 223 $ hg --config ui.portablefilenames=ignore add con.xml
220 224 #endif
221 225
222 226 >>> open('1k', 'wb').write(b' '*1024) and None
223 227 >>> open('2k', 'wb').write(b' '*2048) and None
224 228 $ hg add 1k 2k
225 229 $ fileset 'size("bar")'
226 230 hg: parse error: couldn't parse size: bar
227 231 [255]
228 232 $ fileset '(1k, 2k)'
229 233 hg: parse error: can't use a list in this context
230 234 (see hg help "filesets.x or y")
231 235 [255]
232 236 $ fileset 'size(1k)'
233 237 1k
234 238 $ fileset '(1k or 2k) and size("< 2k")'
235 239 1k
236 240 $ fileset '(1k or 2k) and size("<=2k")'
237 241 1k
238 242 2k
239 243 $ fileset '(1k or 2k) and size("> 1k")'
240 244 2k
241 245 $ fileset '(1k or 2k) and size(">=1K")'
242 246 1k
243 247 2k
244 248 $ fileset '(1k or 2k) and size(".5KB - 1.5kB")'
245 249 1k
246 250 $ fileset 'size("1M")'
247 251 $ fileset 'size("1 GB")'
248 252
249 253 Test merge states
250 254
251 255 $ hg ci -m manychanges
252 256 $ hg file -r . 'set:copied() & modified()'
253 257 [1]
254 258 $ hg up -C 0
255 259 * files updated, 0 files merged, * files removed, 0 files unresolved (glob)
256 260 $ echo c >> b2
257 261 $ hg ci -m diverging b2
258 262 created new head
259 263 $ fileset 'resolved()'
260 264 $ fileset 'unresolved()'
261 265 $ hg merge
262 266 merging b2
263 267 warning: conflicts while merging b2! (edit, then use 'hg resolve --mark')
264 268 * files updated, 0 files merged, 1 files removed, 1 files unresolved (glob)
265 269 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
266 270 [1]
267 271 $ fileset 'resolved()'
268 272 $ fileset 'unresolved()'
269 273 b2
270 274 $ echo e > b2
271 275 $ hg resolve -m b2
272 276 (no more unresolved files)
273 277 $ fileset 'resolved()'
274 278 b2
275 279 $ fileset 'unresolved()'
276 280 $ hg ci -m merge
277 281
278 282 Test subrepo predicate
279 283
280 284 $ hg init sub
281 285 $ echo a > sub/suba
282 286 $ hg -R sub add sub/suba
283 287 $ hg -R sub ci -m sub
284 288 $ echo 'sub = sub' > .hgsub
285 289 $ hg init sub2
286 290 $ echo b > sub2/b
287 291 $ hg -R sub2 ci -Am sub2
288 292 adding b
289 293 $ echo 'sub2 = sub2' >> .hgsub
290 294 $ fileset 'subrepo()'
291 295 $ hg add .hgsub
292 296 $ fileset 'subrepo()'
293 297 sub
294 298 sub2
295 299 $ fileset 'subrepo("sub")'
296 300 sub
297 301 $ fileset 'subrepo("glob:*")'
298 302 sub
299 303 sub2
300 304 $ hg ci -m subrepo
301 305
302 306 Test that .hgsubstate is updated as appropriate during a conversion. The
303 307 saverev property is enough to alter the hashes of the subrepo.
304 308
305 309 $ hg init ../converted
306 310 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
307 311 > sub ../converted/sub
308 312 initializing destination ../converted/sub repository
309 313 scanning source...
310 314 sorting...
311 315 converting...
312 316 0 sub
313 317 $ hg clone -U sub2 ../converted/sub2
314 318 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
315 319 > . ../converted
316 320 scanning source...
317 321 sorting...
318 322 converting...
319 323 4 addfiles
320 324 3 manychanges
321 325 2 diverging
322 326 1 merge
323 327 0 subrepo
324 328 no ".hgsubstate" updates will be made for "sub2"
325 329 $ hg up -q -R ../converted -r tip
326 330 $ hg --cwd ../converted cat sub/suba sub2/b -r tip
327 331 a
328 332 b
329 333 $ oldnode=`hg log -r tip -T "{node}\n"`
330 334 $ newnode=`hg log -R ../converted -r tip -T "{node}\n"`
331 335 $ [ "$oldnode" != "$newnode" ] || echo "nothing changed"
332 336
333 337 Test with a revision
334 338
335 339 $ hg log -G --template '{rev} {desc}\n'
336 340 @ 4 subrepo
337 341 |
338 342 o 3 merge
339 343 |\
340 344 | o 2 diverging
341 345 | |
342 346 o | 1 manychanges
343 347 |/
344 348 o 0 addfiles
345 349
346 350 $ echo unknown > unknown
347 351 $ fileset -r1 'modified()'
348 352 b2
349 353 $ fileset -r1 'added() and c1'
350 354 c1
351 355 $ fileset -r1 'removed()'
352 356 a2
353 357 $ fileset -r1 'deleted()'
354 358 $ fileset -r1 'unknown()'
355 359 $ fileset -r1 'ignored()'
356 360 $ fileset -r1 'hgignore()'
361 .hgignore
362 a2
357 363 b2
358 364 bin
365 c2
366 sub2
359 367 $ fileset -r1 'binary()'
360 368 bin
361 369 $ fileset -r1 'size(1k)'
362 370 1k
363 371 $ fileset -r3 'resolved()'
364 372 $ fileset -r3 'unresolved()'
365 373
366 374 #if execbit
367 375 $ fileset -r1 'exec()'
368 376 b2
369 377 #endif
370 378
371 379 #if symlink
372 380 $ fileset -r1 'symlink()'
373 381 b2link
374 382 #endif
375 383
376 384 #if no-windows
377 385 $ fileset -r1 'not portable()'
378 386 con.xml
379 387 $ hg forget 'con.xml'
380 388 #endif
381 389
382 390 $ fileset -r4 'subrepo("re:su.*")'
383 391 sub
384 392 sub2
385 393 $ fileset -r4 'subrepo(re:su.*)'
386 394 sub
387 395 sub2
388 396 $ fileset -r4 'subrepo("sub")'
389 397 sub
390 398 $ fileset -r4 'b2 or c1'
391 399 b2
392 400 c1
393 401
394 402 >>> open('dos', 'wb').write(b"dos\r\n") and None
395 403 >>> open('mixed', 'wb').write(b"dos\r\nunix\n") and None
396 404 >>> open('mac', 'wb').write(b"mac\r") and None
397 405 $ hg add dos mixed mac
398 406
399 407 (remove a1, to examine safety of 'eol' on removed files)
400 408 $ rm a1
401 409
402 410 $ fileset 'eol(dos)'
403 411 dos
404 412 mixed
405 413 $ fileset 'eol(unix)'
414 .hgignore
406 415 .hgsub
407 416 .hgsubstate
408 417 b1
409 418 b2
419 b2.orig
410 420 c1
421 c2
422 c3
423 con.xml
411 424 mixed
425 unknown
412 426 $ fileset 'eol(mac)'
413 427 mac
414 428
415 429 Test safety of 'encoding' on removed files
416 430
417 431 $ fileset 'encoding("ascii")'
432 .hgignore
418 433 .hgsub
419 434 .hgsubstate
420 435 1k
421 436 2k
422 437 b1
423 438 b2
439 b2.orig
424 440 b2link (symlink !)
425 441 bin
426 442 c1
443 c2
444 c3
445 con.xml
427 446 dos
428 447 mac
429 448 mixed
449 unknown
430 450
431 451 Test detection of unintentional 'matchctx.existing()' invocation
432 452
433 453 $ cat > $TESTTMP/existingcaller.py <<EOF
434 454 > from mercurial import registrar
435 455 >
436 456 > filesetpredicate = registrar.filesetpredicate()
437 457 > @filesetpredicate(b'existingcaller()', callexisting=False)
438 458 > def existingcaller(mctx, x):
439 459 > # this 'mctx.existing()' invocation is unintentional
440 > return [f for f in mctx.existing()]
460 > existing = set(mctx.existing())
461 > return mctx.predicate(existing.__contains__, cache=False)
441 462 > EOF
442 463
443 464 $ cat >> .hg/hgrc <<EOF
444 465 > [extensions]
445 466 > existingcaller = $TESTTMP/existingcaller.py
446 467 > EOF
447 468
448 469 $ fileset 'existingcaller()' 2>&1 | tail -1
449 470 *ProgrammingError: *unexpected existing() invocation* (glob)
450 471
451 472 Test 'revs(...)'
452 473 ================
453 474
454 475 small reminder of the repository state
455 476
456 477 $ hg log -G
457 478 @ changeset: 4:* (glob)
458 479 | tag: tip
459 480 | user: test
460 481 | date: Thu Jan 01 00:00:00 1970 +0000
461 482 | summary: subrepo
462 483 |
463 484 o changeset: 3:* (glob)
464 485 |\ parent: 2:55b05bdebf36
465 486 | | parent: 1:* (glob)
466 487 | | user: test
467 488 | | date: Thu Jan 01 00:00:00 1970 +0000
468 489 | | summary: merge
469 490 | |
470 491 | o changeset: 2:55b05bdebf36
471 492 | | parent: 0:8a9576c51c1f
472 493 | | user: test
473 494 | | date: Thu Jan 01 00:00:00 1970 +0000
474 495 | | summary: diverging
475 496 | |
476 497 o | changeset: 1:* (glob)
477 498 |/ user: test
478 499 | date: Thu Jan 01 00:00:00 1970 +0000
479 500 | summary: manychanges
480 501 |
481 502 o changeset: 0:8a9576c51c1f
482 503 user: test
483 504 date: Thu Jan 01 00:00:00 1970 +0000
484 505 summary: addfiles
485 506
486 507 $ hg status --change 0
487 508 A a1
488 509 A a2
489 510 A b1
490 511 A b2
491 512 $ hg status --change 1
492 513 M b2
493 514 A 1k
494 515 A 2k
495 516 A b2link (no-windows !)
496 517 A bin
497 518 A c1
498 519 A con.xml (no-windows !)
499 520 R a2
500 521 $ hg status --change 2
501 522 M b2
502 523 $ hg status --change 3
503 524 M b2
504 525 A 1k
505 526 A 2k
506 527 A b2link (no-windows !)
507 528 A bin
508 529 A c1
509 530 A con.xml (no-windows !)
510 531 R a2
511 532 $ hg status --change 4
512 533 A .hgsub
513 534 A .hgsubstate
514 535 $ hg status
515 536 A dos
516 537 A mac
517 538 A mixed
518 539 R con.xml (no-windows !)
519 540 ! a1
520 541 ? b2.orig
521 542 ? c3
522 543 ? unknown
523 544
524 545 Test files at -r0 should be filtered by files at wdir
525 546 -----------------------------------------------------
526 547
527 548 $ fileset -r0 'tracked() and revs("wdir()", tracked())'
528 549 a1
529 550 b1
530 551 b2
531 552
532 553 Test that "revs()" work at all
533 554 ------------------------------
534 555
535 556 $ fileset "revs('2', modified())"
536 557 b2
537 558
538 559 Test that "revs()" work for file missing in the working copy/current context
539 560 ----------------------------------------------------------------------------
540 561
541 562 (a2 not in working copy)
542 563
543 564 $ fileset "revs('0', added())"
544 565 a1
545 566 a2
546 567 b1
547 568 b2
548 569
549 570 (none of the file exist in "0")
550 571
551 572 $ fileset -r 0 "revs('4', added())"
552 573 .hgsub
553 574 .hgsubstate
554 575
555 576 Call with empty revset
556 577 --------------------------
557 578
558 579 $ fileset "revs('2-2', modified())"
559 580
560 581 Call with revset matching multiple revs
561 582 ---------------------------------------
562 583
563 584 $ fileset "revs('0+4', added())"
564 585 .hgsub
565 586 .hgsubstate
566 587 a1
567 588 a2
568 589 b1
569 590 b2
570 591
571 592 overlapping set
572 593
573 594 $ fileset "revs('1+2', modified())"
574 595 b2
575 596
576 597 test 'status(...)'
577 598 =================
578 599
579 600 Simple case
580 601 -----------
581 602
582 603 $ fileset "status(3, 4, added())"
583 604 .hgsub
584 605 .hgsubstate
585 606
586 607 use rev to restrict matched file
587 608 -----------------------------------------
588 609
589 610 $ hg status --removed --rev 0 --rev 1
590 611 R a2
591 612 $ fileset "status(0, 1, removed())"
592 613 a2
593 614 $ fileset "tracked() and status(0, 1, removed())"
594 615 $ fileset -r 4 "status(0, 1, removed())"
595 616 a2
596 617 $ fileset -r 4 "tracked() and status(0, 1, removed())"
597 618 $ fileset "revs('4', tracked() and status(0, 1, removed()))"
598 619 $ fileset "revs('0', tracked() and status(0, 1, removed()))"
599 620 a2
600 621
601 622 check wdir()
602 623 ------------
603 624
604 625 $ hg status --removed --rev 4
605 626 R con.xml (no-windows !)
606 627 $ fileset "status(4, 'wdir()', removed())"
607 628 con.xml (no-windows !)
608 629
609 630 $ hg status --removed --rev 2
610 631 R a2
611 632 $ fileset "status('2', 'wdir()', removed())"
612 633 a2
613 634
614 635 test backward status
615 636 --------------------
616 637
617 638 $ hg status --removed --rev 0 --rev 4
618 639 R a2
619 640 $ hg status --added --rev 4 --rev 0
620 641 A a2
621 642 $ fileset "status(4, 0, added())"
622 643 a2
623 644
624 645 test cross branch status
625 646 ------------------------
626 647
627 648 $ hg status --added --rev 1 --rev 2
628 649 A a2
629 650 $ fileset "status(1, 2, added())"
630 651 a2
631 652
632 653 test with multi revs revset
633 654 ---------------------------
634 655 $ hg status --added --rev 0:1 --rev 3:4
635 656 A .hgsub
636 657 A .hgsubstate
637 658 A 1k
638 659 A 2k
639 660 A b2link (no-windows !)
640 661 A bin
641 662 A c1
642 663 A con.xml (no-windows !)
643 664 $ fileset "status('0:1', '3:4', added())"
644 665 .hgsub
645 666 .hgsubstate
646 667 1k
647 668 2k
648 669 b2link (no-windows !)
649 670 bin
650 671 c1
651 672 con.xml (no-windows !)
652 673
653 674 tests with empty value
654 675 ----------------------
655 676
656 677 Fully empty revset
657 678
658 679 $ fileset "status('', '4', added())"
659 680 hg: parse error: first argument to status must be a revision
660 681 [255]
661 682 $ fileset "status('2', '', added())"
662 683 hg: parse error: second argument to status must be a revision
663 684 [255]
664 685
665 686 Empty revset will error at the revset layer
666 687
667 688 $ fileset "status(' ', '4', added())"
668 689 hg: parse error at 1: not a prefix: end
669 690 (
670 691 ^ here)
671 692 [255]
672 693 $ fileset "status('2', ' ', added())"
673 694 hg: parse error at 1: not a prefix: end
674 695 (
675 696 ^ here)
676 697 [255]
@@ -1,1119 +1,1119 b''
1 1 #require no-reposimplestore no-chg
2 2
3 3 # Initial setup
4 4
5 5 $ cat >> $HGRCPATH << EOF
6 6 > [extensions]
7 7 > lfs=
8 8 > [lfs]
9 9 > # Test deprecated config
10 10 > threshold=1000B
11 11 > EOF
12 12
13 13 $ LONG=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
14 14
15 15 # Prepare server and enable extension
16 16 $ hg init server
17 17 $ hg clone -q server client
18 18 $ cd client
19 19
20 20 # Commit small file
21 21 $ echo s > smallfile
22 22 $ echo '**.py = LF' > .hgeol
23 23 $ hg --config lfs.track='"size(\">1000B\")"' commit -Aqm "add small file"
24 24 hg: parse error: unsupported file pattern: size(">1000B")
25 25 (paths must be prefixed with "path:")
26 26 [255]
27 27 $ hg --config lfs.track='size(">1000B")' commit -Aqm "add small file"
28 28
29 29 # Commit large file
30 30 $ echo $LONG > largefile
31 31 $ grep lfs .hg/requires
32 32 [1]
33 33 $ hg commit --traceback -Aqm "add large file"
34 34 $ grep lfs .hg/requires
35 35 lfs
36 36
37 37 # Ensure metadata is stored
38 38 $ hg debugdata largefile 0
39 39 version https://git-lfs.github.com/spec/v1
40 40 oid sha256:f11e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
41 41 size 1501
42 42 x-is-binary 0
43 43
44 44 # Check the blobstore is populated
45 45 $ find .hg/store/lfs/objects | sort
46 46 .hg/store/lfs/objects
47 47 .hg/store/lfs/objects/f1
48 48 .hg/store/lfs/objects/f1/1e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
49 49
50 50 # Check the blob stored contains the actual contents of the file
51 51 $ cat .hg/store/lfs/objects/f1/1e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
52 52 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
53 53
54 54 # Push changes to the server
55 55
56 56 $ hg push
57 57 pushing to $TESTTMP/server
58 58 searching for changes
59 59 abort: lfs.url needs to be configured
60 60 [255]
61 61
62 62 $ cat >> $HGRCPATH << EOF
63 63 > [lfs]
64 64 > url=file:$TESTTMP/dummy-remote/
65 65 > EOF
66 66
67 67 Push to a local non-lfs repo with the extension enabled will add the
68 68 lfs requirement
69 69
70 70 $ grep lfs $TESTTMP/server/.hg/requires
71 71 [1]
72 72 $ hg push -v | egrep -v '^(uncompressed| )'
73 73 pushing to $TESTTMP/server
74 74 searching for changes
75 75 lfs: found f11e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b in the local lfs store
76 76 2 changesets found
77 77 adding changesets
78 78 adding manifests
79 79 adding file changes
80 80 added 2 changesets with 3 changes to 3 files
81 81 calling hook pretxnchangegroup.lfs: hgext.lfs.checkrequireslfs
82 82 $ grep lfs $TESTTMP/server/.hg/requires
83 83 lfs
84 84
85 85 # Unknown URL scheme
86 86
87 87 $ hg push --config lfs.url=ftp://foobar
88 88 abort: lfs: unknown url scheme: ftp
89 89 [255]
90 90
91 91 $ cd ../
92 92
93 93 # Initialize new client (not cloning) and setup extension
94 94 $ hg init client2
95 95 $ cd client2
96 96 $ cat >> .hg/hgrc <<EOF
97 97 > [paths]
98 98 > default = $TESTTMP/server
99 99 > EOF
100 100
101 101 # Pull from server
102 102
103 103 Pulling a local lfs repo into a local non-lfs repo with the extension
104 104 enabled adds the lfs requirement
105 105
106 106 $ grep lfs .hg/requires $TESTTMP/server/.hg/requires
107 107 $TESTTMP/server/.hg/requires:lfs
108 108 $ hg pull default
109 109 pulling from $TESTTMP/server
110 110 requesting all changes
111 111 adding changesets
112 112 adding manifests
113 113 adding file changes
114 114 added 2 changesets with 3 changes to 3 files
115 115 new changesets 0ead593177f7:b88141481348
116 116 (run 'hg update' to get a working copy)
117 117 $ grep lfs .hg/requires $TESTTMP/server/.hg/requires
118 118 .hg/requires:lfs
119 119 $TESTTMP/server/.hg/requires:lfs
120 120
121 121 # Check the blobstore is not yet populated
122 122 $ [ -d .hg/store/lfs/objects ]
123 123 [1]
124 124
125 125 # Update to the last revision containing the large file
126 126 $ hg update
127 127 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
128 128
129 129 # Check the blobstore has been populated on update
130 130 $ find .hg/store/lfs/objects | sort
131 131 .hg/store/lfs/objects
132 132 .hg/store/lfs/objects/f1
133 133 .hg/store/lfs/objects/f1/1e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
134 134
135 135 # Check the contents of the file are fetched from blobstore when requested
136 136 $ hg cat -r . largefile
137 137 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
138 138
139 139 # Check the file has been copied in the working copy
140 140 $ cat largefile
141 141 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
142 142
143 143 $ cd ..
144 144
145 145 # Check rename, and switch between large and small files
146 146
147 147 $ hg init repo3
148 148 $ cd repo3
149 149 $ cat >> .hg/hgrc << EOF
150 150 > [lfs]
151 151 > track=size(">10B")
152 152 > EOF
153 153
154 154 $ echo LONGER-THAN-TEN-BYTES-WILL-TRIGGER-LFS > large
155 155 $ echo SHORTER > small
156 156 $ hg add . -q
157 157 $ hg commit -m 'commit with lfs content'
158 158
159 159 $ hg files -r . 'set:added()'
160 160 large
161 161 small
162 162 $ hg files -r . 'set:added() & lfs()'
163 163 large
164 164
165 165 $ hg mv large l
166 166 $ hg mv small s
167 167 $ hg status 'set:removed()'
168 168 R large
169 169 R small
170 170 $ hg status 'set:removed() & lfs()'
171 171 R large
172 172 $ hg commit -m 'renames'
173 173
174 174 $ hg files -r . 'set:copied()'
175 175 l
176 176 s
177 177 $ hg files -r . 'set:copied() & lfs()'
178 178 l
179 179 $ hg status --change . 'set:removed()'
180 180 R large
181 181 R small
182 182 $ hg status --change . 'set:removed() & lfs()'
183 183 R large
184 184
185 185 $ echo SHORT > l
186 186 $ echo BECOME-LARGER-FROM-SHORTER > s
187 187 $ hg commit -m 'large to small, small to large'
188 188
189 189 $ echo 1 >> l
190 190 $ echo 2 >> s
191 191 $ hg commit -m 'random modifications'
192 192
193 193 $ echo RESTORE-TO-BE-LARGE > l
194 194 $ echo SHORTER > s
195 195 $ hg commit -m 'switch large and small again'
196 196
197 197 # Test lfs_files template
198 198
199 199 $ hg log -r 'all()' -T '{rev} {join(lfs_files, ", ")}\n'
200 200 0 large
201 201 1 l, large
202 202 2 s
203 203 3 s
204 204 4 l
205 205
206 206 # Push and pull the above repo
207 207
208 208 $ hg --cwd .. init repo4
209 209 $ hg push ../repo4
210 210 pushing to ../repo4
211 211 searching for changes
212 212 adding changesets
213 213 adding manifests
214 214 adding file changes
215 215 added 5 changesets with 10 changes to 4 files
216 216
217 217 $ hg --cwd .. init repo5
218 218 $ hg --cwd ../repo5 pull ../repo3
219 219 pulling from ../repo3
220 220 requesting all changes
221 221 adding changesets
222 222 adding manifests
223 223 adding file changes
224 224 added 5 changesets with 10 changes to 4 files
225 225 new changesets fd47a419c4f7:5adf850972b9
226 226 (run 'hg update' to get a working copy)
227 227
228 228 $ cd ..
229 229
230 230 # Test clone
231 231
232 232 $ hg init repo6
233 233 $ cd repo6
234 234 $ cat >> .hg/hgrc << EOF
235 235 > [lfs]
236 236 > track=size(">30B")
237 237 > EOF
238 238
239 239 $ echo LARGE-BECAUSE-IT-IS-MORE-THAN-30-BYTES > large
240 240 $ echo SMALL > small
241 241 $ hg commit -Aqm 'create a lfs file' large small
242 242 $ hg debuglfsupload -r 'all()' -v
243 243 lfs: found 8e92251415339ae9b148c8da89ed5ec665905166a1ab11b09dca8fad83344738 in the local lfs store
244 244
245 245 $ cd ..
246 246
247 247 $ hg clone repo6 repo7
248 248 updating to branch default
249 249 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
250 250 $ cd repo7
251 251 $ hg config extensions --debug | grep lfs
252 252 $TESTTMP/repo7/.hg/hgrc:*: extensions.lfs= (glob)
253 253 $ cat large
254 254 LARGE-BECAUSE-IT-IS-MORE-THAN-30-BYTES
255 255 $ cat small
256 256 SMALL
257 257
258 258 $ cd ..
259 259
260 260 $ hg --config extensions.share= share repo7 sharedrepo
261 261 updating working directory
262 262 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
263 263 $ hg -R sharedrepo config extensions --debug | grep lfs
264 264 $TESTTMP/sharedrepo/.hg/hgrc:*: extensions.lfs= (glob)
265 265
266 266 # Test rename and status
267 267
268 268 $ hg init repo8
269 269 $ cd repo8
270 270 $ cat >> .hg/hgrc << EOF
271 271 > [lfs]
272 272 > track=size(">10B")
273 273 > EOF
274 274
275 275 $ echo THIS-IS-LFS-BECAUSE-10-BYTES > a1
276 276 $ echo SMALL > a2
277 277 $ hg commit -m a -A a1 a2
278 278 $ hg status
279 279 $ hg mv a1 b1
280 280 $ hg mv a2 a1
281 281 $ hg mv b1 a2
282 282 $ hg commit -m b
283 283 $ hg status
284 284 >>> with open('a2', 'wb') as f:
285 285 ... f.write(b'\1\nSTART-WITH-HG-FILELOG-METADATA')
286 286 >>> with open('a1', 'wb') as f:
287 287 ... f.write(b'\1\nMETA\n')
288 288 $ hg commit -m meta
289 289 $ hg status
290 290 $ hg log -T '{rev}: {file_copies} | {file_dels} | {file_adds}\n'
291 291 2: | |
292 292 1: a1 (a2)a2 (a1) | |
293 293 0: | | a1 a2
294 294
295 295 $ for n in a1 a2; do
296 296 > for r in 0 1 2; do
297 297 > printf '\n%s @ %s\n' $n $r
298 298 > hg debugdata $n $r
299 299 > done
300 300 > done
301 301
302 302 a1 @ 0
303 303 version https://git-lfs.github.com/spec/v1
304 304 oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
305 305 size 29
306 306 x-is-binary 0
307 307
308 308 a1 @ 1
309 309 \x01 (esc)
310 310 copy: a2
311 311 copyrev: 50470ad23cf937b1f4b9f80bfe54df38e65b50d9
312 312 \x01 (esc)
313 313 SMALL
314 314
315 315 a1 @ 2
316 316 \x01 (esc)
317 317 \x01 (esc)
318 318 \x01 (esc)
319 319 META
320 320
321 321 a2 @ 0
322 322 SMALL
323 323
324 324 a2 @ 1
325 325 version https://git-lfs.github.com/spec/v1
326 326 oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
327 327 size 29
328 328 x-hg-copy a1
329 329 x-hg-copyrev be23af27908a582af43e5cda209a5a9b319de8d4
330 330 x-is-binary 0
331 331
332 332 a2 @ 2
333 333 version https://git-lfs.github.com/spec/v1
334 334 oid sha256:876dadc86a8542f9798048f2c47f51dbf8e4359aed883e8ec80c5db825f0d943
335 335 size 32
336 336 x-is-binary 0
337 337
338 338 # Verify commit hashes include rename metadata
339 339
340 340 $ hg log -T '{rev}:{node|short} {desc}\n'
341 341 2:0fae949de7fa meta
342 342 1:9cd6bdffdac0 b
343 343 0:7f96794915f7 a
344 344
345 345 $ cd ..
346 346
347 347 # Test bundle
348 348
349 349 $ hg init repo9
350 350 $ cd repo9
351 351 $ cat >> .hg/hgrc << EOF
352 352 > [lfs]
353 353 > track=size(">10B")
354 354 > [diff]
355 355 > git=1
356 356 > EOF
357 357
358 358 $ for i in 0 single two three 4; do
359 359 > echo 'THIS-IS-LFS-'$i > a
360 360 > hg commit -m a-$i -A a
361 361 > done
362 362
363 363 $ hg update 2 -q
364 364 $ echo 'THIS-IS-LFS-2-CHILD' > a
365 365 $ hg commit -m branching -q
366 366
367 367 $ hg bundle --base 1 bundle.hg -v
368 368 lfs: found 5ab7a3739a5feec94a562d070a14f36dba7cad17e5484a4a89eea8e5f3166888 in the local lfs store
369 369 lfs: found a9c7d1cd6ce2b9bbdf46ed9a862845228717b921c089d0d42e3bcaed29eb612e in the local lfs store
370 370 lfs: found f693890c49c409ec33673b71e53f297681f76c1166daf33b2ad7ebf8b1d3237e in the local lfs store
371 371 lfs: found fda198fea753eb66a252e9856915e1f5cddbe41723bd4b695ece2604ad3c9f75 in the local lfs store
372 372 4 changesets found
373 373 uncompressed size of bundle content:
374 374 * (changelog) (glob)
375 375 * (manifests) (glob)
376 376 * a (glob)
377 377 $ hg --config extensions.strip= strip -r 2 --no-backup --force -q
378 378 $ hg -R bundle.hg log -p -T '{rev} {desc}\n' a
379 379 5 branching
380 380 diff --git a/a b/a
381 381 --- a/a
382 382 +++ b/a
383 383 @@ -1,1 +1,1 @@
384 384 -THIS-IS-LFS-two
385 385 +THIS-IS-LFS-2-CHILD
386 386
387 387 4 a-4
388 388 diff --git a/a b/a
389 389 --- a/a
390 390 +++ b/a
391 391 @@ -1,1 +1,1 @@
392 392 -THIS-IS-LFS-three
393 393 +THIS-IS-LFS-4
394 394
395 395 3 a-three
396 396 diff --git a/a b/a
397 397 --- a/a
398 398 +++ b/a
399 399 @@ -1,1 +1,1 @@
400 400 -THIS-IS-LFS-two
401 401 +THIS-IS-LFS-three
402 402
403 403 2 a-two
404 404 diff --git a/a b/a
405 405 --- a/a
406 406 +++ b/a
407 407 @@ -1,1 +1,1 @@
408 408 -THIS-IS-LFS-single
409 409 +THIS-IS-LFS-two
410 410
411 411 1 a-single
412 412 diff --git a/a b/a
413 413 --- a/a
414 414 +++ b/a
415 415 @@ -1,1 +1,1 @@
416 416 -THIS-IS-LFS-0
417 417 +THIS-IS-LFS-single
418 418
419 419 0 a-0
420 420 diff --git a/a b/a
421 421 new file mode 100644
422 422 --- /dev/null
423 423 +++ b/a
424 424 @@ -0,0 +1,1 @@
425 425 +THIS-IS-LFS-0
426 426
427 427 $ hg bundle -R bundle.hg --base 1 bundle-again.hg -q
428 428 $ hg -R bundle-again.hg log -p -T '{rev} {desc}\n' a
429 429 5 branching
430 430 diff --git a/a b/a
431 431 --- a/a
432 432 +++ b/a
433 433 @@ -1,1 +1,1 @@
434 434 -THIS-IS-LFS-two
435 435 +THIS-IS-LFS-2-CHILD
436 436
437 437 4 a-4
438 438 diff --git a/a b/a
439 439 --- a/a
440 440 +++ b/a
441 441 @@ -1,1 +1,1 @@
442 442 -THIS-IS-LFS-three
443 443 +THIS-IS-LFS-4
444 444
445 445 3 a-three
446 446 diff --git a/a b/a
447 447 --- a/a
448 448 +++ b/a
449 449 @@ -1,1 +1,1 @@
450 450 -THIS-IS-LFS-two
451 451 +THIS-IS-LFS-three
452 452
453 453 2 a-two
454 454 diff --git a/a b/a
455 455 --- a/a
456 456 +++ b/a
457 457 @@ -1,1 +1,1 @@
458 458 -THIS-IS-LFS-single
459 459 +THIS-IS-LFS-two
460 460
461 461 1 a-single
462 462 diff --git a/a b/a
463 463 --- a/a
464 464 +++ b/a
465 465 @@ -1,1 +1,1 @@
466 466 -THIS-IS-LFS-0
467 467 +THIS-IS-LFS-single
468 468
469 469 0 a-0
470 470 diff --git a/a b/a
471 471 new file mode 100644
472 472 --- /dev/null
473 473 +++ b/a
474 474 @@ -0,0 +1,1 @@
475 475 +THIS-IS-LFS-0
476 476
477 477 $ cd ..
478 478
479 479 # Test isbinary
480 480
481 481 $ hg init repo10
482 482 $ cd repo10
483 483 $ cat >> .hg/hgrc << EOF
484 484 > [extensions]
485 485 > lfs=
486 486 > [lfs]
487 487 > track=all()
488 488 > EOF
489 489 $ $PYTHON <<'EOF'
490 490 > def write(path, content):
491 491 > with open(path, 'wb') as f:
492 492 > f.write(content)
493 493 > write('a', b'\0\0')
494 494 > write('b', b'\1\n')
495 495 > write('c', b'\1\n\0')
496 496 > write('d', b'xx')
497 497 > EOF
498 498 $ hg add a b c d
499 499 $ hg diff --stat
500 500 a | Bin
501 501 b | 1 +
502 502 c | Bin
503 503 d | 1 +
504 504 4 files changed, 2 insertions(+), 0 deletions(-)
505 505 $ hg commit -m binarytest
506 506 $ cat > $TESTTMP/dumpbinary.py << EOF
507 507 > def reposetup(ui, repo):
508 508 > for n in 'abcd':
509 509 > ui.write(('%s: binary=%s\n') % (n, repo['.'][n].isbinary()))
510 510 > EOF
511 511 $ hg --config extensions.dumpbinary=$TESTTMP/dumpbinary.py id --trace
512 512 a: binary=True
513 513 b: binary=False
514 514 c: binary=True
515 515 d: binary=False
516 516 b55353847f02 tip
517 517
518 518 Binary blobs don't need to be present to be skipped in filesets. (And their
519 519 absence doesn't cause an abort.)
520 520
521 521 $ rm .hg/store/lfs/objects/96/a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7
522 522 $ rm .hg/store/lfs/objects/92/f76135a4baf4faccb8586a60faf830c2bdfce147cefa188aaf4b790bd01b7e
523 523
524 524 $ hg files --debug -r . 'set:eol("unix")' --config 'experimental.lfs.disableusercache=True'
525 525 lfs: found c04b5bb1a5b2eb3e9cd4805420dba5a9d133da5b7adeeafb5474c4adae9faa80 in the local lfs store
526 2 b
526 527 lfs: found 5dde896887f6754c9b15bfe3a441ae4806df2fde94001311e08bf110622e0bbe in the local lfs store
527 2 b
528 528
529 529 $ hg files --debug -r . 'set:binary()' --config 'experimental.lfs.disableusercache=True'
530 530 2 a
531 531 3 c
532 532
533 533 $ cd ..
534 534
535 535 # Test fctx.cmp fastpath - diff without LFS blobs
536 536
537 537 $ hg init repo12
538 538 $ cd repo12
539 539 $ cat >> .hg/hgrc <<EOF
540 540 > [lfs]
541 541 > threshold=1
542 542 > EOF
543 543 $ cat > ../patch.diff <<EOF
544 544 > # HG changeset patch
545 545 > 2
546 546 >
547 547 > diff --git a/a b/a
548 548 > old mode 100644
549 549 > new mode 100755
550 550 > EOF
551 551
552 552 $ for i in 1 2 3; do
553 553 > cp ../repo10/a a
554 554 > if [ $i = 3 ]; then
555 555 > # make a content-only change
556 556 > hg import -q --bypass ../patch.diff
557 557 > hg update -q
558 558 > rm ../patch.diff
559 559 > else
560 560 > echo $i >> a
561 561 > hg commit -m $i -A a
562 562 > fi
563 563 > done
564 564 $ [ -d .hg/store/lfs/objects ]
565 565
566 566 $ cd ..
567 567
568 568 $ hg clone repo12 repo13 --noupdate
569 569 $ cd repo13
570 570 $ hg log --removed -p a -T '{desc}\n' --config diff.nobinary=1 --git
571 571 2
572 572 diff --git a/a b/a
573 573 old mode 100644
574 574 new mode 100755
575 575
576 576 2
577 577 diff --git a/a b/a
578 578 Binary file a has changed
579 579
580 580 1
581 581 diff --git a/a b/a
582 582 new file mode 100644
583 583 Binary file a has changed
584 584
585 585 $ [ -d .hg/store/lfs/objects ]
586 586 [1]
587 587
588 588 $ cd ..
589 589
590 590 # Test filter
591 591
592 592 $ hg init repo11
593 593 $ cd repo11
594 594 $ cat >> .hg/hgrc << EOF
595 595 > [lfs]
596 596 > track=(**.a & size(">5B")) | (**.b & !size(">5B"))
597 597 > | (**.c & "path:d" & !"path:d/c.c") | size(">10B")
598 598 > EOF
599 599
600 600 $ mkdir a
601 601 $ echo aaaaaa > a/1.a
602 602 $ echo a > a/2.a
603 603 $ echo aaaaaa > 1.b
604 604 $ echo a > 2.b
605 605 $ echo a > 1.c
606 606 $ mkdir d
607 607 $ echo a > d/c.c
608 608 $ echo a > d/d.c
609 609 $ echo aaaaaaaaaaaa > x
610 610 $ hg add . -q
611 611 $ hg commit -m files
612 612
613 613 $ for p in a/1.a a/2.a 1.b 2.b 1.c d/c.c d/d.c x; do
614 614 > if hg debugdata $p 0 2>&1 | grep git-lfs >/dev/null; then
615 615 > echo "${p}: is lfs"
616 616 > else
617 617 > echo "${p}: not lfs"
618 618 > fi
619 619 > done
620 620 a/1.a: is lfs
621 621 a/2.a: not lfs
622 622 1.b: not lfs
623 623 2.b: is lfs
624 624 1.c: not lfs
625 625 d/c.c: not lfs
626 626 d/d.c: is lfs
627 627 x: is lfs
628 628
629 629 $ cd ..
630 630
631 631 # Verify the repos
632 632
633 633 $ cat > $TESTTMP/dumpflog.py << EOF
634 634 > # print raw revision sizes, flags, and hashes for certain files
635 635 > import hashlib
636 636 > from mercurial.node import short
637 637 > from mercurial import revlog
638 638 > def hash(rawtext):
639 639 > h = hashlib.sha512()
640 640 > h.update(rawtext)
641 641 > return h.hexdigest()[:4]
642 642 > def reposetup(ui, repo):
643 643 > # these 2 files are interesting
644 644 > for name in ['l', 's']:
645 645 > fl = repo.file(name)
646 646 > if len(fl) == 0:
647 647 > continue
648 648 > sizes = [fl.rawsize(i) for i in fl]
649 649 > texts = [fl.revision(i, raw=True) for i in fl]
650 650 > flags = [int(fl.flags(i)) for i in fl]
651 651 > hashes = [hash(t) for t in texts]
652 652 > print(' %s: rawsizes=%r flags=%r hashes=%r'
653 653 > % (name, sizes, flags, hashes))
654 654 > EOF
655 655
656 656 $ for i in client client2 server repo3 repo4 repo5 repo6 repo7 repo8 repo9 \
657 657 > repo10; do
658 658 > echo 'repo:' $i
659 659 > hg --cwd $i verify --config extensions.dumpflog=$TESTTMP/dumpflog.py -q
660 660 > done
661 661 repo: client
662 662 repo: client2
663 663 repo: server
664 664 repo: repo3
665 665 l: rawsizes=[211, 6, 8, 141] flags=[8192, 0, 0, 8192] hashes=['d2b8', '948c', 'cc88', '724d']
666 666 s: rawsizes=[74, 141, 141, 8] flags=[0, 8192, 8192, 0] hashes=['3c80', 'fce0', '874a', '826b']
667 667 repo: repo4
668 668 l: rawsizes=[211, 6, 8, 141] flags=[8192, 0, 0, 8192] hashes=['d2b8', '948c', 'cc88', '724d']
669 669 s: rawsizes=[74, 141, 141, 8] flags=[0, 8192, 8192, 0] hashes=['3c80', 'fce0', '874a', '826b']
670 670 repo: repo5
671 671 l: rawsizes=[211, 6, 8, 141] flags=[8192, 0, 0, 8192] hashes=['d2b8', '948c', 'cc88', '724d']
672 672 s: rawsizes=[74, 141, 141, 8] flags=[0, 8192, 8192, 0] hashes=['3c80', 'fce0', '874a', '826b']
673 673 repo: repo6
674 674 repo: repo7
675 675 repo: repo8
676 676 repo: repo9
677 677 repo: repo10
678 678
679 679 repo13 doesn't have any cached lfs files and its source never pushed its
680 680 files. Therefore, the files don't exist in the remote store. Use the files in
681 681 the user cache.
682 682
683 683 $ test -d $TESTTMP/repo13/.hg/store/lfs/objects
684 684 [1]
685 685
686 686 $ hg --config extensions.share= share repo13 repo14
687 687 updating working directory
688 688 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
689 689 $ hg -R repo14 -q verify
690 690
691 691 $ hg clone repo13 repo15
692 692 updating to branch default
693 693 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
694 694 $ hg -R repo15 -q verify
695 695
696 696 If the source repo doesn't have the blob (maybe it was pulled or cloned with
697 697 --noupdate), the blob is still accessible via the global cache to send to the
698 698 remote store.
699 699
700 700 $ rm -rf $TESTTMP/repo15/.hg/store/lfs
701 701 $ hg init repo16
702 702 $ hg -R repo15 push repo16
703 703 pushing to repo16
704 704 searching for changes
705 705 adding changesets
706 706 adding manifests
707 707 adding file changes
708 708 added 3 changesets with 2 changes to 1 files
709 709 $ hg -R repo15 -q verify
710 710
711 711 Test damaged file scenarios. (This also damages the usercache because of the
712 712 hardlinks.)
713 713
714 714 $ echo 'damage' >> repo5/.hg/store/lfs/objects/66/100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
715 715
716 716 Repo with damaged lfs objects in any revision will fail verification.
717 717
718 718 $ hg -R repo5 verify
719 719 checking changesets
720 720 checking manifests
721 721 crosschecking files in changesets and manifests
722 722 checking files
723 723 l@1: unpacking 46a2f24864bc: integrity check failed on data/l.i:0
724 724 large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0
725 725 4 files, 5 changesets, 10 total revisions
726 726 2 integrity errors encountered!
727 727 (first damaged changeset appears to be 0)
728 728 [1]
729 729
730 730 Updates work after cloning a damaged repo, if the damaged lfs objects aren't in
731 731 the update destination. Those objects won't be added to the new repo's store
732 732 because they aren't accessed.
733 733
734 734 $ hg clone -v repo5 fromcorrupt
735 735 updating to branch default
736 736 resolving manifests
737 737 getting l
738 738 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the usercache
739 739 getting s
740 740 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
741 741 $ test -f fromcorrupt/.hg/store/lfs/objects/66/100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
742 742 [1]
743 743
744 744 Verify will copy/link all lfs objects into the local store that aren't already
745 745 present. Bypass the corrupted usercache to show that verify works when fed by
746 746 the (uncorrupted) remote store.
747 747
748 748 $ hg -R fromcorrupt --config lfs.usercache=emptycache verify -v
749 749 repository uses revlog format 1
750 750 checking changesets
751 751 checking manifests
752 752 crosschecking files in changesets and manifests
753 753 checking files
754 754 lfs: adding 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e to the usercache
755 755 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store
756 756 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
757 757 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store
758 758 lfs: adding 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 to the usercache
759 759 lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store
760 760 lfs: adding b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c to the usercache
761 761 lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store
762 762 4 files, 5 changesets, 10 total revisions
763 763
764 764 Verify will not copy/link a corrupted file from the usercache into the local
765 765 store, and poison it. (The verify with a good remote now works.)
766 766
767 767 $ rm -r fromcorrupt/.hg/store/lfs/objects/66/100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
768 768 $ hg -R fromcorrupt verify -v
769 769 repository uses revlog format 1
770 770 checking changesets
771 771 checking manifests
772 772 crosschecking files in changesets and manifests
773 773 checking files
774 774 l@1: unpacking 46a2f24864bc: integrity check failed on data/l.i:0
775 775 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
776 776 large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0
777 777 lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store
778 778 lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store
779 779 4 files, 5 changesets, 10 total revisions
780 780 2 integrity errors encountered!
781 781 (first damaged changeset appears to be 0)
782 782 [1]
783 783 $ hg -R fromcorrupt --config lfs.usercache=emptycache verify -v
784 784 repository uses revlog format 1
785 785 checking changesets
786 786 checking manifests
787 787 crosschecking files in changesets and manifests
788 788 checking files
789 789 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the usercache
790 790 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
791 791 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store
792 792 lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store
793 793 lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store
794 794 4 files, 5 changesets, 10 total revisions
795 795
796 796 Damaging a file required by the update destination fails the update.
797 797
798 798 $ echo 'damage' >> $TESTTMP/dummy-remote/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b
799 799 $ hg --config lfs.usercache=emptycache clone -v repo5 fromcorrupt2
800 800 updating to branch default
801 801 resolving manifests
802 802 abort: corrupt remote lfs object: 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b
803 803 [255]
804 804
805 805 A corrupted lfs blob is not transferred from a file://remotestore to the
806 806 usercache or local store.
807 807
808 808 $ test -f emptycache/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b
809 809 [1]
810 810 $ test -f fromcorrupt2/.hg/store/lfs/objects/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b
811 811 [1]
812 812
813 813 $ hg -R fromcorrupt2 verify
814 814 checking changesets
815 815 checking manifests
816 816 crosschecking files in changesets and manifests
817 817 checking files
818 818 l@1: unpacking 46a2f24864bc: integrity check failed on data/l.i:0
819 819 large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0
820 820 4 files, 5 changesets, 10 total revisions
821 821 2 integrity errors encountered!
822 822 (first damaged changeset appears to be 0)
823 823 [1]
824 824
825 825 Corrupt local files are not sent upstream. (The alternate dummy remote
826 826 avoids the corrupt lfs object in the original remote.)
827 827
828 828 $ mkdir $TESTTMP/dummy-remote2
829 829 $ hg init dest
830 830 $ hg -R fromcorrupt2 --config lfs.url=file:///$TESTTMP/dummy-remote2 push -v dest
831 831 pushing to dest
832 832 searching for changes
833 833 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
834 834 lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store
835 835 lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store
836 836 abort: detected corrupt lfs object: 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
837 837 (run hg verify)
838 838 [255]
839 839
840 840 $ hg -R fromcorrupt2 --config lfs.url=file:///$TESTTMP/dummy-remote2 verify -v
841 841 repository uses revlog format 1
842 842 checking changesets
843 843 checking manifests
844 844 crosschecking files in changesets and manifests
845 845 checking files
846 846 l@1: unpacking 46a2f24864bc: integrity check failed on data/l.i:0
847 847 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
848 848 large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0
849 849 lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store
850 850 lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store
851 851 4 files, 5 changesets, 10 total revisions
852 852 2 integrity errors encountered!
853 853 (first damaged changeset appears to be 0)
854 854 [1]
855 855
856 856 $ cat $TESTTMP/dummy-remote2/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b | $TESTDIR/f --sha256
857 857 sha256=22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b
858 858 $ cat fromcorrupt2/.hg/store/lfs/objects/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b | $TESTDIR/f --sha256
859 859 sha256=22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b
860 860 $ test -f $TESTTMP/dummy-remote2/66/100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
861 861 [1]
862 862
863 863 Accessing a corrupt file will complain
864 864
865 865 $ hg --cwd fromcorrupt2 cat -r 0 large
866 866 abort: integrity check failed on data/large.i:0!
867 867 [255]
868 868
869 869 lfs -> normal -> lfs round trip conversions are possible. The 'none()'
870 870 predicate on the command line will override whatever is configured globally and
871 871 locally, and ensures everything converts to a regular file. For lfs -> normal,
872 872 there's no 'lfs' destination repo requirement. For normal -> lfs, there is.
873 873
874 874 $ hg --config extensions.convert= --config 'lfs.track=none()' \
875 875 > convert repo8 convert_normal
876 876 initializing destination convert_normal repository
877 877 scanning source...
878 878 sorting...
879 879 converting...
880 880 2 a
881 881 1 b
882 882 0 meta
883 883 $ grep 'lfs' convert_normal/.hg/requires
884 884 [1]
885 885 $ hg --cwd convert_normal cat a1 -r 0 -T '{rawdata}'
886 886 THIS-IS-LFS-BECAUSE-10-BYTES
887 887
888 888 $ hg --config extensions.convert= --config lfs.threshold=10B \
889 889 > convert convert_normal convert_lfs
890 890 initializing destination convert_lfs repository
891 891 scanning source...
892 892 sorting...
893 893 converting...
894 894 2 a
895 895 1 b
896 896 0 meta
897 897
898 898 $ hg --cwd convert_lfs cat -r 0 a1 -T '{rawdata}'
899 899 version https://git-lfs.github.com/spec/v1
900 900 oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
901 901 size 29
902 902 x-is-binary 0
903 903 $ hg --cwd convert_lfs debugdata a1 0
904 904 version https://git-lfs.github.com/spec/v1
905 905 oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
906 906 size 29
907 907 x-is-binary 0
908 908 $ hg --cwd convert_lfs log -r 0 -T "{lfs_files % '{lfspointer % '{key}={value}\n'}'}"
909 909 version=https://git-lfs.github.com/spec/v1
910 910 oid=sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
911 911 size=29
912 912 x-is-binary=0
913 913 $ hg --cwd convert_lfs log -r 0 \
914 914 > -T '{lfs_files % "{get(lfspointer, "oid")}\n"}{lfs_files % "{lfspointer.oid}\n"}'
915 915 sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
916 916 sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
917 917 $ hg --cwd convert_lfs log -r 0 -T '{lfs_files % "{lfspointer}\n"}'
918 918 version=https://git-lfs.github.com/spec/v1 oid=sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024 size=29 x-is-binary=0
919 919 $ hg --cwd convert_lfs \
920 920 > log -r 'all()' -T '{rev}: {lfs_files % "{file}: {lfsoid}\n"}'
921 921 0: a1: 5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
922 922 1: a2: 5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
923 923 2: a2: 876dadc86a8542f9798048f2c47f51dbf8e4359aed883e8ec80c5db825f0d943
924 924
925 925 $ grep 'lfs' convert_lfs/.hg/requires
926 926 lfs
927 927
928 928 The hashes in all stages of the conversion are unchanged.
929 929
930 930 $ hg -R repo8 log -T '{node|short}\n'
931 931 0fae949de7fa
932 932 9cd6bdffdac0
933 933 7f96794915f7
934 934 $ hg -R convert_normal log -T '{node|short}\n'
935 935 0fae949de7fa
936 936 9cd6bdffdac0
937 937 7f96794915f7
938 938 $ hg -R convert_lfs log -T '{node|short}\n'
939 939 0fae949de7fa
940 940 9cd6bdffdac0
941 941 7f96794915f7
942 942
943 943 This convert is trickier, because it contains deleted files (via `hg mv`)
944 944
945 945 $ hg --config extensions.convert= --config lfs.threshold=1000M \
946 946 > convert repo3 convert_normal2
947 947 initializing destination convert_normal2 repository
948 948 scanning source...
949 949 sorting...
950 950 converting...
951 951 4 commit with lfs content
952 952 3 renames
953 953 2 large to small, small to large
954 954 1 random modifications
955 955 0 switch large and small again
956 956 $ grep 'lfs' convert_normal2/.hg/requires
957 957 [1]
958 958 $ hg --cwd convert_normal2 debugdata large 0
959 959 LONGER-THAN-TEN-BYTES-WILL-TRIGGER-LFS
960 960
961 961 $ hg --config extensions.convert= --config lfs.threshold=10B \
962 962 > convert convert_normal2 convert_lfs2
963 963 initializing destination convert_lfs2 repository
964 964 scanning source...
965 965 sorting...
966 966 converting...
967 967 4 commit with lfs content
968 968 3 renames
969 969 2 large to small, small to large
970 970 1 random modifications
971 971 0 switch large and small again
972 972 $ grep 'lfs' convert_lfs2/.hg/requires
973 973 lfs
974 974 $ hg --cwd convert_lfs2 debugdata large 0
975 975 version https://git-lfs.github.com/spec/v1
976 976 oid sha256:66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
977 977 size 39
978 978 x-is-binary 0
979 979
980 980 $ hg -R convert_lfs2 config --debug extensions | grep lfs
981 981 $TESTTMP/convert_lfs2/.hg/hgrc:*: extensions.lfs= (glob)
982 982
983 983 Committing deleted files works:
984 984
985 985 $ hg init $TESTTMP/repo-del
986 986 $ cd $TESTTMP/repo-del
987 987 $ echo 1 > A
988 988 $ hg commit -m 'add A' -A A
989 989 $ hg rm A
990 990 $ hg commit -m 'rm A'
991 991
992 992 Bad .hglfs files will block the commit with a useful message
993 993
994 994 $ cat > .hglfs << EOF
995 995 > [track]
996 996 > **.test = size(">5B")
997 997 > bad file ... no commit
998 998 > EOF
999 999
1000 1000 $ echo x > file.txt
1001 1001 $ hg ci -Aqm 'should fail'
1002 1002 hg: parse error at .hglfs:3: bad file ... no commit
1003 1003 [255]
1004 1004
1005 1005 $ cat > .hglfs << EOF
1006 1006 > [track]
1007 1007 > **.test = size(">5B")
1008 1008 > ** = nonexistent()
1009 1009 > EOF
1010 1010
1011 1011 $ hg ci -Aqm 'should fail'
1012 1012 abort: parse error in .hglfs: unknown identifier: nonexistent
1013 1013 [255]
1014 1014
1015 1015 '**' works out to mean all files.
1016 1016
1017 1017 $ cat > .hglfs << EOF
1018 1018 > [track]
1019 1019 > path:.hglfs = none()
1020 1020 > **.test = size(">5B")
1021 1021 > **.exclude = none()
1022 1022 > ** = size(">10B")
1023 1023 > EOF
1024 1024
1025 1025 The LFS policy takes effect without tracking the .hglfs file
1026 1026
1027 1027 $ echo 'largefile' > lfs.test
1028 1028 $ echo '012345678901234567890' > nolfs.exclude
1029 1029 $ echo '01234567890123456' > lfs.catchall
1030 1030 $ hg add *
1031 1031 $ hg ci -qm 'before add .hglfs'
1032 1032 $ hg log -r . -T '{rev}: {lfs_files % "{file}: {lfsoid}\n"}\n'
1033 1033 2: lfs.catchall: d4ec46c2869ba22eceb42a729377432052d9dd75d82fc40390ebaadecee87ee9
1034 1034 lfs.test: 5489e6ced8c36a7b267292bde9fd5242a5f80a7482e8f23fa0477393dfaa4d6c
1035 1035
1036 1036 The .hglfs file works when tracked
1037 1037
1038 1038 $ echo 'largefile2' > lfs.test
1039 1039 $ echo '012345678901234567890a' > nolfs.exclude
1040 1040 $ echo '01234567890123456a' > lfs.catchall
1041 1041 $ hg ci -Aqm 'after adding .hglfs'
1042 1042 $ hg log -r . -T '{rev}: {lfs_files % "{file}: {lfsoid}\n"}\n'
1043 1043 3: lfs.catchall: 31f43b9c62b540126b0ad5884dc013d21a61c9329b77de1fceeae2fc58511573
1044 1044 lfs.test: 8acd23467967bc7b8cc5a280056589b0ba0b17ff21dbd88a7b6474d6290378a6
1045 1045
1046 1046 The LFS policy stops when the .hglfs is gone
1047 1047
1048 1048 $ mv .hglfs .hglfs_
1049 1049 $ echo 'largefile3' > lfs.test
1050 1050 $ echo '012345678901234567890abc' > nolfs.exclude
1051 1051 $ echo '01234567890123456abc' > lfs.catchall
1052 1052 $ hg ci -qm 'file test' -X .hglfs
1053 1053 $ hg log -r . -T '{rev}: {lfs_files % "{file}: {lfsoid}\n"}\n'
1054 1054 4:
1055 1055
1056 1056 $ mv .hglfs_ .hglfs
1057 1057 $ echo '012345678901234567890abc' > lfs.test
1058 1058 $ hg ci -m 'back to lfs'
1059 1059 $ hg rm lfs.test
1060 1060 $ hg ci -qm 'remove lfs'
1061 1061
1062 1062 {lfs_files} will list deleted files too
1063 1063
1064 1064 $ hg log -T "{lfs_files % '{rev} {file}: {lfspointer.oid}\n'}"
1065 1065 6 lfs.test:
1066 1066 5 lfs.test: sha256:43f8f41171b6f62a6b61ba4ce98a8a6c1649240a47ebafd43120aa215ac9e7f6
1067 1067 3 lfs.catchall: sha256:31f43b9c62b540126b0ad5884dc013d21a61c9329b77de1fceeae2fc58511573
1068 1068 3 lfs.test: sha256:8acd23467967bc7b8cc5a280056589b0ba0b17ff21dbd88a7b6474d6290378a6
1069 1069 2 lfs.catchall: sha256:d4ec46c2869ba22eceb42a729377432052d9dd75d82fc40390ebaadecee87ee9
1070 1070 2 lfs.test: sha256:5489e6ced8c36a7b267292bde9fd5242a5f80a7482e8f23fa0477393dfaa4d6c
1071 1071
1072 1072 $ hg log -r 'file("set:lfs()")' -T '{rev} {join(lfs_files, ", ")}\n'
1073 1073 2 lfs.catchall, lfs.test
1074 1074 3 lfs.catchall, lfs.test
1075 1075 5 lfs.test
1076 1076 6 lfs.test
1077 1077
1078 1078 $ cd ..
1079 1079
1080 1080 Unbundling adds a requirement to a non-lfs repo, if necessary.
1081 1081
1082 1082 $ hg bundle -R $TESTTMP/repo-del -qr 0 --base null nolfs.hg
1083 1083 $ hg bundle -R convert_lfs2 -qr tip --base null lfs.hg
1084 1084 $ hg init unbundle
1085 1085 $ hg pull -R unbundle -q nolfs.hg
1086 1086 $ grep lfs unbundle/.hg/requires
1087 1087 [1]
1088 1088 $ hg pull -R unbundle -q lfs.hg
1089 1089 $ grep lfs unbundle/.hg/requires
1090 1090 lfs
1091 1091
1092 1092 $ hg init no_lfs
1093 1093 $ cat >> no_lfs/.hg/hgrc <<EOF
1094 1094 > [experimental]
1095 1095 > changegroup3 = True
1096 1096 > [extensions]
1097 1097 > lfs=!
1098 1098 > EOF
1099 1099 $ cp -R no_lfs no_lfs2
1100 1100
1101 1101 Pushing from a local lfs repo to a local repo without an lfs requirement and
1102 1102 with lfs disabled, fails.
1103 1103
1104 1104 $ hg push -R convert_lfs2 no_lfs
1105 1105 pushing to no_lfs
1106 1106 abort: required features are not supported in the destination: lfs
1107 1107 [255]
1108 1108 $ grep lfs no_lfs/.hg/requires
1109 1109 [1]
1110 1110
1111 1111 Pulling from a local lfs repo to a local repo without an lfs requirement and
1112 1112 with lfs disabled, fails.
1113 1113
1114 1114 $ hg pull -R no_lfs2 convert_lfs2
1115 1115 pulling from convert_lfs2
1116 1116 abort: required features are not supported in the destination: lfs
1117 1117 [255]
1118 1118 $ grep lfs no_lfs2/.hg/requires
1119 1119 [1]
General Comments 0
You need to be logged in to leave comments. Login now