##// END OF EJS Templates
errors: raise ConfigError on failure to parse config file...
Martin von Zweigbergk -
r46506:9dc1351d default
parent child Browse files
Show More
@@ -1,474 +1,474 b''
1 1 """automatically manage newlines in repository files
2 2
3 3 This extension allows you to manage the type of line endings (CRLF or
4 4 LF) that are used in the repository and in the local working
5 5 directory. That way you can get CRLF line endings on Windows and LF on
6 6 Unix/Mac, thereby letting everybody use their OS native line endings.
7 7
8 8 The extension reads its configuration from a versioned ``.hgeol``
9 9 configuration file found in the root of the working directory. The
10 10 ``.hgeol`` file use the same syntax as all other Mercurial
11 11 configuration files. It uses two sections, ``[patterns]`` and
12 12 ``[repository]``.
13 13
14 14 The ``[patterns]`` section specifies how line endings should be
15 15 converted between the working directory and the repository. The format is
16 16 specified by a file pattern. The first match is used, so put more
17 17 specific patterns first. The available line endings are ``LF``,
18 18 ``CRLF``, and ``BIN``.
19 19
20 20 Files with the declared format of ``CRLF`` or ``LF`` are always
21 21 checked out and stored in the repository in that format and files
22 22 declared to be binary (``BIN``) are left unchanged. Additionally,
23 23 ``native`` is an alias for checking out in the platform's default line
24 24 ending: ``LF`` on Unix (including Mac OS X) and ``CRLF`` on
25 25 Windows. Note that ``BIN`` (do nothing to line endings) is Mercurial's
26 26 default behavior; it is only needed if you need to override a later,
27 27 more general pattern.
28 28
29 29 The optional ``[repository]`` section specifies the line endings to
30 30 use for files stored in the repository. It has a single setting,
31 31 ``native``, which determines the storage line endings for files
32 32 declared as ``native`` in the ``[patterns]`` section. It can be set to
33 33 ``LF`` or ``CRLF``. The default is ``LF``. For example, this means
34 34 that on Windows, files configured as ``native`` (``CRLF`` by default)
35 35 will be converted to ``LF`` when stored in the repository. Files
36 36 declared as ``LF``, ``CRLF``, or ``BIN`` in the ``[patterns]`` section
37 37 are always stored as-is in the repository.
38 38
39 39 Example versioned ``.hgeol`` file::
40 40
41 41 [patterns]
42 42 **.py = native
43 43 **.vcproj = CRLF
44 44 **.txt = native
45 45 Makefile = LF
46 46 **.jpg = BIN
47 47
48 48 [repository]
49 49 native = LF
50 50
51 51 .. note::
52 52
53 53 The rules will first apply when files are touched in the working
54 54 directory, e.g. by updating to null and back to tip to touch all files.
55 55
56 56 The extension uses an optional ``[eol]`` section read from both the
57 57 normal Mercurial configuration files and the ``.hgeol`` file, with the
58 58 latter overriding the former. You can use that section to control the
59 59 overall behavior. There are three settings:
60 60
61 61 - ``eol.native`` (default ``os.linesep``) can be set to ``LF`` or
62 62 ``CRLF`` to override the default interpretation of ``native`` for
63 63 checkout. This can be used with :hg:`archive` on Unix, say, to
64 64 generate an archive where files have line endings for Windows.
65 65
66 66 - ``eol.only-consistent`` (default True) can be set to False to make
67 67 the extension convert files with inconsistent EOLs. Inconsistent
68 68 means that there is both ``CRLF`` and ``LF`` present in the file.
69 69 Such files are normally not touched under the assumption that they
70 70 have mixed EOLs on purpose.
71 71
72 72 - ``eol.fix-trailing-newline`` (default False) can be set to True to
73 73 ensure that converted files end with a EOL character (either ``\\n``
74 74 or ``\\r\\n`` as per the configured patterns).
75 75
76 76 The extension provides ``cleverencode:`` and ``cleverdecode:`` filters
77 77 like the deprecated win32text extension does. This means that you can
78 78 disable win32text and enable eol and your filters will still work. You
79 79 only need to these filters until you have prepared a ``.hgeol`` file.
80 80
81 81 The ``win32text.forbid*`` hooks provided by the win32text extension
82 82 have been unified into a single hook named ``eol.checkheadshook``. The
83 83 hook will lookup the expected line endings from the ``.hgeol`` file,
84 84 which means you must migrate to a ``.hgeol`` file first before using
85 85 the hook. ``eol.checkheadshook`` only checks heads, intermediate
86 86 invalid revisions will be pushed. To forbid them completely, use the
87 87 ``eol.checkallhook`` hook. These hooks are best used as
88 88 ``pretxnchangegroup`` hooks.
89 89
90 90 See :hg:`help patterns` for more information about the glob patterns
91 91 used.
92 92 """
93 93
94 94 from __future__ import absolute_import
95 95
96 96 import os
97 97 import re
98 98 from mercurial.i18n import _
99 99 from mercurial import (
100 100 config,
101 101 error as errormod,
102 102 extensions,
103 103 match,
104 104 pycompat,
105 105 registrar,
106 106 scmutil,
107 107 util,
108 108 )
109 109 from mercurial.utils import stringutil
110 110
111 111 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
112 112 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
113 113 # be specifying the version(s) of Mercurial they are tested with, or
114 114 # leave the attribute unspecified.
115 115 testedwith = b'ships-with-hg-core'
116 116
117 117 configtable = {}
118 118 configitem = registrar.configitem(configtable)
119 119
120 120 configitem(
121 121 b'eol', b'fix-trailing-newline', default=False,
122 122 )
123 123 configitem(
124 124 b'eol', b'native', default=pycompat.oslinesep,
125 125 )
126 126 configitem(
127 127 b'eol', b'only-consistent', default=True,
128 128 )
129 129
130 130 # Matches a lone LF, i.e., one that is not part of CRLF.
131 131 singlelf = re.compile(b'(^|[^\r])\n')
132 132
133 133
134 134 def inconsistenteol(data):
135 135 return b'\r\n' in data and singlelf.search(data)
136 136
137 137
138 138 def tolf(s, params, ui, **kwargs):
139 139 """Filter to convert to LF EOLs."""
140 140 if stringutil.binary(s):
141 141 return s
142 142 if ui.configbool(b'eol', b'only-consistent') and inconsistenteol(s):
143 143 return s
144 144 if (
145 145 ui.configbool(b'eol', b'fix-trailing-newline')
146 146 and s
147 147 and not s.endswith(b'\n')
148 148 ):
149 149 s = s + b'\n'
150 150 return util.tolf(s)
151 151
152 152
153 153 def tocrlf(s, params, ui, **kwargs):
154 154 """Filter to convert to CRLF EOLs."""
155 155 if stringutil.binary(s):
156 156 return s
157 157 if ui.configbool(b'eol', b'only-consistent') and inconsistenteol(s):
158 158 return s
159 159 if (
160 160 ui.configbool(b'eol', b'fix-trailing-newline')
161 161 and s
162 162 and not s.endswith(b'\n')
163 163 ):
164 164 s = s + b'\n'
165 165 return util.tocrlf(s)
166 166
167 167
168 168 def isbinary(s, params, ui, **kwargs):
169 169 """Filter to do nothing with the file."""
170 170 return s
171 171
172 172
173 173 filters = {
174 174 b'to-lf': tolf,
175 175 b'to-crlf': tocrlf,
176 176 b'is-binary': isbinary,
177 177 # The following provide backwards compatibility with win32text
178 178 b'cleverencode:': tolf,
179 179 b'cleverdecode:': tocrlf,
180 180 }
181 181
182 182
183 183 class eolfile(object):
184 184 def __init__(self, ui, root, data):
185 185 self._decode = {
186 186 b'LF': b'to-lf',
187 187 b'CRLF': b'to-crlf',
188 188 b'BIN': b'is-binary',
189 189 }
190 190 self._encode = {
191 191 b'LF': b'to-lf',
192 192 b'CRLF': b'to-crlf',
193 193 b'BIN': b'is-binary',
194 194 }
195 195
196 196 self.cfg = config.config()
197 197 # Our files should not be touched. The pattern must be
198 198 # inserted first override a '** = native' pattern.
199 199 self.cfg.set(b'patterns', b'.hg*', b'BIN', b'eol')
200 200 # We can then parse the user's patterns.
201 201 self.cfg.parse(b'.hgeol', data)
202 202
203 203 isrepolf = self.cfg.get(b'repository', b'native') != b'CRLF'
204 204 self._encode[b'NATIVE'] = isrepolf and b'to-lf' or b'to-crlf'
205 205 iswdlf = ui.config(b'eol', b'native') in (b'LF', b'\n')
206 206 self._decode[b'NATIVE'] = iswdlf and b'to-lf' or b'to-crlf'
207 207
208 208 include = []
209 209 exclude = []
210 210 self.patterns = []
211 211 for pattern, style in self.cfg.items(b'patterns'):
212 212 key = style.upper()
213 213 if key == b'BIN':
214 214 exclude.append(pattern)
215 215 else:
216 216 include.append(pattern)
217 217 m = match.match(root, b'', [pattern])
218 218 self.patterns.append((pattern, key, m))
219 219 # This will match the files for which we need to care
220 220 # about inconsistent newlines.
221 221 self.match = match.match(root, b'', [], include, exclude)
222 222
223 223 def copytoui(self, ui):
224 224 newpatterns = {pattern for pattern, key, m in self.patterns}
225 225 for section in (b'decode', b'encode'):
226 226 for oldpattern, _filter in ui.configitems(section):
227 227 if oldpattern not in newpatterns:
228 228 if ui.configsource(section, oldpattern) == b'eol':
229 229 ui.setconfig(section, oldpattern, b'!', b'eol')
230 230 for pattern, key, m in self.patterns:
231 231 try:
232 232 ui.setconfig(b'decode', pattern, self._decode[key], b'eol')
233 233 ui.setconfig(b'encode', pattern, self._encode[key], b'eol')
234 234 except KeyError:
235 235 ui.warn(
236 236 _(b"ignoring unknown EOL style '%s' from %s\n")
237 237 % (key, self.cfg.source(b'patterns', pattern))
238 238 )
239 239 # eol.only-consistent can be specified in ~/.hgrc or .hgeol
240 240 for k, v in self.cfg.items(b'eol'):
241 241 ui.setconfig(b'eol', k, v, b'eol')
242 242
243 243 def checkrev(self, repo, ctx, files):
244 244 failed = []
245 245 for f in files or ctx.files():
246 246 if f not in ctx:
247 247 continue
248 248 for pattern, key, m in self.patterns:
249 249 if not m(f):
250 250 continue
251 251 target = self._encode[key]
252 252 data = ctx[f].data()
253 253 if (
254 254 target == b"to-lf"
255 255 and b"\r\n" in data
256 256 or target == b"to-crlf"
257 257 and singlelf.search(data)
258 258 ):
259 259 failed.append((f, target, bytes(ctx)))
260 260 break
261 261 return failed
262 262
263 263
264 264 def parseeol(ui, repo, nodes):
265 265 try:
266 266 for node in nodes:
267 267 try:
268 268 if node is None:
269 269 # Cannot use workingctx.data() since it would load
270 270 # and cache the filters before we configure them.
271 271 data = repo.wvfs(b'.hgeol').read()
272 272 else:
273 273 data = repo[node][b'.hgeol'].data()
274 274 return eolfile(ui, repo.root, data)
275 275 except (IOError, LookupError):
276 276 pass
277 except errormod.ParseError as inst:
277 except errormod.ConfigError as inst:
278 278 ui.warn(
279 279 _(
280 280 b"warning: ignoring .hgeol file due to parse error "
281 281 b"at %s: %s\n"
282 282 )
283 283 % (inst.location, inst.message)
284 284 )
285 285 return None
286 286
287 287
288 288 def ensureenabled(ui):
289 289 """make sure the extension is enabled when used as hook
290 290
291 291 When eol is used through hooks, the extension is never formally loaded and
292 292 enabled. This has some side effect, for example the config declaration is
293 293 never loaded. This function ensure the extension is enabled when running
294 294 hooks.
295 295 """
296 296 if b'eol' in ui._knownconfig:
297 297 return
298 298 ui.setconfig(b'extensions', b'eol', b'', source=b'internal')
299 299 extensions.loadall(ui, [b'eol'])
300 300
301 301
302 302 def _checkhook(ui, repo, node, headsonly):
303 303 # Get revisions to check and touched files at the same time
304 304 ensureenabled(ui)
305 305 files = set()
306 306 revs = set()
307 307 for rev in pycompat.xrange(repo[node].rev(), len(repo)):
308 308 revs.add(rev)
309 309 if headsonly:
310 310 ctx = repo[rev]
311 311 files.update(ctx.files())
312 312 for pctx in ctx.parents():
313 313 revs.discard(pctx.rev())
314 314 failed = []
315 315 for rev in revs:
316 316 ctx = repo[rev]
317 317 eol = parseeol(ui, repo, [ctx.node()])
318 318 if eol:
319 319 failed.extend(eol.checkrev(repo, ctx, files))
320 320
321 321 if failed:
322 322 eols = {b'to-lf': b'CRLF', b'to-crlf': b'LF'}
323 323 msgs = []
324 324 for f, target, node in sorted(failed):
325 325 msgs.append(
326 326 _(b" %s in %s should not have %s line endings")
327 327 % (f, node, eols[target])
328 328 )
329 329 raise errormod.Abort(
330 330 _(b"end-of-line check failed:\n") + b"\n".join(msgs)
331 331 )
332 332
333 333
334 334 def checkallhook(ui, repo, node, hooktype, **kwargs):
335 335 """verify that files have expected EOLs"""
336 336 _checkhook(ui, repo, node, False)
337 337
338 338
339 339 def checkheadshook(ui, repo, node, hooktype, **kwargs):
340 340 """verify that files have expected EOLs"""
341 341 _checkhook(ui, repo, node, True)
342 342
343 343
344 344 # "checkheadshook" used to be called "hook"
345 345 hook = checkheadshook
346 346
347 347
348 348 def preupdate(ui, repo, hooktype, parent1, parent2):
349 349 p1node = scmutil.resolvehexnodeidprefix(repo, parent1)
350 350 repo.loadeol([p1node])
351 351 return False
352 352
353 353
354 354 def uisetup(ui):
355 355 ui.setconfig(b'hooks', b'preupdate.eol', preupdate, b'eol')
356 356
357 357
358 358 def extsetup(ui):
359 359 try:
360 360 extensions.find(b'win32text')
361 361 ui.warn(
362 362 _(
363 363 b"the eol extension is incompatible with the "
364 364 b"win32text extension\n"
365 365 )
366 366 )
367 367 except KeyError:
368 368 pass
369 369
370 370
371 371 def reposetup(ui, repo):
372 372 uisetup(repo.ui)
373 373
374 374 if not repo.local():
375 375 return
376 376 for name, fn in pycompat.iteritems(filters):
377 377 repo.adddatafilter(name, fn)
378 378
379 379 ui.setconfig(b'patch', b'eol', b'auto', b'eol')
380 380
381 381 class eolrepo(repo.__class__):
382 382 def loadeol(self, nodes):
383 383 eol = parseeol(self.ui, self, nodes)
384 384 if eol is None:
385 385 return None
386 386 eol.copytoui(self.ui)
387 387 return eol.match
388 388
389 389 def _hgcleardirstate(self):
390 390 self._eolmatch = self.loadeol([None])
391 391 if not self._eolmatch:
392 392 self._eolmatch = util.never
393 393 return
394 394
395 395 oldeol = None
396 396 try:
397 397 cachemtime = os.path.getmtime(self.vfs.join(b"eol.cache"))
398 398 except OSError:
399 399 cachemtime = 0
400 400 else:
401 401 olddata = self.vfs.read(b"eol.cache")
402 402 if olddata:
403 403 oldeol = eolfile(self.ui, self.root, olddata)
404 404
405 405 try:
406 406 eolmtime = os.path.getmtime(self.wjoin(b".hgeol"))
407 407 except OSError:
408 408 eolmtime = 0
409 409
410 410 if eolmtime >= cachemtime and eolmtime > 0:
411 411 self.ui.debug(b"eol: detected change in .hgeol\n")
412 412
413 413 hgeoldata = self.wvfs.read(b'.hgeol')
414 414 neweol = eolfile(self.ui, self.root, hgeoldata)
415 415
416 416 wlock = None
417 417 try:
418 418 wlock = self.wlock()
419 419 for f in self.dirstate:
420 420 if self.dirstate[f] != b'n':
421 421 continue
422 422 if oldeol is not None:
423 423 if not oldeol.match(f) and not neweol.match(f):
424 424 continue
425 425 oldkey = None
426 426 for pattern, key, m in oldeol.patterns:
427 427 if m(f):
428 428 oldkey = key
429 429 break
430 430 newkey = None
431 431 for pattern, key, m in neweol.patterns:
432 432 if m(f):
433 433 newkey = key
434 434 break
435 435 if oldkey == newkey:
436 436 continue
437 437 # all normal files need to be looked at again since
438 438 # the new .hgeol file specify a different filter
439 439 self.dirstate.normallookup(f)
440 440 # Write the cache to update mtime and cache .hgeol
441 441 with self.vfs(b"eol.cache", b"w") as f:
442 442 f.write(hgeoldata)
443 443 except errormod.LockUnavailable:
444 444 # If we cannot lock the repository and clear the
445 445 # dirstate, then a commit might not see all files
446 446 # as modified. But if we cannot lock the
447 447 # repository, then we can also not make a commit,
448 448 # so ignore the error.
449 449 pass
450 450 finally:
451 451 if wlock is not None:
452 452 wlock.release()
453 453
454 454 def commitctx(self, ctx, error=False, origctx=None):
455 455 for f in sorted(ctx.added() + ctx.modified()):
456 456 if not self._eolmatch(f):
457 457 continue
458 458 fctx = ctx[f]
459 459 if fctx is None:
460 460 continue
461 461 data = fctx.data()
462 462 if stringutil.binary(data):
463 463 # We should not abort here, since the user should
464 464 # be able to say "** = native" to automatically
465 465 # have all non-binary files taken care of.
466 466 continue
467 467 if inconsistenteol(data):
468 468 raise errormod.Abort(
469 469 _(b"inconsistent newline style in %s\n") % f
470 470 )
471 471 return super(eolrepo, self).commitctx(ctx, error, origctx)
472 472
473 473 repo.__class__ = eolrepo
474 474 repo._hgcleardirstate()
@@ -1,314 +1,314 b''
1 1 # config.py - configuration parsing for Mercurial
2 2 #
3 3 # Copyright 2009 Matt Mackall <mpm@selenic.com> and others
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 os
12 12
13 13 from .i18n import _
14 14 from .pycompat import getattr
15 15 from . import (
16 16 encoding,
17 17 error,
18 18 pycompat,
19 19 util,
20 20 )
21 21
22 22
23 23 class config(object):
24 24 def __init__(self, data=None):
25 25 self._data = {}
26 26 self._unset = []
27 27 if data:
28 28 for k in data._data:
29 29 self._data[k] = data[k].copy()
30 30 self._source = data._source.copy()
31 31 else:
32 32 self._source = util.cowdict()
33 33
34 34 def copy(self):
35 35 return config(self)
36 36
37 37 def __contains__(self, section):
38 38 return section in self._data
39 39
40 40 def hasitem(self, section, item):
41 41 return item in self._data.get(section, {})
42 42
43 43 def __getitem__(self, section):
44 44 return self._data.get(section, {})
45 45
46 46 def __iter__(self):
47 47 for d in self.sections():
48 48 yield d
49 49
50 50 def update(self, src):
51 51 self._source = self._source.preparewrite()
52 52 for s, n in src._unset:
53 53 ds = self._data.get(s, None)
54 54 if ds is not None and n in ds:
55 55 self._data[s] = ds.preparewrite()
56 56 del self._data[s][n]
57 57 del self._source[(s, n)]
58 58 for s in src:
59 59 ds = self._data.get(s, None)
60 60 if ds:
61 61 self._data[s] = ds.preparewrite()
62 62 else:
63 63 self._data[s] = util.cowsortdict()
64 64 self._data[s].update(src._data[s])
65 65 self._source.update(src._source)
66 66
67 67 def get(self, section, item, default=None):
68 68 return self._data.get(section, {}).get(item, default)
69 69
70 70 def backup(self, section, item):
71 71 """return a tuple allowing restore to reinstall a previous value
72 72
73 73 The main reason we need it is because it handles the "no data" case.
74 74 """
75 75 try:
76 76 value = self._data[section][item]
77 77 source = self.source(section, item)
78 78 return (section, item, value, source)
79 79 except KeyError:
80 80 return (section, item)
81 81
82 82 def source(self, section, item):
83 83 return self._source.get((section, item), b"")
84 84
85 85 def sections(self):
86 86 return sorted(self._data.keys())
87 87
88 88 def items(self, section):
89 89 return list(pycompat.iteritems(self._data.get(section, {})))
90 90
91 91 def set(self, section, item, value, source=b""):
92 92 if pycompat.ispy3:
93 93 assert not isinstance(
94 94 section, str
95 95 ), b'config section may not be unicode strings on Python 3'
96 96 assert not isinstance(
97 97 item, str
98 98 ), b'config item may not be unicode strings on Python 3'
99 99 assert not isinstance(
100 100 value, str
101 101 ), b'config values may not be unicode strings on Python 3'
102 102 if section not in self:
103 103 self._data[section] = util.cowsortdict()
104 104 else:
105 105 self._data[section] = self._data[section].preparewrite()
106 106 self._data[section][item] = value
107 107 if source:
108 108 self._source = self._source.preparewrite()
109 109 self._source[(section, item)] = source
110 110
111 111 def restore(self, data):
112 112 """restore data returned by self.backup"""
113 113 self._source = self._source.preparewrite()
114 114 if len(data) == 4:
115 115 # restore old data
116 116 section, item, value, source = data
117 117 self._data[section] = self._data[section].preparewrite()
118 118 self._data[section][item] = value
119 119 self._source[(section, item)] = source
120 120 else:
121 121 # no data before, remove everything
122 122 section, item = data
123 123 if section in self._data:
124 124 self._data[section].pop(item, None)
125 125 self._source.pop((section, item), None)
126 126
127 127 def parse(self, src, data, sections=None, remap=None, include=None):
128 128 sectionre = util.re.compile(br'\[([^\[]+)\]')
129 129 itemre = util.re.compile(br'([^=\s][^=]*?)\s*=\s*(.*\S|)')
130 130 contre = util.re.compile(br'\s+(\S|\S.*\S)\s*$')
131 131 emptyre = util.re.compile(br'(;|#|\s*$)')
132 132 commentre = util.re.compile(br'(;|#)')
133 133 unsetre = util.re.compile(br'%unset\s+(\S+)')
134 134 includere = util.re.compile(br'%include\s+(\S|\S.*\S)\s*$')
135 135 section = b""
136 136 item = None
137 137 line = 0
138 138 cont = False
139 139
140 140 if remap:
141 141 section = remap.get(section, section)
142 142
143 143 for l in data.splitlines(True):
144 144 line += 1
145 145 if line == 1 and l.startswith(b'\xef\xbb\xbf'):
146 146 # Someone set us up the BOM
147 147 l = l[3:]
148 148 if cont:
149 149 if commentre.match(l):
150 150 continue
151 151 m = contre.match(l)
152 152 if m:
153 153 if sections and section not in sections:
154 154 continue
155 155 v = self.get(section, item) + b"\n" + m.group(1)
156 156 self.set(section, item, v, b"%s:%d" % (src, line))
157 157 continue
158 158 item = None
159 159 cont = False
160 160 m = includere.match(l)
161 161
162 162 if m and include:
163 163 expanded = util.expandpath(m.group(1))
164 164 try:
165 165 include(expanded, remap=remap, sections=sections)
166 166 except IOError as inst:
167 167 if inst.errno != errno.ENOENT:
168 raise error.ParseError(
168 raise error.ConfigError(
169 169 _(b"cannot include %s (%s)")
170 170 % (expanded, encoding.strtolocal(inst.strerror)),
171 171 b"%s:%d" % (src, line),
172 172 )
173 173 continue
174 174 if emptyre.match(l):
175 175 continue
176 176 m = sectionre.match(l)
177 177 if m:
178 178 section = m.group(1)
179 179 if remap:
180 180 section = remap.get(section, section)
181 181 if section not in self:
182 182 self._data[section] = util.cowsortdict()
183 183 continue
184 184 m = itemre.match(l)
185 185 if m:
186 186 item = m.group(1)
187 187 cont = True
188 188 if sections and section not in sections:
189 189 continue
190 190 self.set(section, item, m.group(2), b"%s:%d" % (src, line))
191 191 continue
192 192 m = unsetre.match(l)
193 193 if m:
194 194 name = m.group(1)
195 195 if sections and section not in sections:
196 196 continue
197 197 if self.get(section, name) is not None:
198 198 self._data[section] = self._data[section].preparewrite()
199 199 del self._data[section][name]
200 200 self._unset.append((section, name))
201 201 continue
202 202
203 203 message = l.rstrip()
204 204 if l.startswith(b' '):
205 205 message = b"unexpected leading whitespace: %s" % message
206 raise error.ParseError(message, (b"%s:%d" % (src, line)))
206 raise error.ConfigError(message, (b"%s:%d" % (src, line)))
207 207
208 208 def read(self, path, fp=None, sections=None, remap=None):
209 209 if not fp:
210 210 fp = util.posixfile(path, b'rb')
211 211 assert getattr(fp, 'mode', 'rb') == 'rb', (
212 212 b'config files must be opened in binary mode, got fp=%r mode=%r'
213 213 % (fp, fp.mode,)
214 214 )
215 215
216 216 dir = os.path.dirname(path)
217 217
218 218 def include(rel, remap, sections):
219 219 abs = os.path.normpath(os.path.join(dir, rel))
220 220 self.read(abs, remap=remap, sections=sections)
221 221
222 222 self.parse(
223 223 path, fp.read(), sections=sections, remap=remap, include=include
224 224 )
225 225
226 226
227 227 def parselist(value):
228 228 """parse a configuration value as a list of comma/space separated strings
229 229
230 230 >>> parselist(b'this,is "a small" ,test')
231 231 ['this', 'is', 'a small', 'test']
232 232 """
233 233
234 234 def _parse_plain(parts, s, offset):
235 235 whitespace = False
236 236 while offset < len(s) and (
237 237 s[offset : offset + 1].isspace() or s[offset : offset + 1] == b','
238 238 ):
239 239 whitespace = True
240 240 offset += 1
241 241 if offset >= len(s):
242 242 return None, parts, offset
243 243 if whitespace:
244 244 parts.append(b'')
245 245 if s[offset : offset + 1] == b'"' and not parts[-1]:
246 246 return _parse_quote, parts, offset + 1
247 247 elif s[offset : offset + 1] == b'"' and parts[-1][-1:] == b'\\':
248 248 parts[-1] = parts[-1][:-1] + s[offset : offset + 1]
249 249 return _parse_plain, parts, offset + 1
250 250 parts[-1] += s[offset : offset + 1]
251 251 return _parse_plain, parts, offset + 1
252 252
253 253 def _parse_quote(parts, s, offset):
254 254 if offset < len(s) and s[offset : offset + 1] == b'"': # ""
255 255 parts.append(b'')
256 256 offset += 1
257 257 while offset < len(s) and (
258 258 s[offset : offset + 1].isspace()
259 259 or s[offset : offset + 1] == b','
260 260 ):
261 261 offset += 1
262 262 return _parse_plain, parts, offset
263 263
264 264 while offset < len(s) and s[offset : offset + 1] != b'"':
265 265 if (
266 266 s[offset : offset + 1] == b'\\'
267 267 and offset + 1 < len(s)
268 268 and s[offset + 1 : offset + 2] == b'"'
269 269 ):
270 270 offset += 1
271 271 parts[-1] += b'"'
272 272 else:
273 273 parts[-1] += s[offset : offset + 1]
274 274 offset += 1
275 275
276 276 if offset >= len(s):
277 277 real_parts = _configlist(parts[-1])
278 278 if not real_parts:
279 279 parts[-1] = b'"'
280 280 else:
281 281 real_parts[0] = b'"' + real_parts[0]
282 282 parts = parts[:-1]
283 283 parts.extend(real_parts)
284 284 return None, parts, offset
285 285
286 286 offset += 1
287 287 while offset < len(s) and s[offset : offset + 1] in [b' ', b',']:
288 288 offset += 1
289 289
290 290 if offset < len(s):
291 291 if offset + 1 == len(s) and s[offset : offset + 1] == b'"':
292 292 parts[-1] += b'"'
293 293 offset += 1
294 294 else:
295 295 parts.append(b'')
296 296 else:
297 297 return None, parts, offset
298 298
299 299 return _parse_plain, parts, offset
300 300
301 301 def _configlist(s):
302 302 s = s.rstrip(b' ,')
303 303 if not s:
304 304 return []
305 305 parser, parts, offset = _parse_plain, [b''], 0
306 306 while parser:
307 307 parser, parts, offset = parser(parts, s, offset)
308 308 return parts
309 309
310 310 if value is not None and isinstance(value, bytes):
311 311 result = _configlist(value.lstrip(b' ,\n'))
312 312 else:
313 313 result = value
314 314 return result or []
@@ -1,556 +1,574 b''
1 1 # error.py - Mercurial exceptions
2 2 #
3 3 # Copyright 2005-2008 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 """Mercurial exceptions.
9 9
10 10 This allows us to catch exceptions at higher levels without forcing
11 11 imports.
12 12 """
13 13
14 14 from __future__ import absolute_import
15 15
16 16 import difflib
17 17
18 18 # Do not import anything but pycompat here, please
19 19 from . import pycompat
20 20
21 21
22 22 def _tobytes(exc):
23 23 """Byte-stringify exception in the same way as BaseException_str()"""
24 24 if not exc.args:
25 25 return b''
26 26 if len(exc.args) == 1:
27 27 return pycompat.bytestr(exc.args[0])
28 28 return b'(%s)' % b', '.join(b"'%s'" % pycompat.bytestr(a) for a in exc.args)
29 29
30 30
31 31 class Hint(object):
32 32 """Mix-in to provide a hint of an error
33 33
34 34 This should come first in the inheritance list to consume a hint and
35 35 pass remaining arguments to the exception class.
36 36 """
37 37
38 38 def __init__(self, *args, **kw):
39 39 self.hint = kw.pop('hint', None)
40 40 super(Hint, self).__init__(*args, **kw)
41 41
42 42
43 43 class StorageError(Hint, Exception):
44 44 """Raised when an error occurs in a storage layer.
45 45
46 46 Usually subclassed by a storage-specific exception.
47 47 """
48 48
49 49 __bytes__ = _tobytes
50 50
51 51
52 52 class RevlogError(StorageError):
53 53 pass
54 54
55 55
56 56 class SidedataHashError(RevlogError):
57 57 def __init__(self, key, expected, got):
58 58 self.sidedatakey = key
59 59 self.expecteddigest = expected
60 60 self.actualdigest = got
61 61
62 62
63 63 class FilteredIndexError(IndexError):
64 64 __bytes__ = _tobytes
65 65
66 66
67 67 class LookupError(RevlogError, KeyError):
68 68 def __init__(self, name, index, message):
69 69 self.name = name
70 70 self.index = index
71 71 # this can't be called 'message' because at least some installs of
72 72 # Python 2.6+ complain about the 'message' property being deprecated
73 73 self.lookupmessage = message
74 74 if isinstance(name, bytes) and len(name) == 20:
75 75 from .node import short
76 76
77 77 name = short(name)
78 78 # if name is a binary node, it can be None
79 79 RevlogError.__init__(
80 80 self, b'%s@%s: %s' % (index, pycompat.bytestr(name), message)
81 81 )
82 82
83 83 def __bytes__(self):
84 84 return RevlogError.__bytes__(self)
85 85
86 86 def __str__(self):
87 87 return RevlogError.__str__(self)
88 88
89 89
90 90 class AmbiguousPrefixLookupError(LookupError):
91 91 pass
92 92
93 93
94 94 class FilteredLookupError(LookupError):
95 95 pass
96 96
97 97
98 98 class ManifestLookupError(LookupError):
99 99 pass
100 100
101 101
102 102 class CommandError(Exception):
103 103 """Exception raised on errors in parsing the command line."""
104 104
105 105 def __init__(self, command, message):
106 106 self.command = command
107 107 self.message = message
108 108 super(CommandError, self).__init__()
109 109
110 110 __bytes__ = _tobytes
111 111
112 112
113 113 class UnknownCommand(Exception):
114 114 """Exception raised if command is not in the command table."""
115 115
116 116 def __init__(self, command, all_commands=None):
117 117 self.command = command
118 118 self.all_commands = all_commands
119 119 super(UnknownCommand, self).__init__()
120 120
121 121 __bytes__ = _tobytes
122 122
123 123
124 124 class AmbiguousCommand(Exception):
125 125 """Exception raised if command shortcut matches more than one command."""
126 126
127 127 def __init__(self, prefix, matches):
128 128 self.prefix = prefix
129 129 self.matches = matches
130 130 super(AmbiguousCommand, self).__init__()
131 131
132 132 __bytes__ = _tobytes
133 133
134 134
135 135 class WorkerError(Exception):
136 136 """Exception raised when a worker process dies."""
137 137
138 138 def __init__(self, status_code):
139 139 self.status_code = status_code
140 140
141 141
142 142 class InterventionRequired(Hint, Exception):
143 143 """Exception raised when a command requires human intervention."""
144 144
145 145 __bytes__ = _tobytes
146 146
147 147
148 148 class ConflictResolutionRequired(InterventionRequired):
149 149 """Exception raised when a continuable command required merge conflict resolution."""
150 150
151 151 def __init__(self, opname):
152 152 from .i18n import _
153 153
154 154 self.opname = opname
155 155 InterventionRequired.__init__(
156 156 self,
157 157 _(
158 158 b"unresolved conflicts (see 'hg resolve', then 'hg %s --continue')"
159 159 )
160 160 % opname,
161 161 )
162 162
163 163
164 164 class Abort(Hint, Exception):
165 165 """Raised if a command needs to print an error and exit."""
166 166
167 167 def __init__(self, message, hint=None):
168 168 self.message = message
169 169 self.hint = hint
170 170 # Pass the message into the Exception constructor to help extensions
171 171 # that look for exc.args[0].
172 172 Exception.__init__(self, message)
173 173
174 174 def __bytes__(self):
175 175 return self.message
176 176
177 177 if pycompat.ispy3:
178 178
179 179 def __str__(self):
180 180 # the output would be unreadable if the message was translated,
181 181 # but do not replace it with encoding.strfromlocal(), which
182 182 # may raise another exception.
183 183 return pycompat.sysstr(self.__bytes__())
184 184
185 185 def format(self):
186 186 from .i18n import _
187 187
188 188 message = _(b"abort: %s\n") % self.message
189 189 if self.hint:
190 190 message += _(b"(%s)\n") % self.hint
191 191 return message
192 192
193 193
194 194 class InputError(Abort):
195 195 """Indicates that the user made an error in their input.
196 196
197 197 Examples: Invalid command, invalid flags, invalid revision.
198 198 """
199 199
200 200
201 201 class StateError(Abort):
202 202 """Indicates that the operation might work if retried in a different state.
203 203
204 204 Examples: Unresolved merge conflicts, unfinished operations.
205 205 """
206 206
207 207
208 208 class CanceledError(Abort):
209 209 """Indicates that the user canceled the operation.
210 210
211 211 Examples: Close commit editor with error status, quit chistedit.
212 212 """
213 213
214 214
215 215 class HookLoadError(Abort):
216 216 """raised when loading a hook fails, aborting an operation
217 217
218 218 Exists to allow more specialized catching."""
219 219
220 220
221 221 class HookAbort(Abort):
222 222 """raised when a validation hook fails, aborting an operation
223 223
224 224 Exists to allow more specialized catching."""
225 225
226 226
227 227 class ConfigError(Abort):
228 228 """Exception raised when parsing config files"""
229 229
230 def __init__(self, message, location=None, hint=None):
231 super(ConfigError, self).__init__(message, hint=hint)
232 self.location = location
233
234 def format(self):
235 from .i18n import _
236
237 if self.location is not None:
238 message = _(b"config error at %s: %s\n") % (
239 pycompat.bytestr(self.location),
240 self.message,
241 )
242 else:
243 message = _(b"config error: %s\n") % self.message
244 if self.hint:
245 message += _(b"(%s)\n") % self.hint
246 return message
247
230 248
231 249 class UpdateAbort(Abort):
232 250 """Raised when an update is aborted for destination issue"""
233 251
234 252
235 253 class MergeDestAbort(Abort):
236 254 """Raised when an update is aborted for destination issues"""
237 255
238 256
239 257 class NoMergeDestAbort(MergeDestAbort):
240 258 """Raised when an update is aborted because there is nothing to merge"""
241 259
242 260
243 261 class ManyMergeDestAbort(MergeDestAbort):
244 262 """Raised when an update is aborted because destination is ambiguous"""
245 263
246 264
247 265 class ResponseExpected(Abort):
248 266 """Raised when an EOF is received for a prompt"""
249 267
250 268 def __init__(self):
251 269 from .i18n import _
252 270
253 271 Abort.__init__(self, _(b'response expected'))
254 272
255 273
256 274 class OutOfBandError(Hint, Exception):
257 275 """Exception raised when a remote repo reports failure"""
258 276
259 277 __bytes__ = _tobytes
260 278
261 279
262 280 class ParseError(Abort):
263 281 """Raised when parsing config files and {rev,file}sets (msg[, pos])"""
264 282
265 283 def __init__(self, message, location=None, hint=None):
266 284 super(ParseError, self).__init__(message, hint=hint)
267 285 self.location = location
268 286
269 287 def format(self):
270 288 from .i18n import _
271 289
272 290 if self.location is not None:
273 291 message = _(b"hg: parse error at %s: %s\n") % (
274 292 pycompat.bytestr(self.location),
275 293 self.message,
276 294 )
277 295 else:
278 296 message = _(b"hg: parse error: %s\n") % self.message
279 297 if self.hint:
280 298 message += _(b"(%s)\n") % self.hint
281 299 return message
282 300
283 301
284 302 class PatchError(Exception):
285 303 __bytes__ = _tobytes
286 304
287 305
288 306 def getsimilar(symbols, value):
289 307 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
290 308 # The cutoff for similarity here is pretty arbitrary. It should
291 309 # probably be investigated and tweaked.
292 310 return [s for s in symbols if sim(s) > 0.6]
293 311
294 312
295 313 def similarity_hint(similar):
296 314 from .i18n import _
297 315
298 316 if len(similar) == 1:
299 317 return _(b"did you mean %s?") % similar[0]
300 318 elif similar:
301 319 ss = b", ".join(sorted(similar))
302 320 return _(b"did you mean one of %s?") % ss
303 321 else:
304 322 return None
305 323
306 324
307 325 class UnknownIdentifier(ParseError):
308 326 """Exception raised when a {rev,file}set references an unknown identifier"""
309 327
310 328 def __init__(self, function, symbols):
311 329 from .i18n import _
312 330
313 331 similar = getsimilar(symbols, function)
314 332 hint = similarity_hint(similar)
315 333
316 334 ParseError.__init__(
317 335 self, _(b"unknown identifier: %s") % function, hint=hint
318 336 )
319 337
320 338
321 339 class RepoError(Hint, Exception):
322 340 __bytes__ = _tobytes
323 341
324 342
325 343 class RepoLookupError(RepoError):
326 344 pass
327 345
328 346
329 347 class FilteredRepoLookupError(RepoLookupError):
330 348 pass
331 349
332 350
333 351 class CapabilityError(RepoError):
334 352 pass
335 353
336 354
337 355 class RequirementError(RepoError):
338 356 """Exception raised if .hg/requires has an unknown entry."""
339 357
340 358
341 359 class StdioError(IOError):
342 360 """Raised if I/O to stdout or stderr fails"""
343 361
344 362 def __init__(self, err):
345 363 IOError.__init__(self, err.errno, err.strerror)
346 364
347 365 # no __bytes__() because error message is derived from the standard IOError
348 366
349 367
350 368 class UnsupportedMergeRecords(Abort):
351 369 def __init__(self, recordtypes):
352 370 from .i18n import _
353 371
354 372 self.recordtypes = sorted(recordtypes)
355 373 s = b' '.join(self.recordtypes)
356 374 Abort.__init__(
357 375 self,
358 376 _(b'unsupported merge state records: %s') % s,
359 377 hint=_(
360 378 b'see https://mercurial-scm.org/wiki/MergeStateRecords for '
361 379 b'more information'
362 380 ),
363 381 )
364 382
365 383
366 384 class UnknownVersion(Abort):
367 385 """generic exception for aborting from an encounter with an unknown version
368 386 """
369 387
370 388 def __init__(self, msg, hint=None, version=None):
371 389 self.version = version
372 390 super(UnknownVersion, self).__init__(msg, hint=hint)
373 391
374 392
375 393 class LockError(IOError):
376 394 def __init__(self, errno, strerror, filename, desc):
377 395 IOError.__init__(self, errno, strerror, filename)
378 396 self.desc = desc
379 397
380 398 # no __bytes__() because error message is derived from the standard IOError
381 399
382 400
383 401 class LockHeld(LockError):
384 402 def __init__(self, errno, filename, desc, locker):
385 403 LockError.__init__(self, errno, b'Lock held', filename, desc)
386 404 self.locker = locker
387 405
388 406
389 407 class LockUnavailable(LockError):
390 408 pass
391 409
392 410
393 411 # LockError is for errors while acquiring the lock -- this is unrelated
394 412 class LockInheritanceContractViolation(RuntimeError):
395 413 __bytes__ = _tobytes
396 414
397 415
398 416 class ResponseError(Exception):
399 417 """Raised to print an error with part of output and exit."""
400 418
401 419 __bytes__ = _tobytes
402 420
403 421
404 422 # derived from KeyboardInterrupt to simplify some breakout code
405 423 class SignalInterrupt(KeyboardInterrupt):
406 424 """Exception raised on SIGTERM and SIGHUP."""
407 425
408 426
409 427 class SignatureError(Exception):
410 428 __bytes__ = _tobytes
411 429
412 430
413 431 class PushRaced(RuntimeError):
414 432 """An exception raised during unbundling that indicate a push race"""
415 433
416 434 __bytes__ = _tobytes
417 435
418 436
419 437 class ProgrammingError(Hint, RuntimeError):
420 438 """Raised if a mercurial (core or extension) developer made a mistake"""
421 439
422 440 def __init__(self, msg, *args, **kwargs):
423 441 # On Python 3, turn the message back into a string since this is
424 442 # an internal-only error that won't be printed except in a
425 443 # stack traces.
426 444 msg = pycompat.sysstr(msg)
427 445 super(ProgrammingError, self).__init__(msg, *args, **kwargs)
428 446
429 447 __bytes__ = _tobytes
430 448
431 449
432 450 class WdirUnsupported(Exception):
433 451 """An exception which is raised when 'wdir()' is not supported"""
434 452
435 453 __bytes__ = _tobytes
436 454
437 455
438 456 # bundle2 related errors
439 457 class BundleValueError(ValueError):
440 458 """error raised when bundle2 cannot be processed"""
441 459
442 460 __bytes__ = _tobytes
443 461
444 462
445 463 class BundleUnknownFeatureError(BundleValueError):
446 464 def __init__(self, parttype=None, params=(), values=()):
447 465 self.parttype = parttype
448 466 self.params = params
449 467 self.values = values
450 468 if self.parttype is None:
451 469 msg = b'Stream Parameter'
452 470 else:
453 471 msg = parttype
454 472 entries = self.params
455 473 if self.params and self.values:
456 474 assert len(self.params) == len(self.values)
457 475 entries = []
458 476 for idx, par in enumerate(self.params):
459 477 val = self.values[idx]
460 478 if val is None:
461 479 entries.append(val)
462 480 else:
463 481 entries.append(b"%s=%r" % (par, pycompat.maybebytestr(val)))
464 482 if entries:
465 483 msg = b'%s - %s' % (msg, b', '.join(entries))
466 484 ValueError.__init__(self, msg)
467 485
468 486
469 487 class ReadOnlyPartError(RuntimeError):
470 488 """error raised when code tries to alter a part being generated"""
471 489
472 490 __bytes__ = _tobytes
473 491
474 492
475 493 class PushkeyFailed(Abort):
476 494 """error raised when a pushkey part failed to update a value"""
477 495
478 496 def __init__(
479 497 self, partid, namespace=None, key=None, new=None, old=None, ret=None
480 498 ):
481 499 self.partid = partid
482 500 self.namespace = namespace
483 501 self.key = key
484 502 self.new = new
485 503 self.old = old
486 504 self.ret = ret
487 505 # no i18n expected to be processed into a better message
488 506 Abort.__init__(
489 507 self, b'failed to update value for "%s/%s"' % (namespace, key)
490 508 )
491 509
492 510
493 511 class CensoredNodeError(StorageError):
494 512 """error raised when content verification fails on a censored node
495 513
496 514 Also contains the tombstone data substituted for the uncensored data.
497 515 """
498 516
499 517 def __init__(self, filename, node, tombstone):
500 518 from .node import short
501 519
502 520 StorageError.__init__(self, b'%s:%s' % (filename, short(node)))
503 521 self.tombstone = tombstone
504 522
505 523
506 524 class CensoredBaseError(StorageError):
507 525 """error raised when a delta is rejected because its base is censored
508 526
509 527 A delta based on a censored revision must be formed as single patch
510 528 operation which replaces the entire base with new content. This ensures
511 529 the delta may be applied by clones which have not censored the base.
512 530 """
513 531
514 532
515 533 class InvalidBundleSpecification(Exception):
516 534 """error raised when a bundle specification is invalid.
517 535
518 536 This is used for syntax errors as opposed to support errors.
519 537 """
520 538
521 539 __bytes__ = _tobytes
522 540
523 541
524 542 class UnsupportedBundleSpecification(Exception):
525 543 """error raised when a bundle specification is not supported."""
526 544
527 545 __bytes__ = _tobytes
528 546
529 547
530 548 class CorruptedState(Exception):
531 549 """error raised when a command is not able to read its state from file"""
532 550
533 551 __bytes__ = _tobytes
534 552
535 553
536 554 class PeerTransportError(Abort):
537 555 """Transport-level I/O error when communicating with a peer repo."""
538 556
539 557
540 558 class InMemoryMergeConflictsError(Exception):
541 559 """Exception raised when merge conflicts arose during an in-memory merge."""
542 560
543 561 __bytes__ = _tobytes
544 562
545 563
546 564 class WireprotoCommandError(Exception):
547 565 """Represents an error during execution of a wire protocol command.
548 566
549 567 Should only be thrown by wire protocol version 2 commands.
550 568
551 569 The error is a formatter string and an optional iterable of arguments.
552 570 """
553 571
554 572 def __init__(self, message, args=None):
555 573 self.message = message
556 574 self.messageargs = args
@@ -1,2376 +1,2376 b''
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005-2007 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 collections
11 11 import contextlib
12 12 import datetime
13 13 import errno
14 14 import getpass
15 15 import inspect
16 16 import os
17 17 import re
18 18 import signal
19 19 import socket
20 20 import subprocess
21 21 import sys
22 22 import traceback
23 23
24 24 from .i18n import _
25 25 from .node import hex
26 26 from .pycompat import (
27 27 getattr,
28 28 open,
29 29 setattr,
30 30 )
31 31
32 32 from . import (
33 33 color,
34 34 config,
35 35 configitems,
36 36 encoding,
37 37 error,
38 38 formatter,
39 39 loggingutil,
40 40 progress,
41 41 pycompat,
42 42 rcutil,
43 43 scmutil,
44 44 util,
45 45 )
46 46 from .utils import (
47 47 dateutil,
48 48 procutil,
49 49 resourceutil,
50 50 stringutil,
51 51 )
52 52
53 53 urlreq = util.urlreq
54 54
55 55 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
56 56 _keepalnum = b''.join(
57 57 c for c in map(pycompat.bytechr, range(256)) if not c.isalnum()
58 58 )
59 59
60 60 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
61 61 tweakrc = b"""
62 62 [ui]
63 63 # Gives detailed exit codes for input/user errors, config errors, etc.
64 64 detailed-exit-code = True
65 65 # The rollback command is dangerous. As a rule, don't use it.
66 66 rollback = False
67 67 # Make `hg status` report copy information
68 68 statuscopies = yes
69 69 # Prefer curses UIs when available. Revert to plain-text with `text`.
70 70 interface = curses
71 71 # Make compatible commands emit cwd-relative paths by default.
72 72 relative-paths = yes
73 73
74 74 [commands]
75 75 # Grep working directory by default.
76 76 grep.all-files = True
77 77 # Refuse to perform an `hg update` that would cause a file content merge
78 78 update.check = noconflict
79 79 # Show conflicts information in `hg status`
80 80 status.verbose = True
81 81 # Make `hg resolve` with no action (like `-m`) fail instead of re-merging.
82 82 resolve.explicit-re-merge = True
83 83
84 84 [diff]
85 85 git = 1
86 86 showfunc = 1
87 87 word-diff = 1
88 88 """
89 89
90 90 samplehgrcs = {
91 91 b'user': b"""# example user config (see 'hg help config' for more info)
92 92 [ui]
93 93 # name and email, e.g.
94 94 # username = Jane Doe <jdoe@example.com>
95 95 username =
96 96
97 97 # We recommend enabling tweakdefaults to get slight improvements to
98 98 # the UI over time. Make sure to set HGPLAIN in the environment when
99 99 # writing scripts!
100 100 # tweakdefaults = True
101 101
102 102 # uncomment to disable color in command output
103 103 # (see 'hg help color' for details)
104 104 # color = never
105 105
106 106 # uncomment to disable command output pagination
107 107 # (see 'hg help pager' for details)
108 108 # paginate = never
109 109
110 110 [extensions]
111 111 # uncomment the lines below to enable some popular extensions
112 112 # (see 'hg help extensions' for more info)
113 113 #
114 114 # histedit =
115 115 # rebase =
116 116 # uncommit =
117 117 """,
118 118 b'cloned': b"""# example repository config (see 'hg help config' for more info)
119 119 [paths]
120 120 default = %s
121 121
122 122 # path aliases to other clones of this repo in URLs or filesystem paths
123 123 # (see 'hg help config.paths' for more info)
124 124 #
125 125 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
126 126 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
127 127 # my-clone = /home/jdoe/jdoes-clone
128 128
129 129 [ui]
130 130 # name and email (local to this repository, optional), e.g.
131 131 # username = Jane Doe <jdoe@example.com>
132 132 """,
133 133 b'local': b"""# example repository config (see 'hg help config' for more info)
134 134 [paths]
135 135 # path aliases to other clones of this repo in URLs or filesystem paths
136 136 # (see 'hg help config.paths' for more info)
137 137 #
138 138 # default = http://example.com/hg/example-repo
139 139 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
140 140 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
141 141 # my-clone = /home/jdoe/jdoes-clone
142 142
143 143 [ui]
144 144 # name and email (local to this repository, optional), e.g.
145 145 # username = Jane Doe <jdoe@example.com>
146 146 """,
147 147 b'global': b"""# example system-wide hg config (see 'hg help config' for more info)
148 148
149 149 [ui]
150 150 # uncomment to disable color in command output
151 151 # (see 'hg help color' for details)
152 152 # color = never
153 153
154 154 # uncomment to disable command output pagination
155 155 # (see 'hg help pager' for details)
156 156 # paginate = never
157 157
158 158 [extensions]
159 159 # uncomment the lines below to enable some popular extensions
160 160 # (see 'hg help extensions' for more info)
161 161 #
162 162 # blackbox =
163 163 # churn =
164 164 """,
165 165 }
166 166
167 167
168 168 def _maybestrurl(maybebytes):
169 169 return pycompat.rapply(pycompat.strurl, maybebytes)
170 170
171 171
172 172 def _maybebytesurl(maybestr):
173 173 return pycompat.rapply(pycompat.bytesurl, maybestr)
174 174
175 175
176 176 class httppasswordmgrdbproxy(object):
177 177 """Delays loading urllib2 until it's needed."""
178 178
179 179 def __init__(self):
180 180 self._mgr = None
181 181
182 182 def _get_mgr(self):
183 183 if self._mgr is None:
184 184 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
185 185 return self._mgr
186 186
187 187 def add_password(self, realm, uris, user, passwd):
188 188 return self._get_mgr().add_password(
189 189 _maybestrurl(realm),
190 190 _maybestrurl(uris),
191 191 _maybestrurl(user),
192 192 _maybestrurl(passwd),
193 193 )
194 194
195 195 def find_user_password(self, realm, uri):
196 196 mgr = self._get_mgr()
197 197 return _maybebytesurl(
198 198 mgr.find_user_password(_maybestrurl(realm), _maybestrurl(uri))
199 199 )
200 200
201 201
202 202 def _catchterm(*args):
203 203 raise error.SignalInterrupt
204 204
205 205
206 206 # unique object used to detect no default value has been provided when
207 207 # retrieving configuration value.
208 208 _unset = object()
209 209
210 210 # _reqexithandlers: callbacks run at the end of a request
211 211 _reqexithandlers = []
212 212
213 213
214 214 class ui(object):
215 215 def __init__(self, src=None):
216 216 """Create a fresh new ui object if no src given
217 217
218 218 Use uimod.ui.load() to create a ui which knows global and user configs.
219 219 In most cases, you should use ui.copy() to create a copy of an existing
220 220 ui object.
221 221 """
222 222 # _buffers: used for temporary capture of output
223 223 self._buffers = []
224 224 # 3-tuple describing how each buffer in the stack behaves.
225 225 # Values are (capture stderr, capture subprocesses, apply labels).
226 226 self._bufferstates = []
227 227 # When a buffer is active, defines whether we are expanding labels.
228 228 # This exists to prevent an extra list lookup.
229 229 self._bufferapplylabels = None
230 230 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
231 231 self._reportuntrusted = True
232 232 self._knownconfig = configitems.coreitems
233 233 self._ocfg = config.config() # overlay
234 234 self._tcfg = config.config() # trusted
235 235 self._ucfg = config.config() # untrusted
236 236 self._trustusers = set()
237 237 self._trustgroups = set()
238 238 self.callhooks = True
239 239 # Insecure server connections requested.
240 240 self.insecureconnections = False
241 241 # Blocked time
242 242 self.logblockedtimes = False
243 243 # color mode: see mercurial/color.py for possible value
244 244 self._colormode = None
245 245 self._terminfoparams = {}
246 246 self._styles = {}
247 247 self._uninterruptible = False
248 248 self.showtimestamp = False
249 249
250 250 if src:
251 251 self._fout = src._fout
252 252 self._ferr = src._ferr
253 253 self._fin = src._fin
254 254 self._fmsg = src._fmsg
255 255 self._fmsgout = src._fmsgout
256 256 self._fmsgerr = src._fmsgerr
257 257 self._finoutredirected = src._finoutredirected
258 258 self._loggers = src._loggers.copy()
259 259 self.pageractive = src.pageractive
260 260 self._disablepager = src._disablepager
261 261 self._tweaked = src._tweaked
262 262
263 263 self._tcfg = src._tcfg.copy()
264 264 self._ucfg = src._ucfg.copy()
265 265 self._ocfg = src._ocfg.copy()
266 266 self._trustusers = src._trustusers.copy()
267 267 self._trustgroups = src._trustgroups.copy()
268 268 self.environ = src.environ
269 269 self.callhooks = src.callhooks
270 270 self.insecureconnections = src.insecureconnections
271 271 self._colormode = src._colormode
272 272 self._terminfoparams = src._terminfoparams.copy()
273 273 self._styles = src._styles.copy()
274 274
275 275 self.fixconfig()
276 276
277 277 self.httppasswordmgrdb = src.httppasswordmgrdb
278 278 self._blockedtimes = src._blockedtimes
279 279 else:
280 280 self._fout = procutil.stdout
281 281 self._ferr = procutil.stderr
282 282 self._fin = procutil.stdin
283 283 self._fmsg = None
284 284 self._fmsgout = self.fout # configurable
285 285 self._fmsgerr = self.ferr # configurable
286 286 self._finoutredirected = False
287 287 self._loggers = {}
288 288 self.pageractive = False
289 289 self._disablepager = False
290 290 self._tweaked = False
291 291
292 292 # shared read-only environment
293 293 self.environ = encoding.environ
294 294
295 295 self.httppasswordmgrdb = httppasswordmgrdbproxy()
296 296 self._blockedtimes = collections.defaultdict(int)
297 297
298 298 allowed = self.configlist(b'experimental', b'exportableenviron')
299 299 if b'*' in allowed:
300 300 self._exportableenviron = self.environ
301 301 else:
302 302 self._exportableenviron = {}
303 303 for k in allowed:
304 304 if k in self.environ:
305 305 self._exportableenviron[k] = self.environ[k]
306 306
307 307 @classmethod
308 308 def load(cls):
309 309 """Create a ui and load global and user configs"""
310 310 u = cls()
311 311 # we always trust global config files and environment variables
312 312 for t, f in rcutil.rccomponents():
313 313 if t == b'path':
314 314 u.readconfig(f, trust=True)
315 315 elif t == b'resource':
316 316 u.read_resource_config(f, trust=True)
317 317 elif t == b'items':
318 318 sections = set()
319 319 for section, name, value, source in f:
320 320 # do not set u._ocfg
321 321 # XXX clean this up once immutable config object is a thing
322 322 u._tcfg.set(section, name, value, source)
323 323 u._ucfg.set(section, name, value, source)
324 324 sections.add(section)
325 325 for section in sections:
326 326 u.fixconfig(section=section)
327 327 else:
328 328 raise error.ProgrammingError(b'unknown rctype: %s' % t)
329 329 u._maybetweakdefaults()
330 330 return u
331 331
332 332 def _maybetweakdefaults(self):
333 333 if not self.configbool(b'ui', b'tweakdefaults'):
334 334 return
335 335 if self._tweaked or self.plain(b'tweakdefaults'):
336 336 return
337 337
338 338 # Note: it is SUPER IMPORTANT that you set self._tweaked to
339 339 # True *before* any calls to setconfig(), otherwise you'll get
340 340 # infinite recursion between setconfig and this method.
341 341 #
342 342 # TODO: We should extract an inner method in setconfig() to
343 343 # avoid this weirdness.
344 344 self._tweaked = True
345 345 tmpcfg = config.config()
346 346 tmpcfg.parse(b'<tweakdefaults>', tweakrc)
347 347 for section in tmpcfg:
348 348 for name, value in tmpcfg.items(section):
349 349 if not self.hasconfig(section, name):
350 350 self.setconfig(section, name, value, b"<tweakdefaults>")
351 351
352 352 def copy(self):
353 353 return self.__class__(self)
354 354
355 355 def resetstate(self):
356 356 """Clear internal state that shouldn't persist across commands"""
357 357 if self._progbar:
358 358 self._progbar.resetstate() # reset last-print time of progress bar
359 359 self.httppasswordmgrdb = httppasswordmgrdbproxy()
360 360
361 361 @contextlib.contextmanager
362 362 def timeblockedsection(self, key):
363 363 # this is open-coded below - search for timeblockedsection to find them
364 364 starttime = util.timer()
365 365 try:
366 366 yield
367 367 finally:
368 368 self._blockedtimes[key + b'_blocked'] += (
369 369 util.timer() - starttime
370 370 ) * 1000
371 371
372 372 @contextlib.contextmanager
373 373 def uninterruptible(self):
374 374 """Mark an operation as unsafe.
375 375
376 376 Most operations on a repository are safe to interrupt, but a
377 377 few are risky (for example repair.strip). This context manager
378 378 lets you advise Mercurial that something risky is happening so
379 379 that control-C etc can be blocked if desired.
380 380 """
381 381 enabled = self.configbool(b'experimental', b'nointerrupt')
382 382 if enabled and self.configbool(
383 383 b'experimental', b'nointerrupt-interactiveonly'
384 384 ):
385 385 enabled = self.interactive()
386 386 if self._uninterruptible or not enabled:
387 387 # if nointerrupt support is turned off, the process isn't
388 388 # interactive, or we're already in an uninterruptible
389 389 # block, do nothing.
390 390 yield
391 391 return
392 392
393 393 def warn():
394 394 self.warn(_(b"shutting down cleanly\n"))
395 395 self.warn(
396 396 _(b"press ^C again to terminate immediately (dangerous)\n")
397 397 )
398 398 return True
399 399
400 400 with procutil.uninterruptible(warn):
401 401 try:
402 402 self._uninterruptible = True
403 403 yield
404 404 finally:
405 405 self._uninterruptible = False
406 406
407 407 def formatter(self, topic, opts):
408 408 return formatter.formatter(self, self, topic, opts)
409 409
410 410 def _trusted(self, fp, f):
411 411 st = util.fstat(fp)
412 412 if util.isowner(st):
413 413 return True
414 414
415 415 tusers, tgroups = self._trustusers, self._trustgroups
416 416 if b'*' in tusers or b'*' in tgroups:
417 417 return True
418 418
419 419 user = util.username(st.st_uid)
420 420 group = util.groupname(st.st_gid)
421 421 if user in tusers or group in tgroups or user == util.username():
422 422 return True
423 423
424 424 if self._reportuntrusted:
425 425 self.warn(
426 426 _(
427 427 b'not trusting file %s from untrusted '
428 428 b'user %s, group %s\n'
429 429 )
430 430 % (f, user, group)
431 431 )
432 432 return False
433 433
434 434 def read_resource_config(
435 435 self, name, root=None, trust=False, sections=None, remap=None
436 436 ):
437 437 try:
438 438 fp = resourceutil.open_resource(name[0], name[1])
439 439 except IOError:
440 440 if not sections: # ignore unless we were looking for something
441 441 return
442 442 raise
443 443
444 444 self._readconfig(
445 445 b'resource:%s.%s' % name, fp, root, trust, sections, remap
446 446 )
447 447
448 448 def readconfig(
449 449 self, filename, root=None, trust=False, sections=None, remap=None
450 450 ):
451 451 try:
452 452 fp = open(filename, 'rb')
453 453 except IOError:
454 454 if not sections: # ignore unless we were looking for something
455 455 return
456 456 raise
457 457
458 458 self._readconfig(filename, fp, root, trust, sections, remap)
459 459
460 460 def _readconfig(
461 461 self, filename, fp, root=None, trust=False, sections=None, remap=None
462 462 ):
463 463 with fp:
464 464 cfg = config.config()
465 465 trusted = sections or trust or self._trusted(fp, filename)
466 466
467 467 try:
468 468 cfg.read(filename, fp, sections=sections, remap=remap)
469 except error.ParseError as inst:
469 except error.ConfigError as inst:
470 470 if trusted:
471 471 raise
472 472 self.warn(
473 473 _(b'ignored %s: %s\n') % (inst.location, inst.message)
474 474 )
475 475
476 476 self._applyconfig(cfg, trusted, root)
477 477
478 478 def applyconfig(self, configitems, source=b"", root=None):
479 479 """Add configitems from a non-file source. Unlike with ``setconfig()``,
480 480 they can be overridden by subsequent config file reads. The items are
481 481 in the same format as ``configoverride()``, namely a dict of the
482 482 following structures: {(section, name) : value}
483 483
484 484 Typically this is used by extensions that inject themselves into the
485 485 config file load procedure by monkeypatching ``localrepo.loadhgrc()``.
486 486 """
487 487 cfg = config.config()
488 488
489 489 for (section, name), value in configitems.items():
490 490 cfg.set(section, name, value, source)
491 491
492 492 self._applyconfig(cfg, True, root)
493 493
494 494 def _applyconfig(self, cfg, trusted, root):
495 495 if self.plain():
496 496 for k in (
497 497 b'debug',
498 498 b'fallbackencoding',
499 499 b'quiet',
500 500 b'slash',
501 501 b'logtemplate',
502 502 b'message-output',
503 503 b'statuscopies',
504 504 b'style',
505 505 b'traceback',
506 506 b'verbose',
507 507 ):
508 508 if k in cfg[b'ui']:
509 509 del cfg[b'ui'][k]
510 510 for k, v in cfg.items(b'defaults'):
511 511 del cfg[b'defaults'][k]
512 512 for k, v in cfg.items(b'commands'):
513 513 del cfg[b'commands'][k]
514 514 for k, v in cfg.items(b'command-templates'):
515 515 del cfg[b'command-templates'][k]
516 516 # Don't remove aliases from the configuration if in the exceptionlist
517 517 if self.plain(b'alias'):
518 518 for k, v in cfg.items(b'alias'):
519 519 del cfg[b'alias'][k]
520 520 if self.plain(b'revsetalias'):
521 521 for k, v in cfg.items(b'revsetalias'):
522 522 del cfg[b'revsetalias'][k]
523 523 if self.plain(b'templatealias'):
524 524 for k, v in cfg.items(b'templatealias'):
525 525 del cfg[b'templatealias'][k]
526 526
527 527 if trusted:
528 528 self._tcfg.update(cfg)
529 529 self._tcfg.update(self._ocfg)
530 530 self._ucfg.update(cfg)
531 531 self._ucfg.update(self._ocfg)
532 532
533 533 if root is None:
534 534 root = os.path.expanduser(b'~')
535 535 self.fixconfig(root=root)
536 536
537 537 def fixconfig(self, root=None, section=None):
538 538 if section in (None, b'paths'):
539 539 # expand vars and ~
540 540 # translate paths relative to root (or home) into absolute paths
541 541 root = root or encoding.getcwd()
542 542 for c in self._tcfg, self._ucfg, self._ocfg:
543 543 for n, p in c.items(b'paths'):
544 544 # Ignore sub-options.
545 545 if b':' in n:
546 546 continue
547 547 if not p:
548 548 continue
549 549 if b'%%' in p:
550 550 s = self.configsource(b'paths', n) or b'none'
551 551 self.warn(
552 552 _(b"(deprecated '%%' in path %s=%s from %s)\n")
553 553 % (n, p, s)
554 554 )
555 555 p = p.replace(b'%%', b'%')
556 556 p = util.expandpath(p)
557 557 if not util.hasscheme(p) and not os.path.isabs(p):
558 558 p = os.path.normpath(os.path.join(root, p))
559 559 c.set(b"paths", n, p)
560 560
561 561 if section in (None, b'ui'):
562 562 # update ui options
563 563 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
564 564 self.debugflag = self.configbool(b'ui', b'debug')
565 565 self.verbose = self.debugflag or self.configbool(b'ui', b'verbose')
566 566 self.quiet = not self.debugflag and self.configbool(b'ui', b'quiet')
567 567 if self.verbose and self.quiet:
568 568 self.quiet = self.verbose = False
569 569 self._reportuntrusted = self.debugflag or self.configbool(
570 570 b"ui", b"report_untrusted"
571 571 )
572 572 self.showtimestamp = self.configbool(b'ui', b'timestamp-output')
573 573 self.tracebackflag = self.configbool(b'ui', b'traceback')
574 574 self.logblockedtimes = self.configbool(b'ui', b'logblockedtimes')
575 575
576 576 if section in (None, b'trusted'):
577 577 # update trust information
578 578 self._trustusers.update(self.configlist(b'trusted', b'users'))
579 579 self._trustgroups.update(self.configlist(b'trusted', b'groups'))
580 580
581 581 if section in (None, b'devel', b'ui') and self.debugflag:
582 582 tracked = set()
583 583 if self.configbool(b'devel', b'debug.extensions'):
584 584 tracked.add(b'extension')
585 585 if tracked:
586 586 logger = loggingutil.fileobjectlogger(self._ferr, tracked)
587 587 self.setlogger(b'debug', logger)
588 588
589 589 def backupconfig(self, section, item):
590 590 return (
591 591 self._ocfg.backup(section, item),
592 592 self._tcfg.backup(section, item),
593 593 self._ucfg.backup(section, item),
594 594 )
595 595
596 596 def restoreconfig(self, data):
597 597 self._ocfg.restore(data[0])
598 598 self._tcfg.restore(data[1])
599 599 self._ucfg.restore(data[2])
600 600
601 601 def setconfig(self, section, name, value, source=b''):
602 602 for cfg in (self._ocfg, self._tcfg, self._ucfg):
603 603 cfg.set(section, name, value, source)
604 604 self.fixconfig(section=section)
605 605 self._maybetweakdefaults()
606 606
607 607 def _data(self, untrusted):
608 608 return untrusted and self._ucfg or self._tcfg
609 609
610 610 def configsource(self, section, name, untrusted=False):
611 611 return self._data(untrusted).source(section, name)
612 612
613 613 def config(self, section, name, default=_unset, untrusted=False):
614 614 """return the plain string version of a config"""
615 615 value = self._config(
616 616 section, name, default=default, untrusted=untrusted
617 617 )
618 618 if value is _unset:
619 619 return None
620 620 return value
621 621
622 622 def _config(self, section, name, default=_unset, untrusted=False):
623 623 value = itemdefault = default
624 624 item = self._knownconfig.get(section, {}).get(name)
625 625 alternates = [(section, name)]
626 626
627 627 if item is not None:
628 628 alternates.extend(item.alias)
629 629 if callable(item.default):
630 630 itemdefault = item.default()
631 631 else:
632 632 itemdefault = item.default
633 633 else:
634 634 msg = b"accessing unregistered config item: '%s.%s'"
635 635 msg %= (section, name)
636 636 self.develwarn(msg, 2, b'warn-config-unknown')
637 637
638 638 if default is _unset:
639 639 if item is None:
640 640 value = default
641 641 elif item.default is configitems.dynamicdefault:
642 642 value = None
643 643 msg = b"config item requires an explicit default value: '%s.%s'"
644 644 msg %= (section, name)
645 645 self.develwarn(msg, 2, b'warn-config-default')
646 646 else:
647 647 value = itemdefault
648 648 elif (
649 649 item is not None
650 650 and item.default is not configitems.dynamicdefault
651 651 and default != itemdefault
652 652 ):
653 653 msg = (
654 654 b"specifying a mismatched default value for a registered "
655 655 b"config item: '%s.%s' '%s'"
656 656 )
657 657 msg %= (section, name, pycompat.bytestr(default))
658 658 self.develwarn(msg, 2, b'warn-config-default')
659 659
660 660 for s, n in alternates:
661 661 candidate = self._data(untrusted).get(s, n, None)
662 662 if candidate is not None:
663 663 value = candidate
664 664 break
665 665
666 666 if self.debugflag and not untrusted and self._reportuntrusted:
667 667 for s, n in alternates:
668 668 uvalue = self._ucfg.get(s, n)
669 669 if uvalue is not None and uvalue != value:
670 670 self.debug(
671 671 b"ignoring untrusted configuration option "
672 672 b"%s.%s = %s\n" % (s, n, uvalue)
673 673 )
674 674 return value
675 675
676 676 def configsuboptions(self, section, name, default=_unset, untrusted=False):
677 677 """Get a config option and all sub-options.
678 678
679 679 Some config options have sub-options that are declared with the
680 680 format "key:opt = value". This method is used to return the main
681 681 option and all its declared sub-options.
682 682
683 683 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
684 684 is a dict of defined sub-options where keys and values are strings.
685 685 """
686 686 main = self.config(section, name, default, untrusted=untrusted)
687 687 data = self._data(untrusted)
688 688 sub = {}
689 689 prefix = b'%s:' % name
690 690 for k, v in data.items(section):
691 691 if k.startswith(prefix):
692 692 sub[k[len(prefix) :]] = v
693 693
694 694 if self.debugflag and not untrusted and self._reportuntrusted:
695 695 for k, v in sub.items():
696 696 uvalue = self._ucfg.get(section, b'%s:%s' % (name, k))
697 697 if uvalue is not None and uvalue != v:
698 698 self.debug(
699 699 b'ignoring untrusted configuration option '
700 700 b'%s:%s.%s = %s\n' % (section, name, k, uvalue)
701 701 )
702 702
703 703 return main, sub
704 704
705 705 def configpath(self, section, name, default=_unset, untrusted=False):
706 706 """get a path config item, expanded relative to repo root or config
707 707 file"""
708 708 v = self.config(section, name, default, untrusted)
709 709 if v is None:
710 710 return None
711 711 if not os.path.isabs(v) or b"://" not in v:
712 712 src = self.configsource(section, name, untrusted)
713 713 if b':' in src:
714 714 base = os.path.dirname(src.rsplit(b':')[0])
715 715 v = os.path.join(base, os.path.expanduser(v))
716 716 return v
717 717
718 718 def configbool(self, section, name, default=_unset, untrusted=False):
719 719 """parse a configuration element as a boolean
720 720
721 721 >>> u = ui(); s = b'foo'
722 722 >>> u.setconfig(s, b'true', b'yes')
723 723 >>> u.configbool(s, b'true')
724 724 True
725 725 >>> u.setconfig(s, b'false', b'no')
726 726 >>> u.configbool(s, b'false')
727 727 False
728 728 >>> u.configbool(s, b'unknown')
729 729 False
730 730 >>> u.configbool(s, b'unknown', True)
731 731 True
732 732 >>> u.setconfig(s, b'invalid', b'somevalue')
733 733 >>> u.configbool(s, b'invalid')
734 734 Traceback (most recent call last):
735 735 ...
736 736 ConfigError: foo.invalid is not a boolean ('somevalue')
737 737 """
738 738
739 739 v = self._config(section, name, default, untrusted=untrusted)
740 740 if v is None:
741 741 return v
742 742 if v is _unset:
743 743 if default is _unset:
744 744 return False
745 745 return default
746 746 if isinstance(v, bool):
747 747 return v
748 748 b = stringutil.parsebool(v)
749 749 if b is None:
750 750 raise error.ConfigError(
751 751 _(b"%s.%s is not a boolean ('%s')") % (section, name, v)
752 752 )
753 753 return b
754 754
755 755 def configwith(
756 756 self, convert, section, name, default=_unset, desc=None, untrusted=False
757 757 ):
758 758 """parse a configuration element with a conversion function
759 759
760 760 >>> u = ui(); s = b'foo'
761 761 >>> u.setconfig(s, b'float1', b'42')
762 762 >>> u.configwith(float, s, b'float1')
763 763 42.0
764 764 >>> u.setconfig(s, b'float2', b'-4.25')
765 765 >>> u.configwith(float, s, b'float2')
766 766 -4.25
767 767 >>> u.configwith(float, s, b'unknown', 7)
768 768 7.0
769 769 >>> u.setconfig(s, b'invalid', b'somevalue')
770 770 >>> u.configwith(float, s, b'invalid')
771 771 Traceback (most recent call last):
772 772 ...
773 773 ConfigError: foo.invalid is not a valid float ('somevalue')
774 774 >>> u.configwith(float, s, b'invalid', desc=b'womble')
775 775 Traceback (most recent call last):
776 776 ...
777 777 ConfigError: foo.invalid is not a valid womble ('somevalue')
778 778 """
779 779
780 780 v = self.config(section, name, default, untrusted)
781 781 if v is None:
782 782 return v # do not attempt to convert None
783 783 try:
784 784 return convert(v)
785 785 except (ValueError, error.ParseError):
786 786 if desc is None:
787 787 desc = pycompat.sysbytes(convert.__name__)
788 788 raise error.ConfigError(
789 789 _(b"%s.%s is not a valid %s ('%s')") % (section, name, desc, v)
790 790 )
791 791
792 792 def configint(self, section, name, default=_unset, untrusted=False):
793 793 """parse a configuration element as an integer
794 794
795 795 >>> u = ui(); s = b'foo'
796 796 >>> u.setconfig(s, b'int1', b'42')
797 797 >>> u.configint(s, b'int1')
798 798 42
799 799 >>> u.setconfig(s, b'int2', b'-42')
800 800 >>> u.configint(s, b'int2')
801 801 -42
802 802 >>> u.configint(s, b'unknown', 7)
803 803 7
804 804 >>> u.setconfig(s, b'invalid', b'somevalue')
805 805 >>> u.configint(s, b'invalid')
806 806 Traceback (most recent call last):
807 807 ...
808 808 ConfigError: foo.invalid is not a valid integer ('somevalue')
809 809 """
810 810
811 811 return self.configwith(
812 812 int, section, name, default, b'integer', untrusted
813 813 )
814 814
815 815 def configbytes(self, section, name, default=_unset, untrusted=False):
816 816 """parse a configuration element as a quantity in bytes
817 817
818 818 Units can be specified as b (bytes), k or kb (kilobytes), m or
819 819 mb (megabytes), g or gb (gigabytes).
820 820
821 821 >>> u = ui(); s = b'foo'
822 822 >>> u.setconfig(s, b'val1', b'42')
823 823 >>> u.configbytes(s, b'val1')
824 824 42
825 825 >>> u.setconfig(s, b'val2', b'42.5 kb')
826 826 >>> u.configbytes(s, b'val2')
827 827 43520
828 828 >>> u.configbytes(s, b'unknown', b'7 MB')
829 829 7340032
830 830 >>> u.setconfig(s, b'invalid', b'somevalue')
831 831 >>> u.configbytes(s, b'invalid')
832 832 Traceback (most recent call last):
833 833 ...
834 834 ConfigError: foo.invalid is not a byte quantity ('somevalue')
835 835 """
836 836
837 837 value = self._config(section, name, default, untrusted)
838 838 if value is _unset:
839 839 if default is _unset:
840 840 default = 0
841 841 value = default
842 842 if not isinstance(value, bytes):
843 843 return value
844 844 try:
845 845 return util.sizetoint(value)
846 846 except error.ParseError:
847 847 raise error.ConfigError(
848 848 _(b"%s.%s is not a byte quantity ('%s')")
849 849 % (section, name, value)
850 850 )
851 851
852 852 def configlist(self, section, name, default=_unset, untrusted=False):
853 853 """parse a configuration element as a list of comma/space separated
854 854 strings
855 855
856 856 >>> u = ui(); s = b'foo'
857 857 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
858 858 >>> u.configlist(s, b'list1')
859 859 ['this', 'is', 'a small', 'test']
860 860 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
861 861 >>> u.configlist(s, b'list2')
862 862 ['this', 'is', 'a small', 'test']
863 863 """
864 864 # default is not always a list
865 865 v = self.configwith(
866 866 config.parselist, section, name, default, b'list', untrusted
867 867 )
868 868 if isinstance(v, bytes):
869 869 return config.parselist(v)
870 870 elif v is None:
871 871 return []
872 872 return v
873 873
874 874 def configdate(self, section, name, default=_unset, untrusted=False):
875 875 """parse a configuration element as a tuple of ints
876 876
877 877 >>> u = ui(); s = b'foo'
878 878 >>> u.setconfig(s, b'date', b'0 0')
879 879 >>> u.configdate(s, b'date')
880 880 (0, 0)
881 881 """
882 882 if self.config(section, name, default, untrusted):
883 883 return self.configwith(
884 884 dateutil.parsedate, section, name, default, b'date', untrusted
885 885 )
886 886 if default is _unset:
887 887 return None
888 888 return default
889 889
890 890 def configdefault(self, section, name):
891 891 """returns the default value of the config item"""
892 892 item = self._knownconfig.get(section, {}).get(name)
893 893 itemdefault = None
894 894 if item is not None:
895 895 if callable(item.default):
896 896 itemdefault = item.default()
897 897 else:
898 898 itemdefault = item.default
899 899 return itemdefault
900 900
901 901 def hasconfig(self, section, name, untrusted=False):
902 902 return self._data(untrusted).hasitem(section, name)
903 903
904 904 def has_section(self, section, untrusted=False):
905 905 '''tell whether section exists in config.'''
906 906 return section in self._data(untrusted)
907 907
908 908 def configitems(self, section, untrusted=False, ignoresub=False):
909 909 items = self._data(untrusted).items(section)
910 910 if ignoresub:
911 911 items = [i for i in items if b':' not in i[0]]
912 912 if self.debugflag and not untrusted and self._reportuntrusted:
913 913 for k, v in self._ucfg.items(section):
914 914 if self._tcfg.get(section, k) != v:
915 915 self.debug(
916 916 b"ignoring untrusted configuration option "
917 917 b"%s.%s = %s\n" % (section, k, v)
918 918 )
919 919 return items
920 920
921 921 def walkconfig(self, untrusted=False):
922 922 cfg = self._data(untrusted)
923 923 for section in cfg.sections():
924 924 for name, value in self.configitems(section, untrusted):
925 925 yield section, name, value
926 926
927 927 def plain(self, feature=None):
928 928 '''is plain mode active?
929 929
930 930 Plain mode means that all configuration variables which affect
931 931 the behavior and output of Mercurial should be
932 932 ignored. Additionally, the output should be stable,
933 933 reproducible and suitable for use in scripts or applications.
934 934
935 935 The only way to trigger plain mode is by setting either the
936 936 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
937 937
938 938 The return value can either be
939 939 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
940 940 - False if feature is disabled by default and not included in HGPLAIN
941 941 - True otherwise
942 942 '''
943 943 if (
944 944 b'HGPLAIN' not in encoding.environ
945 945 and b'HGPLAINEXCEPT' not in encoding.environ
946 946 ):
947 947 return False
948 948 exceptions = (
949 949 encoding.environ.get(b'HGPLAINEXCEPT', b'').strip().split(b',')
950 950 )
951 951 # TODO: add support for HGPLAIN=+feature,-feature syntax
952 952 if b'+strictflags' not in encoding.environ.get(b'HGPLAIN', b'').split(
953 953 b','
954 954 ):
955 955 exceptions.append(b'strictflags')
956 956 if feature and exceptions:
957 957 return feature not in exceptions
958 958 return True
959 959
960 960 def username(self, acceptempty=False):
961 961 """Return default username to be used in commits.
962 962
963 963 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
964 964 and stop searching if one of these is set.
965 965 If not found and acceptempty is True, returns None.
966 966 If not found and ui.askusername is True, ask the user, else use
967 967 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
968 968 If no username could be found, raise an Abort error.
969 969 """
970 970 user = encoding.environ.get(b"HGUSER")
971 971 if user is None:
972 972 user = self.config(b"ui", b"username")
973 973 if user is not None:
974 974 user = os.path.expandvars(user)
975 975 if user is None:
976 976 user = encoding.environ.get(b"EMAIL")
977 977 if user is None and acceptempty:
978 978 return user
979 979 if user is None and self.configbool(b"ui", b"askusername"):
980 980 user = self.prompt(_(b"enter a commit username:"), default=None)
981 981 if user is None and not self.interactive():
982 982 try:
983 983 user = b'%s@%s' % (
984 984 procutil.getuser(),
985 985 encoding.strtolocal(socket.getfqdn()),
986 986 )
987 987 self.warn(_(b"no username found, using '%s' instead\n") % user)
988 988 except KeyError:
989 989 pass
990 990 if not user:
991 991 raise error.Abort(
992 992 _(b'no username supplied'),
993 993 hint=_(b"use 'hg config --edit' " b'to set your username'),
994 994 )
995 995 if b"\n" in user:
996 996 raise error.Abort(
997 997 _(b"username %r contains a newline\n") % pycompat.bytestr(user)
998 998 )
999 999 return user
1000 1000
1001 1001 def shortuser(self, user):
1002 1002 """Return a short representation of a user name or email address."""
1003 1003 if not self.verbose:
1004 1004 user = stringutil.shortuser(user)
1005 1005 return user
1006 1006
1007 1007 def expandpath(self, loc, default=None):
1008 1008 """Return repository location relative to cwd or from [paths]"""
1009 1009 try:
1010 1010 p = self.paths.getpath(loc)
1011 1011 if p:
1012 1012 return p.rawloc
1013 1013 except error.RepoError:
1014 1014 pass
1015 1015
1016 1016 if default:
1017 1017 try:
1018 1018 p = self.paths.getpath(default)
1019 1019 if p:
1020 1020 return p.rawloc
1021 1021 except error.RepoError:
1022 1022 pass
1023 1023
1024 1024 return loc
1025 1025
1026 1026 @util.propertycache
1027 1027 def paths(self):
1028 1028 return paths(self)
1029 1029
1030 1030 @property
1031 1031 def fout(self):
1032 1032 return self._fout
1033 1033
1034 1034 @fout.setter
1035 1035 def fout(self, f):
1036 1036 self._fout = f
1037 1037 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1038 1038
1039 1039 @property
1040 1040 def ferr(self):
1041 1041 return self._ferr
1042 1042
1043 1043 @ferr.setter
1044 1044 def ferr(self, f):
1045 1045 self._ferr = f
1046 1046 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1047 1047
1048 1048 @property
1049 1049 def fin(self):
1050 1050 return self._fin
1051 1051
1052 1052 @fin.setter
1053 1053 def fin(self, f):
1054 1054 self._fin = f
1055 1055
1056 1056 @property
1057 1057 def fmsg(self):
1058 1058 """Stream dedicated for status/error messages; may be None if
1059 1059 fout/ferr are used"""
1060 1060 return self._fmsg
1061 1061
1062 1062 @fmsg.setter
1063 1063 def fmsg(self, f):
1064 1064 self._fmsg = f
1065 1065 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1066 1066
1067 1067 def pushbuffer(self, error=False, subproc=False, labeled=False):
1068 1068 """install a buffer to capture standard output of the ui object
1069 1069
1070 1070 If error is True, the error output will be captured too.
1071 1071
1072 1072 If subproc is True, output from subprocesses (typically hooks) will be
1073 1073 captured too.
1074 1074
1075 1075 If labeled is True, any labels associated with buffered
1076 1076 output will be handled. By default, this has no effect
1077 1077 on the output returned, but extensions and GUI tools may
1078 1078 handle this argument and returned styled output. If output
1079 1079 is being buffered so it can be captured and parsed or
1080 1080 processed, labeled should not be set to True.
1081 1081 """
1082 1082 self._buffers.append([])
1083 1083 self._bufferstates.append((error, subproc, labeled))
1084 1084 self._bufferapplylabels = labeled
1085 1085
1086 1086 def popbuffer(self):
1087 1087 '''pop the last buffer and return the buffered output'''
1088 1088 self._bufferstates.pop()
1089 1089 if self._bufferstates:
1090 1090 self._bufferapplylabels = self._bufferstates[-1][2]
1091 1091 else:
1092 1092 self._bufferapplylabels = None
1093 1093
1094 1094 return b"".join(self._buffers.pop())
1095 1095
1096 1096 def _isbuffered(self, dest):
1097 1097 if dest is self._fout:
1098 1098 return bool(self._buffers)
1099 1099 if dest is self._ferr:
1100 1100 return bool(self._bufferstates and self._bufferstates[-1][0])
1101 1101 return False
1102 1102
1103 1103 def canwritewithoutlabels(self):
1104 1104 '''check if write skips the label'''
1105 1105 if self._buffers and not self._bufferapplylabels:
1106 1106 return True
1107 1107 return self._colormode is None
1108 1108
1109 1109 def canbatchlabeledwrites(self):
1110 1110 '''check if write calls with labels are batchable'''
1111 1111 # Windows color printing is special, see ``write``.
1112 1112 return self._colormode != b'win32'
1113 1113
1114 1114 def write(self, *args, **opts):
1115 1115 '''write args to output
1116 1116
1117 1117 By default, this method simply writes to the buffer or stdout.
1118 1118 Color mode can be set on the UI class to have the output decorated
1119 1119 with color modifier before being written to stdout.
1120 1120
1121 1121 The color used is controlled by an optional keyword argument, "label".
1122 1122 This should be a string containing label names separated by space.
1123 1123 Label names take the form of "topic.type". For example, ui.debug()
1124 1124 issues a label of "ui.debug".
1125 1125
1126 1126 Progress reports via stderr are normally cleared before writing as
1127 1127 stdout and stderr go to the same terminal. This can be skipped with
1128 1128 the optional keyword argument "keepprogressbar". The progress bar
1129 1129 will continue to occupy a partial line on stderr in that case.
1130 1130 This functionality is intended when Mercurial acts as data source
1131 1131 in a pipe.
1132 1132
1133 1133 When labeling output for a specific command, a label of
1134 1134 "cmdname.type" is recommended. For example, status issues
1135 1135 a label of "status.modified" for modified files.
1136 1136 '''
1137 1137 dest = self._fout
1138 1138
1139 1139 # inlined _write() for speed
1140 1140 if self._buffers:
1141 1141 label = opts.get('label', b'')
1142 1142 if label and self._bufferapplylabels:
1143 1143 self._buffers[-1].extend(self.label(a, label) for a in args)
1144 1144 else:
1145 1145 self._buffers[-1].extend(args)
1146 1146 return
1147 1147
1148 1148 # inlined _writenobuf() for speed
1149 1149 if not opts.get('keepprogressbar', False):
1150 1150 self._progclear()
1151 1151 msg = b''.join(args)
1152 1152
1153 1153 # opencode timeblockedsection because this is a critical path
1154 1154 starttime = util.timer()
1155 1155 try:
1156 1156 if self._colormode == b'win32':
1157 1157 # windows color printing is its own can of crab, defer to
1158 1158 # the color module and that is it.
1159 1159 color.win32print(self, dest.write, msg, **opts)
1160 1160 else:
1161 1161 if self._colormode is not None:
1162 1162 label = opts.get('label', b'')
1163 1163 msg = self.label(msg, label)
1164 1164 dest.write(msg)
1165 1165 except IOError as err:
1166 1166 raise error.StdioError(err)
1167 1167 finally:
1168 1168 self._blockedtimes[b'stdio_blocked'] += (
1169 1169 util.timer() - starttime
1170 1170 ) * 1000
1171 1171
1172 1172 def write_err(self, *args, **opts):
1173 1173 self._write(self._ferr, *args, **opts)
1174 1174
1175 1175 def _write(self, dest, *args, **opts):
1176 1176 # update write() as well if you touch this code
1177 1177 if self._isbuffered(dest):
1178 1178 label = opts.get('label', b'')
1179 1179 if label and self._bufferapplylabels:
1180 1180 self._buffers[-1].extend(self.label(a, label) for a in args)
1181 1181 else:
1182 1182 self._buffers[-1].extend(args)
1183 1183 else:
1184 1184 self._writenobuf(dest, *args, **opts)
1185 1185
1186 1186 def _writenobuf(self, dest, *args, **opts):
1187 1187 # update write() as well if you touch this code
1188 1188 if not opts.get('keepprogressbar', False):
1189 1189 self._progclear()
1190 1190 msg = b''.join(args)
1191 1191
1192 1192 # opencode timeblockedsection because this is a critical path
1193 1193 starttime = util.timer()
1194 1194 try:
1195 1195 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1196 1196 self._fout.flush()
1197 1197 if getattr(dest, 'structured', False):
1198 1198 # channel for machine-readable output with metadata, where
1199 1199 # no extra colorization is necessary.
1200 1200 dest.write(msg, **opts)
1201 1201 elif self._colormode == b'win32':
1202 1202 # windows color printing is its own can of crab, defer to
1203 1203 # the color module and that is it.
1204 1204 color.win32print(self, dest.write, msg, **opts)
1205 1205 else:
1206 1206 if self._colormode is not None:
1207 1207 label = opts.get('label', b'')
1208 1208 msg = self.label(msg, label)
1209 1209 dest.write(msg)
1210 1210 # stderr may be buffered under win32 when redirected to files,
1211 1211 # including stdout.
1212 1212 if dest is self._ferr and not getattr(dest, 'closed', False):
1213 1213 dest.flush()
1214 1214 except IOError as err:
1215 1215 if dest is self._ferr and err.errno in (
1216 1216 errno.EPIPE,
1217 1217 errno.EIO,
1218 1218 errno.EBADF,
1219 1219 ):
1220 1220 # no way to report the error, so ignore it
1221 1221 return
1222 1222 raise error.StdioError(err)
1223 1223 finally:
1224 1224 self._blockedtimes[b'stdio_blocked'] += (
1225 1225 util.timer() - starttime
1226 1226 ) * 1000
1227 1227
1228 1228 def _writemsg(self, dest, *args, **opts):
1229 1229 timestamp = self.showtimestamp and opts.get('type') in {
1230 1230 b'debug',
1231 1231 b'error',
1232 1232 b'note',
1233 1233 b'status',
1234 1234 b'warning',
1235 1235 }
1236 1236 if timestamp:
1237 1237 args = (
1238 1238 b'[%s] '
1239 1239 % pycompat.bytestr(datetime.datetime.now().isoformat()),
1240 1240 ) + args
1241 1241 _writemsgwith(self._write, dest, *args, **opts)
1242 1242 if timestamp:
1243 1243 dest.flush()
1244 1244
1245 1245 def _writemsgnobuf(self, dest, *args, **opts):
1246 1246 _writemsgwith(self._writenobuf, dest, *args, **opts)
1247 1247
1248 1248 def flush(self):
1249 1249 # opencode timeblockedsection because this is a critical path
1250 1250 starttime = util.timer()
1251 1251 try:
1252 1252 try:
1253 1253 self._fout.flush()
1254 1254 except IOError as err:
1255 1255 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1256 1256 raise error.StdioError(err)
1257 1257 finally:
1258 1258 try:
1259 1259 self._ferr.flush()
1260 1260 except IOError as err:
1261 1261 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1262 1262 raise error.StdioError(err)
1263 1263 finally:
1264 1264 self._blockedtimes[b'stdio_blocked'] += (
1265 1265 util.timer() - starttime
1266 1266 ) * 1000
1267 1267
1268 1268 def _isatty(self, fh):
1269 1269 if self.configbool(b'ui', b'nontty'):
1270 1270 return False
1271 1271 return procutil.isatty(fh)
1272 1272
1273 1273 def protectfinout(self):
1274 1274 """Duplicate ui streams and redirect original if they are stdio
1275 1275
1276 1276 Returns (fin, fout) which point to the original ui fds, but may be
1277 1277 copy of them. The returned streams can be considered "owned" in that
1278 1278 print(), exec(), etc. never reach to them.
1279 1279 """
1280 1280 if self._finoutredirected:
1281 1281 # if already redirected, protectstdio() would just create another
1282 1282 # nullfd pair, which is equivalent to returning self._fin/_fout.
1283 1283 return self._fin, self._fout
1284 1284 fin, fout = procutil.protectstdio(self._fin, self._fout)
1285 1285 self._finoutredirected = (fin, fout) != (self._fin, self._fout)
1286 1286 return fin, fout
1287 1287
1288 1288 def restorefinout(self, fin, fout):
1289 1289 """Restore ui streams from possibly duplicated (fin, fout)"""
1290 1290 if (fin, fout) == (self._fin, self._fout):
1291 1291 return
1292 1292 procutil.restorestdio(self._fin, self._fout, fin, fout)
1293 1293 # protectfinout() won't create more than one duplicated streams,
1294 1294 # so we can just turn the redirection flag off.
1295 1295 self._finoutredirected = False
1296 1296
1297 1297 @contextlib.contextmanager
1298 1298 def protectedfinout(self):
1299 1299 """Run code block with protected standard streams"""
1300 1300 fin, fout = self.protectfinout()
1301 1301 try:
1302 1302 yield fin, fout
1303 1303 finally:
1304 1304 self.restorefinout(fin, fout)
1305 1305
1306 1306 def disablepager(self):
1307 1307 self._disablepager = True
1308 1308
1309 1309 def pager(self, command):
1310 1310 """Start a pager for subsequent command output.
1311 1311
1312 1312 Commands which produce a long stream of output should call
1313 1313 this function to activate the user's preferred pagination
1314 1314 mechanism (which may be no pager). Calling this function
1315 1315 precludes any future use of interactive functionality, such as
1316 1316 prompting the user or activating curses.
1317 1317
1318 1318 Args:
1319 1319 command: The full, non-aliased name of the command. That is, "log"
1320 1320 not "history, "summary" not "summ", etc.
1321 1321 """
1322 1322 if self._disablepager or self.pageractive:
1323 1323 # how pager should do is already determined
1324 1324 return
1325 1325
1326 1326 if not command.startswith(b'internal-always-') and (
1327 1327 # explicit --pager=on (= 'internal-always-' prefix) should
1328 1328 # take precedence over disabling factors below
1329 1329 command in self.configlist(b'pager', b'ignore')
1330 1330 or not self.configbool(b'ui', b'paginate')
1331 1331 or not self.configbool(b'pager', b'attend-' + command, True)
1332 1332 or encoding.environ.get(b'TERM') == b'dumb'
1333 1333 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1334 1334 # formatted() will need some adjustment.
1335 1335 or not self.formatted()
1336 1336 or self.plain()
1337 1337 or self._buffers
1338 1338 # TODO: expose debugger-enabled on the UI object
1339 1339 or b'--debugger' in pycompat.sysargv
1340 1340 ):
1341 1341 # We only want to paginate if the ui appears to be
1342 1342 # interactive, the user didn't say HGPLAIN or
1343 1343 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1344 1344 return
1345 1345
1346 1346 pagercmd = self.config(b'pager', b'pager', rcutil.fallbackpager)
1347 1347 if not pagercmd:
1348 1348 return
1349 1349
1350 1350 pagerenv = {}
1351 1351 for name, value in rcutil.defaultpagerenv().items():
1352 1352 if name not in encoding.environ:
1353 1353 pagerenv[name] = value
1354 1354
1355 1355 self.debug(
1356 1356 b'starting pager for command %s\n' % stringutil.pprint(command)
1357 1357 )
1358 1358 self.flush()
1359 1359
1360 1360 wasformatted = self.formatted()
1361 1361 if util.safehasattr(signal, b"SIGPIPE"):
1362 1362 signal.signal(signal.SIGPIPE, _catchterm)
1363 1363 if self._runpager(pagercmd, pagerenv):
1364 1364 self.pageractive = True
1365 1365 # Preserve the formatted-ness of the UI. This is important
1366 1366 # because we mess with stdout, which might confuse
1367 1367 # auto-detection of things being formatted.
1368 1368 self.setconfig(b'ui', b'formatted', wasformatted, b'pager')
1369 1369 self.setconfig(b'ui', b'interactive', False, b'pager')
1370 1370
1371 1371 # If pagermode differs from color.mode, reconfigure color now that
1372 1372 # pageractive is set.
1373 1373 cm = self._colormode
1374 1374 if cm != self.config(b'color', b'pagermode', cm):
1375 1375 color.setup(self)
1376 1376 else:
1377 1377 # If the pager can't be spawned in dispatch when --pager=on is
1378 1378 # given, don't try again when the command runs, to avoid a duplicate
1379 1379 # warning about a missing pager command.
1380 1380 self.disablepager()
1381 1381
1382 1382 def _runpager(self, command, env=None):
1383 1383 """Actually start the pager and set up file descriptors.
1384 1384
1385 1385 This is separate in part so that extensions (like chg) can
1386 1386 override how a pager is invoked.
1387 1387 """
1388 1388 if command == b'cat':
1389 1389 # Save ourselves some work.
1390 1390 return False
1391 1391 # If the command doesn't contain any of these characters, we
1392 1392 # assume it's a binary and exec it directly. This means for
1393 1393 # simple pager command configurations, we can degrade
1394 1394 # gracefully and tell the user about their broken pager.
1395 1395 shell = any(c in command for c in b"|&;<>()$`\\\"' \t\n*?[#~=%")
1396 1396
1397 1397 if pycompat.iswindows and not shell:
1398 1398 # Window's built-in `more` cannot be invoked with shell=False, but
1399 1399 # its `more.com` can. Hide this implementation detail from the
1400 1400 # user so we can also get sane bad PAGER behavior. MSYS has
1401 1401 # `more.exe`, so do a cmd.exe style resolution of the executable to
1402 1402 # determine which one to use.
1403 1403 fullcmd = procutil.findexe(command)
1404 1404 if not fullcmd:
1405 1405 self.warn(
1406 1406 _(b"missing pager command '%s', skipping pager\n") % command
1407 1407 )
1408 1408 return False
1409 1409
1410 1410 command = fullcmd
1411 1411
1412 1412 try:
1413 1413 pager = subprocess.Popen(
1414 1414 procutil.tonativestr(command),
1415 1415 shell=shell,
1416 1416 bufsize=-1,
1417 1417 close_fds=procutil.closefds,
1418 1418 stdin=subprocess.PIPE,
1419 1419 stdout=procutil.stdout,
1420 1420 stderr=procutil.stderr,
1421 1421 env=procutil.tonativeenv(procutil.shellenviron(env)),
1422 1422 )
1423 1423 except OSError as e:
1424 1424 if e.errno == errno.ENOENT and not shell:
1425 1425 self.warn(
1426 1426 _(b"missing pager command '%s', skipping pager\n") % command
1427 1427 )
1428 1428 return False
1429 1429 raise
1430 1430
1431 1431 # back up original file descriptors
1432 1432 stdoutfd = os.dup(procutil.stdout.fileno())
1433 1433 stderrfd = os.dup(procutil.stderr.fileno())
1434 1434
1435 1435 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1436 1436 if self._isatty(procutil.stderr):
1437 1437 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1438 1438
1439 1439 @self.atexit
1440 1440 def killpager():
1441 1441 if util.safehasattr(signal, b"SIGINT"):
1442 1442 signal.signal(signal.SIGINT, signal.SIG_IGN)
1443 1443 # restore original fds, closing pager.stdin copies in the process
1444 1444 os.dup2(stdoutfd, procutil.stdout.fileno())
1445 1445 os.dup2(stderrfd, procutil.stderr.fileno())
1446 1446 pager.stdin.close()
1447 1447 pager.wait()
1448 1448
1449 1449 return True
1450 1450
1451 1451 @property
1452 1452 def _exithandlers(self):
1453 1453 return _reqexithandlers
1454 1454
1455 1455 def atexit(self, func, *args, **kwargs):
1456 1456 '''register a function to run after dispatching a request
1457 1457
1458 1458 Handlers do not stay registered across request boundaries.'''
1459 1459 self._exithandlers.append((func, args, kwargs))
1460 1460 return func
1461 1461
1462 1462 def interface(self, feature):
1463 1463 """what interface to use for interactive console features?
1464 1464
1465 1465 The interface is controlled by the value of `ui.interface` but also by
1466 1466 the value of feature-specific configuration. For example:
1467 1467
1468 1468 ui.interface.histedit = text
1469 1469 ui.interface.chunkselector = curses
1470 1470
1471 1471 Here the features are "histedit" and "chunkselector".
1472 1472
1473 1473 The configuration above means that the default interfaces for commands
1474 1474 is curses, the interface for histedit is text and the interface for
1475 1475 selecting chunk is crecord (the best curses interface available).
1476 1476
1477 1477 Consider the following example:
1478 1478 ui.interface = curses
1479 1479 ui.interface.histedit = text
1480 1480
1481 1481 Then histedit will use the text interface and chunkselector will use
1482 1482 the default curses interface (crecord at the moment).
1483 1483 """
1484 1484 alldefaults = frozenset([b"text", b"curses"])
1485 1485
1486 1486 featureinterfaces = {
1487 1487 b"chunkselector": [b"text", b"curses",],
1488 1488 b"histedit": [b"text", b"curses",],
1489 1489 }
1490 1490
1491 1491 # Feature-specific interface
1492 1492 if feature not in featureinterfaces.keys():
1493 1493 # Programming error, not user error
1494 1494 raise ValueError(b"Unknown feature requested %s" % feature)
1495 1495
1496 1496 availableinterfaces = frozenset(featureinterfaces[feature])
1497 1497 if alldefaults > availableinterfaces:
1498 1498 # Programming error, not user error. We need a use case to
1499 1499 # define the right thing to do here.
1500 1500 raise ValueError(
1501 1501 b"Feature %s does not handle all default interfaces" % feature
1502 1502 )
1503 1503
1504 1504 if self.plain() or encoding.environ.get(b'TERM') == b'dumb':
1505 1505 return b"text"
1506 1506
1507 1507 # Default interface for all the features
1508 1508 defaultinterface = b"text"
1509 1509 i = self.config(b"ui", b"interface")
1510 1510 if i in alldefaults:
1511 1511 defaultinterface = i
1512 1512
1513 1513 choseninterface = defaultinterface
1514 1514 f = self.config(b"ui", b"interface.%s" % feature)
1515 1515 if f in availableinterfaces:
1516 1516 choseninterface = f
1517 1517
1518 1518 if i is not None and defaultinterface != i:
1519 1519 if f is not None:
1520 1520 self.warn(_(b"invalid value for ui.interface: %s\n") % (i,))
1521 1521 else:
1522 1522 self.warn(
1523 1523 _(b"invalid value for ui.interface: %s (using %s)\n")
1524 1524 % (i, choseninterface)
1525 1525 )
1526 1526 if f is not None and choseninterface != f:
1527 1527 self.warn(
1528 1528 _(b"invalid value for ui.interface.%s: %s (using %s)\n")
1529 1529 % (feature, f, choseninterface)
1530 1530 )
1531 1531
1532 1532 return choseninterface
1533 1533
1534 1534 def interactive(self):
1535 1535 '''is interactive input allowed?
1536 1536
1537 1537 An interactive session is a session where input can be reasonably read
1538 1538 from `sys.stdin'. If this function returns false, any attempt to read
1539 1539 from stdin should fail with an error, unless a sensible default has been
1540 1540 specified.
1541 1541
1542 1542 Interactiveness is triggered by the value of the `ui.interactive'
1543 1543 configuration variable or - if it is unset - when `sys.stdin' points
1544 1544 to a terminal device.
1545 1545
1546 1546 This function refers to input only; for output, see `ui.formatted()'.
1547 1547 '''
1548 1548 i = self.configbool(b"ui", b"interactive")
1549 1549 if i is None:
1550 1550 # some environments replace stdin without implementing isatty
1551 1551 # usually those are non-interactive
1552 1552 return self._isatty(self._fin)
1553 1553
1554 1554 return i
1555 1555
1556 1556 def termwidth(self):
1557 1557 '''how wide is the terminal in columns?
1558 1558 '''
1559 1559 if b'COLUMNS' in encoding.environ:
1560 1560 try:
1561 1561 return int(encoding.environ[b'COLUMNS'])
1562 1562 except ValueError:
1563 1563 pass
1564 1564 return scmutil.termsize(self)[0]
1565 1565
1566 1566 def formatted(self):
1567 1567 '''should formatted output be used?
1568 1568
1569 1569 It is often desirable to format the output to suite the output medium.
1570 1570 Examples of this are truncating long lines or colorizing messages.
1571 1571 However, this is not often not desirable when piping output into other
1572 1572 utilities, e.g. `grep'.
1573 1573
1574 1574 Formatted output is triggered by the value of the `ui.formatted'
1575 1575 configuration variable or - if it is unset - when `sys.stdout' points
1576 1576 to a terminal device. Please note that `ui.formatted' should be
1577 1577 considered an implementation detail; it is not intended for use outside
1578 1578 Mercurial or its extensions.
1579 1579
1580 1580 This function refers to output only; for input, see `ui.interactive()'.
1581 1581 This function always returns false when in plain mode, see `ui.plain()'.
1582 1582 '''
1583 1583 if self.plain():
1584 1584 return False
1585 1585
1586 1586 i = self.configbool(b"ui", b"formatted")
1587 1587 if i is None:
1588 1588 # some environments replace stdout without implementing isatty
1589 1589 # usually those are non-interactive
1590 1590 return self._isatty(self._fout)
1591 1591
1592 1592 return i
1593 1593
1594 1594 def _readline(self, prompt=b' ', promptopts=None):
1595 1595 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1596 1596 # because they have to be text streams with *no buffering*. Instead,
1597 1597 # we use rawinput() only if call_readline() will be invoked by
1598 1598 # PyOS_Readline(), so no I/O will be made at Python layer.
1599 1599 usereadline = (
1600 1600 self._isatty(self._fin)
1601 1601 and self._isatty(self._fout)
1602 1602 and procutil.isstdin(self._fin)
1603 1603 and procutil.isstdout(self._fout)
1604 1604 )
1605 1605 if usereadline:
1606 1606 try:
1607 1607 # magically add command line editing support, where
1608 1608 # available
1609 1609 import readline
1610 1610
1611 1611 # force demandimport to really load the module
1612 1612 readline.read_history_file
1613 1613 # windows sometimes raises something other than ImportError
1614 1614 except Exception:
1615 1615 usereadline = False
1616 1616
1617 1617 if self._colormode == b'win32' or not usereadline:
1618 1618 if not promptopts:
1619 1619 promptopts = {}
1620 1620 self._writemsgnobuf(
1621 1621 self._fmsgout, prompt, type=b'prompt', **promptopts
1622 1622 )
1623 1623 self.flush()
1624 1624 prompt = b' '
1625 1625 else:
1626 1626 prompt = self.label(prompt, b'ui.prompt') + b' '
1627 1627
1628 1628 # prompt ' ' must exist; otherwise readline may delete entire line
1629 1629 # - http://bugs.python.org/issue12833
1630 1630 with self.timeblockedsection(b'stdio'):
1631 1631 if usereadline:
1632 1632 self.flush()
1633 1633 prompt = encoding.strfromlocal(prompt)
1634 1634 line = encoding.strtolocal(pycompat.rawinput(prompt))
1635 1635 # When stdin is in binary mode on Windows, it can cause
1636 1636 # raw_input() to emit an extra trailing carriage return
1637 1637 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1638 1638 line = line[:-1]
1639 1639 else:
1640 1640 self._fout.write(pycompat.bytestr(prompt))
1641 1641 self._fout.flush()
1642 1642 line = self._fin.readline()
1643 1643 if not line:
1644 1644 raise EOFError
1645 1645 line = line.rstrip(pycompat.oslinesep)
1646 1646
1647 1647 return line
1648 1648
1649 1649 def prompt(self, msg, default=b"y"):
1650 1650 """Prompt user with msg, read response.
1651 1651 If ui is not interactive, the default is returned.
1652 1652 """
1653 1653 return self._prompt(msg, default=default)
1654 1654
1655 1655 def _prompt(self, msg, **opts):
1656 1656 default = opts['default']
1657 1657 if not self.interactive():
1658 1658 self._writemsg(self._fmsgout, msg, b' ', type=b'prompt', **opts)
1659 1659 self._writemsg(
1660 1660 self._fmsgout, default or b'', b"\n", type=b'promptecho'
1661 1661 )
1662 1662 return default
1663 1663 try:
1664 1664 r = self._readline(prompt=msg, promptopts=opts)
1665 1665 if not r:
1666 1666 r = default
1667 1667 if self.configbool(b'ui', b'promptecho'):
1668 1668 self._writemsg(
1669 1669 self._fmsgout, r or b'', b"\n", type=b'promptecho'
1670 1670 )
1671 1671 return r
1672 1672 except EOFError:
1673 1673 raise error.ResponseExpected()
1674 1674
1675 1675 @staticmethod
1676 1676 def extractchoices(prompt):
1677 1677 """Extract prompt message and list of choices from specified prompt.
1678 1678
1679 1679 This returns tuple "(message, choices)", and "choices" is the
1680 1680 list of tuple "(response character, text without &)".
1681 1681
1682 1682 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1683 1683 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1684 1684 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1685 1685 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1686 1686 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1687 1687 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1688 1688 """
1689 1689
1690 1690 # Sadly, the prompt string may have been built with a filename
1691 1691 # containing "$$" so let's try to find the first valid-looking
1692 1692 # prompt to start parsing. Sadly, we also can't rely on
1693 1693 # choices containing spaces, ASCII, or basically anything
1694 1694 # except an ampersand followed by a character.
1695 1695 m = re.match(br'(?s)(.+?)\$\$([^$]*&[^ $].*)', prompt)
1696 1696 msg = m.group(1)
1697 1697 choices = [p.strip(b' ') for p in m.group(2).split(b'$$')]
1698 1698
1699 1699 def choicetuple(s):
1700 1700 ampidx = s.index(b'&')
1701 1701 return s[ampidx + 1 : ampidx + 2].lower(), s.replace(b'&', b'', 1)
1702 1702
1703 1703 return (msg, [choicetuple(s) for s in choices])
1704 1704
1705 1705 def promptchoice(self, prompt, default=0):
1706 1706 """Prompt user with a message, read response, and ensure it matches
1707 1707 one of the provided choices. The prompt is formatted as follows:
1708 1708
1709 1709 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1710 1710
1711 1711 The index of the choice is returned. Responses are case
1712 1712 insensitive. If ui is not interactive, the default is
1713 1713 returned.
1714 1714 """
1715 1715
1716 1716 msg, choices = self.extractchoices(prompt)
1717 1717 resps = [r for r, t in choices]
1718 1718 while True:
1719 1719 r = self._prompt(msg, default=resps[default], choices=choices)
1720 1720 if r.lower() in resps:
1721 1721 return resps.index(r.lower())
1722 1722 # TODO: shouldn't it be a warning?
1723 1723 self._writemsg(self._fmsgout, _(b"unrecognized response\n"))
1724 1724
1725 1725 def getpass(self, prompt=None, default=None):
1726 1726 if not self.interactive():
1727 1727 return default
1728 1728 try:
1729 1729 self._writemsg(
1730 1730 self._fmsgerr,
1731 1731 prompt or _(b'password: '),
1732 1732 type=b'prompt',
1733 1733 password=True,
1734 1734 )
1735 1735 # disable getpass() only if explicitly specified. it's still valid
1736 1736 # to interact with tty even if fin is not a tty.
1737 1737 with self.timeblockedsection(b'stdio'):
1738 1738 if self.configbool(b'ui', b'nontty'):
1739 1739 l = self._fin.readline()
1740 1740 if not l:
1741 1741 raise EOFError
1742 1742 return l.rstrip(b'\n')
1743 1743 else:
1744 1744 return getpass.getpass('')
1745 1745 except EOFError:
1746 1746 raise error.ResponseExpected()
1747 1747
1748 1748 def status(self, *msg, **opts):
1749 1749 '''write status message to output (if ui.quiet is False)
1750 1750
1751 1751 This adds an output label of "ui.status".
1752 1752 '''
1753 1753 if not self.quiet:
1754 1754 self._writemsg(self._fmsgout, type=b'status', *msg, **opts)
1755 1755
1756 1756 def warn(self, *msg, **opts):
1757 1757 '''write warning message to output (stderr)
1758 1758
1759 1759 This adds an output label of "ui.warning".
1760 1760 '''
1761 1761 self._writemsg(self._fmsgerr, type=b'warning', *msg, **opts)
1762 1762
1763 1763 def error(self, *msg, **opts):
1764 1764 '''write error message to output (stderr)
1765 1765
1766 1766 This adds an output label of "ui.error".
1767 1767 '''
1768 1768 self._writemsg(self._fmsgerr, type=b'error', *msg, **opts)
1769 1769
1770 1770 def note(self, *msg, **opts):
1771 1771 '''write note to output (if ui.verbose is True)
1772 1772
1773 1773 This adds an output label of "ui.note".
1774 1774 '''
1775 1775 if self.verbose:
1776 1776 self._writemsg(self._fmsgout, type=b'note', *msg, **opts)
1777 1777
1778 1778 def debug(self, *msg, **opts):
1779 1779 '''write debug message to output (if ui.debugflag is True)
1780 1780
1781 1781 This adds an output label of "ui.debug".
1782 1782 '''
1783 1783 if self.debugflag:
1784 1784 self._writemsg(self._fmsgout, type=b'debug', *msg, **opts)
1785 1785 self.log(b'debug', b'%s', b''.join(msg))
1786 1786
1787 1787 # Aliases to defeat check-code.
1788 1788 statusnoi18n = status
1789 1789 notenoi18n = note
1790 1790 warnnoi18n = warn
1791 1791 writenoi18n = write
1792 1792
1793 1793 def edit(
1794 1794 self,
1795 1795 text,
1796 1796 user,
1797 1797 extra=None,
1798 1798 editform=None,
1799 1799 pending=None,
1800 1800 repopath=None,
1801 1801 action=None,
1802 1802 ):
1803 1803 if action is None:
1804 1804 self.develwarn(
1805 1805 b'action is None but will soon be a required '
1806 1806 b'parameter to ui.edit()'
1807 1807 )
1808 1808 extra_defaults = {
1809 1809 b'prefix': b'editor',
1810 1810 b'suffix': b'.txt',
1811 1811 }
1812 1812 if extra is not None:
1813 1813 if extra.get(b'suffix') is not None:
1814 1814 self.develwarn(
1815 1815 b'extra.suffix is not None but will soon be '
1816 1816 b'ignored by ui.edit()'
1817 1817 )
1818 1818 extra_defaults.update(extra)
1819 1819 extra = extra_defaults
1820 1820
1821 1821 if action == b'diff':
1822 1822 suffix = b'.diff'
1823 1823 elif action:
1824 1824 suffix = b'.%s.hg.txt' % action
1825 1825 else:
1826 1826 suffix = extra[b'suffix']
1827 1827
1828 1828 rdir = None
1829 1829 if self.configbool(b'experimental', b'editortmpinhg'):
1830 1830 rdir = repopath
1831 1831 (fd, name) = pycompat.mkstemp(
1832 1832 prefix=b'hg-' + extra[b'prefix'] + b'-', suffix=suffix, dir=rdir
1833 1833 )
1834 1834 try:
1835 1835 with os.fdopen(fd, 'wb') as f:
1836 1836 f.write(util.tonativeeol(text))
1837 1837
1838 1838 environ = {b'HGUSER': user}
1839 1839 if b'transplant_source' in extra:
1840 1840 environ.update(
1841 1841 {b'HGREVISION': hex(extra[b'transplant_source'])}
1842 1842 )
1843 1843 for label in (b'intermediate-source', b'source', b'rebase_source'):
1844 1844 if label in extra:
1845 1845 environ.update({b'HGREVISION': extra[label]})
1846 1846 break
1847 1847 if editform:
1848 1848 environ.update({b'HGEDITFORM': editform})
1849 1849 if pending:
1850 1850 environ.update({b'HG_PENDING': pending})
1851 1851
1852 1852 editor = self.geteditor()
1853 1853
1854 1854 self.system(
1855 1855 b"%s \"%s\"" % (editor, name),
1856 1856 environ=environ,
1857 1857 onerr=error.CanceledError,
1858 1858 errprefix=_(b"edit failed"),
1859 1859 blockedtag=b'editor',
1860 1860 )
1861 1861
1862 1862 with open(name, 'rb') as f:
1863 1863 t = util.fromnativeeol(f.read())
1864 1864 finally:
1865 1865 os.unlink(name)
1866 1866
1867 1867 return t
1868 1868
1869 1869 def system(
1870 1870 self,
1871 1871 cmd,
1872 1872 environ=None,
1873 1873 cwd=None,
1874 1874 onerr=None,
1875 1875 errprefix=None,
1876 1876 blockedtag=None,
1877 1877 ):
1878 1878 '''execute shell command with appropriate output stream. command
1879 1879 output will be redirected if fout is not stdout.
1880 1880
1881 1881 if command fails and onerr is None, return status, else raise onerr
1882 1882 object as exception.
1883 1883 '''
1884 1884 if blockedtag is None:
1885 1885 # Long cmds tend to be because of an absolute path on cmd. Keep
1886 1886 # the tail end instead
1887 1887 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1888 1888 blockedtag = b'unknown_system_' + cmdsuffix
1889 1889 out = self._fout
1890 1890 if any(s[1] for s in self._bufferstates):
1891 1891 out = self
1892 1892 with self.timeblockedsection(blockedtag):
1893 1893 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1894 1894 if rc and onerr:
1895 1895 errmsg = b'%s %s' % (
1896 1896 procutil.shellsplit(cmd)[0],
1897 1897 procutil.explainexit(rc),
1898 1898 )
1899 1899 if errprefix:
1900 1900 errmsg = b'%s: %s' % (errprefix, errmsg)
1901 1901 raise onerr(errmsg)
1902 1902 return rc
1903 1903
1904 1904 def _runsystem(self, cmd, environ, cwd, out):
1905 1905 """actually execute the given shell command (can be overridden by
1906 1906 extensions like chg)"""
1907 1907 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1908 1908
1909 1909 def traceback(self, exc=None, force=False):
1910 1910 '''print exception traceback if traceback printing enabled or forced.
1911 1911 only to call in exception handler. returns true if traceback
1912 1912 printed.'''
1913 1913 if self.tracebackflag or force:
1914 1914 if exc is None:
1915 1915 exc = sys.exc_info()
1916 1916 cause = getattr(exc[1], 'cause', None)
1917 1917
1918 1918 if cause is not None:
1919 1919 causetb = traceback.format_tb(cause[2])
1920 1920 exctb = traceback.format_tb(exc[2])
1921 1921 exconly = traceback.format_exception_only(cause[0], cause[1])
1922 1922
1923 1923 # exclude frame where 'exc' was chained and rethrown from exctb
1924 1924 self.write_err(
1925 1925 b'Traceback (most recent call last):\n',
1926 1926 encoding.strtolocal(''.join(exctb[:-1])),
1927 1927 encoding.strtolocal(''.join(causetb)),
1928 1928 encoding.strtolocal(''.join(exconly)),
1929 1929 )
1930 1930 else:
1931 1931 output = traceback.format_exception(exc[0], exc[1], exc[2])
1932 1932 self.write_err(encoding.strtolocal(''.join(output)))
1933 1933 return self.tracebackflag or force
1934 1934
1935 1935 def geteditor(self):
1936 1936 '''return editor to use'''
1937 1937 if pycompat.sysplatform == b'plan9':
1938 1938 # vi is the MIPS instruction simulator on Plan 9. We
1939 1939 # instead default to E to plumb commit messages to
1940 1940 # avoid confusion.
1941 1941 editor = b'E'
1942 1942 elif pycompat.isdarwin:
1943 1943 # vi on darwin is POSIX compatible to a fault, and that includes
1944 1944 # exiting non-zero if you make any mistake when running an ex
1945 1945 # command. Proof: `vi -c ':unknown' -c ':qa'; echo $?` produces 1,
1946 1946 # while s/vi/vim/ doesn't.
1947 1947 editor = b'vim'
1948 1948 else:
1949 1949 editor = b'vi'
1950 1950 return encoding.environ.get(b"HGEDITOR") or self.config(
1951 1951 b"ui", b"editor", editor
1952 1952 )
1953 1953
1954 1954 @util.propertycache
1955 1955 def _progbar(self):
1956 1956 """setup the progbar singleton to the ui object"""
1957 1957 if (
1958 1958 self.quiet
1959 1959 or self.debugflag
1960 1960 or self.configbool(b'progress', b'disable')
1961 1961 or not progress.shouldprint(self)
1962 1962 ):
1963 1963 return None
1964 1964 return getprogbar(self)
1965 1965
1966 1966 def _progclear(self):
1967 1967 """clear progress bar output if any. use it before any output"""
1968 1968 if not haveprogbar(): # nothing loaded yet
1969 1969 return
1970 1970 if self._progbar is not None and self._progbar.printed:
1971 1971 self._progbar.clear()
1972 1972
1973 1973 def makeprogress(self, topic, unit=b"", total=None):
1974 1974 """Create a progress helper for the specified topic"""
1975 1975 if getattr(self._fmsgerr, 'structured', False):
1976 1976 # channel for machine-readable output with metadata, just send
1977 1977 # raw information
1978 1978 # TODO: consider porting some useful information (e.g. estimated
1979 1979 # time) from progbar. we might want to support update delay to
1980 1980 # reduce the cost of transferring progress messages.
1981 1981 def updatebar(topic, pos, item, unit, total):
1982 1982 self._fmsgerr.write(
1983 1983 None,
1984 1984 type=b'progress',
1985 1985 topic=topic,
1986 1986 pos=pos,
1987 1987 item=item,
1988 1988 unit=unit,
1989 1989 total=total,
1990 1990 )
1991 1991
1992 1992 elif self._progbar is not None:
1993 1993 updatebar = self._progbar.progress
1994 1994 else:
1995 1995
1996 1996 def updatebar(topic, pos, item, unit, total):
1997 1997 pass
1998 1998
1999 1999 return scmutil.progress(self, updatebar, topic, unit, total)
2000 2000
2001 2001 def getlogger(self, name):
2002 2002 """Returns a logger of the given name; or None if not registered"""
2003 2003 return self._loggers.get(name)
2004 2004
2005 2005 def setlogger(self, name, logger):
2006 2006 """Install logger which can be identified later by the given name
2007 2007
2008 2008 More than one loggers can be registered. Use extension or module
2009 2009 name to uniquely identify the logger instance.
2010 2010 """
2011 2011 self._loggers[name] = logger
2012 2012
2013 2013 def log(self, event, msgfmt, *msgargs, **opts):
2014 2014 '''hook for logging facility extensions
2015 2015
2016 2016 event should be a readily-identifiable subsystem, which will
2017 2017 allow filtering.
2018 2018
2019 2019 msgfmt should be a newline-terminated format string to log, and
2020 2020 *msgargs are %-formatted into it.
2021 2021
2022 2022 **opts currently has no defined meanings.
2023 2023 '''
2024 2024 if not self._loggers:
2025 2025 return
2026 2026 activeloggers = [
2027 2027 l for l in pycompat.itervalues(self._loggers) if l.tracked(event)
2028 2028 ]
2029 2029 if not activeloggers:
2030 2030 return
2031 2031 msg = msgfmt % msgargs
2032 2032 opts = pycompat.byteskwargs(opts)
2033 2033 # guard against recursion from e.g. ui.debug()
2034 2034 registeredloggers = self._loggers
2035 2035 self._loggers = {}
2036 2036 try:
2037 2037 for logger in activeloggers:
2038 2038 logger.log(self, event, msg, opts)
2039 2039 finally:
2040 2040 self._loggers = registeredloggers
2041 2041
2042 2042 def label(self, msg, label):
2043 2043 '''style msg based on supplied label
2044 2044
2045 2045 If some color mode is enabled, this will add the necessary control
2046 2046 characters to apply such color. In addition, 'debug' color mode adds
2047 2047 markup showing which label affects a piece of text.
2048 2048
2049 2049 ui.write(s, 'label') is equivalent to
2050 2050 ui.write(ui.label(s, 'label')).
2051 2051 '''
2052 2052 if self._colormode is not None:
2053 2053 return color.colorlabel(self, msg, label)
2054 2054 return msg
2055 2055
2056 2056 def develwarn(self, msg, stacklevel=1, config=None):
2057 2057 """issue a developer warning message
2058 2058
2059 2059 Use 'stacklevel' to report the offender some layers further up in the
2060 2060 stack.
2061 2061 """
2062 2062 if not self.configbool(b'devel', b'all-warnings'):
2063 2063 if config is None or not self.configbool(b'devel', config):
2064 2064 return
2065 2065 msg = b'devel-warn: ' + msg
2066 2066 stacklevel += 1 # get in develwarn
2067 2067 if self.tracebackflag:
2068 2068 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
2069 2069 self.log(
2070 2070 b'develwarn',
2071 2071 b'%s at:\n%s'
2072 2072 % (msg, b''.join(util.getstackframes(stacklevel))),
2073 2073 )
2074 2074 else:
2075 2075 curframe = inspect.currentframe()
2076 2076 calframe = inspect.getouterframes(curframe, 2)
2077 2077 fname, lineno, fmsg = calframe[stacklevel][1:4]
2078 2078 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
2079 2079 self.write_err(b'%s at: %s:%d (%s)\n' % (msg, fname, lineno, fmsg))
2080 2080 self.log(
2081 2081 b'develwarn', b'%s at: %s:%d (%s)\n', msg, fname, lineno, fmsg
2082 2082 )
2083 2083
2084 2084 # avoid cycles
2085 2085 del curframe
2086 2086 del calframe
2087 2087
2088 2088 def deprecwarn(self, msg, version, stacklevel=2):
2089 2089 """issue a deprecation warning
2090 2090
2091 2091 - msg: message explaining what is deprecated and how to upgrade,
2092 2092 - version: last version where the API will be supported,
2093 2093 """
2094 2094 if not (
2095 2095 self.configbool(b'devel', b'all-warnings')
2096 2096 or self.configbool(b'devel', b'deprec-warn')
2097 2097 ):
2098 2098 return
2099 2099 msg += (
2100 2100 b"\n(compatibility will be dropped after Mercurial-%s,"
2101 2101 b" update your code.)"
2102 2102 ) % version
2103 2103 self.develwarn(msg, stacklevel=stacklevel, config=b'deprec-warn')
2104 2104
2105 2105 def exportableenviron(self):
2106 2106 """The environment variables that are safe to export, e.g. through
2107 2107 hgweb.
2108 2108 """
2109 2109 return self._exportableenviron
2110 2110
2111 2111 @contextlib.contextmanager
2112 2112 def configoverride(self, overrides, source=b""):
2113 2113 """Context manager for temporary config overrides
2114 2114 `overrides` must be a dict of the following structure:
2115 2115 {(section, name) : value}"""
2116 2116 backups = {}
2117 2117 try:
2118 2118 for (section, name), value in overrides.items():
2119 2119 backups[(section, name)] = self.backupconfig(section, name)
2120 2120 self.setconfig(section, name, value, source)
2121 2121 yield
2122 2122 finally:
2123 2123 for __, backup in backups.items():
2124 2124 self.restoreconfig(backup)
2125 2125 # just restoring ui.quiet config to the previous value is not enough
2126 2126 # as it does not update ui.quiet class member
2127 2127 if (b'ui', b'quiet') in overrides:
2128 2128 self.fixconfig(section=b'ui')
2129 2129
2130 2130 def estimatememory(self):
2131 2131 """Provide an estimate for the available system memory in Bytes.
2132 2132
2133 2133 This can be overriden via ui.available-memory. It returns None, if
2134 2134 no estimate can be computed.
2135 2135 """
2136 2136 value = self.config(b'ui', b'available-memory')
2137 2137 if value is not None:
2138 2138 try:
2139 2139 return util.sizetoint(value)
2140 2140 except error.ParseError:
2141 2141 raise error.ConfigError(
2142 2142 _(b"ui.available-memory value is invalid ('%s')") % value
2143 2143 )
2144 2144 return util._estimatememory()
2145 2145
2146 2146
2147 2147 class paths(dict):
2148 2148 """Represents a collection of paths and their configs.
2149 2149
2150 2150 Data is initially derived from ui instances and the config files they have
2151 2151 loaded.
2152 2152 """
2153 2153
2154 2154 def __init__(self, ui):
2155 2155 dict.__init__(self)
2156 2156
2157 2157 for name, loc in ui.configitems(b'paths', ignoresub=True):
2158 2158 # No location is the same as not existing.
2159 2159 if not loc:
2160 2160 continue
2161 2161 loc, sub = ui.configsuboptions(b'paths', name)
2162 2162 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
2163 2163
2164 2164 def getpath(self, name, default=None):
2165 2165 """Return a ``path`` from a string, falling back to default.
2166 2166
2167 2167 ``name`` can be a named path or locations. Locations are filesystem
2168 2168 paths or URIs.
2169 2169
2170 2170 Returns None if ``name`` is not a registered path, a URI, or a local
2171 2171 path to a repo.
2172 2172 """
2173 2173 # Only fall back to default if no path was requested.
2174 2174 if name is None:
2175 2175 if not default:
2176 2176 default = ()
2177 2177 elif not isinstance(default, (tuple, list)):
2178 2178 default = (default,)
2179 2179 for k in default:
2180 2180 try:
2181 2181 return self[k]
2182 2182 except KeyError:
2183 2183 continue
2184 2184 return None
2185 2185
2186 2186 # Most likely empty string.
2187 2187 # This may need to raise in the future.
2188 2188 if not name:
2189 2189 return None
2190 2190
2191 2191 try:
2192 2192 return self[name]
2193 2193 except KeyError:
2194 2194 # Try to resolve as a local path or URI.
2195 2195 try:
2196 2196 # We don't pass sub-options in, so no need to pass ui instance.
2197 2197 return path(None, None, rawloc=name)
2198 2198 except ValueError:
2199 2199 raise error.RepoError(_(b'repository %s does not exist') % name)
2200 2200
2201 2201
2202 2202 _pathsuboptions = {}
2203 2203
2204 2204
2205 2205 def pathsuboption(option, attr):
2206 2206 """Decorator used to declare a path sub-option.
2207 2207
2208 2208 Arguments are the sub-option name and the attribute it should set on
2209 2209 ``path`` instances.
2210 2210
2211 2211 The decorated function will receive as arguments a ``ui`` instance,
2212 2212 ``path`` instance, and the string value of this option from the config.
2213 2213 The function should return the value that will be set on the ``path``
2214 2214 instance.
2215 2215
2216 2216 This decorator can be used to perform additional verification of
2217 2217 sub-options and to change the type of sub-options.
2218 2218 """
2219 2219
2220 2220 def register(func):
2221 2221 _pathsuboptions[option] = (attr, func)
2222 2222 return func
2223 2223
2224 2224 return register
2225 2225
2226 2226
2227 2227 @pathsuboption(b'pushurl', b'pushloc')
2228 2228 def pushurlpathoption(ui, path, value):
2229 2229 u = util.url(value)
2230 2230 # Actually require a URL.
2231 2231 if not u.scheme:
2232 2232 ui.warn(_(b'(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
2233 2233 return None
2234 2234
2235 2235 # Don't support the #foo syntax in the push URL to declare branch to
2236 2236 # push.
2237 2237 if u.fragment:
2238 2238 ui.warn(
2239 2239 _(
2240 2240 b'("#fragment" in paths.%s:pushurl not supported; '
2241 2241 b'ignoring)\n'
2242 2242 )
2243 2243 % path.name
2244 2244 )
2245 2245 u.fragment = None
2246 2246
2247 2247 return bytes(u)
2248 2248
2249 2249
2250 2250 @pathsuboption(b'pushrev', b'pushrev')
2251 2251 def pushrevpathoption(ui, path, value):
2252 2252 return value
2253 2253
2254 2254
2255 2255 class path(object):
2256 2256 """Represents an individual path and its configuration."""
2257 2257
2258 2258 def __init__(self, ui, name, rawloc=None, suboptions=None):
2259 2259 """Construct a path from its config options.
2260 2260
2261 2261 ``ui`` is the ``ui`` instance the path is coming from.
2262 2262 ``name`` is the symbolic name of the path.
2263 2263 ``rawloc`` is the raw location, as defined in the config.
2264 2264 ``pushloc`` is the raw locations pushes should be made to.
2265 2265
2266 2266 If ``name`` is not defined, we require that the location be a) a local
2267 2267 filesystem path with a .hg directory or b) a URL. If not,
2268 2268 ``ValueError`` is raised.
2269 2269 """
2270 2270 if not rawloc:
2271 2271 raise ValueError(b'rawloc must be defined')
2272 2272
2273 2273 # Locations may define branches via syntax <base>#<branch>.
2274 2274 u = util.url(rawloc)
2275 2275 branch = None
2276 2276 if u.fragment:
2277 2277 branch = u.fragment
2278 2278 u.fragment = None
2279 2279
2280 2280 self.url = u
2281 2281 self.branch = branch
2282 2282
2283 2283 self.name = name
2284 2284 self.rawloc = rawloc
2285 2285 self.loc = b'%s' % u
2286 2286
2287 2287 # When given a raw location but not a symbolic name, validate the
2288 2288 # location is valid.
2289 2289 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
2290 2290 raise ValueError(
2291 2291 b'location is not a URL or path to a local '
2292 2292 b'repo: %s' % rawloc
2293 2293 )
2294 2294
2295 2295 suboptions = suboptions or {}
2296 2296
2297 2297 # Now process the sub-options. If a sub-option is registered, its
2298 2298 # attribute will always be present. The value will be None if there
2299 2299 # was no valid sub-option.
2300 2300 for suboption, (attr, func) in pycompat.iteritems(_pathsuboptions):
2301 2301 if suboption not in suboptions:
2302 2302 setattr(self, attr, None)
2303 2303 continue
2304 2304
2305 2305 value = func(ui, self, suboptions[suboption])
2306 2306 setattr(self, attr, value)
2307 2307
2308 2308 def _isvalidlocalpath(self, path):
2309 2309 """Returns True if the given path is a potentially valid repository.
2310 2310 This is its own function so that extensions can change the definition of
2311 2311 'valid' in this case (like when pulling from a git repo into a hg
2312 2312 one)."""
2313 2313 try:
2314 2314 return os.path.isdir(os.path.join(path, b'.hg'))
2315 2315 # Python 2 may return TypeError. Python 3, ValueError.
2316 2316 except (TypeError, ValueError):
2317 2317 return False
2318 2318
2319 2319 @property
2320 2320 def suboptions(self):
2321 2321 """Return sub-options and their values for this path.
2322 2322
2323 2323 This is intended to be used for presentation purposes.
2324 2324 """
2325 2325 d = {}
2326 2326 for subopt, (attr, _func) in pycompat.iteritems(_pathsuboptions):
2327 2327 value = getattr(self, attr)
2328 2328 if value is not None:
2329 2329 d[subopt] = value
2330 2330 return d
2331 2331
2332 2332
2333 2333 # we instantiate one globally shared progress bar to avoid
2334 2334 # competing progress bars when multiple UI objects get created
2335 2335 _progresssingleton = None
2336 2336
2337 2337
2338 2338 def getprogbar(ui):
2339 2339 global _progresssingleton
2340 2340 if _progresssingleton is None:
2341 2341 # passing 'ui' object to the singleton is fishy,
2342 2342 # this is how the extension used to work but feel free to rework it.
2343 2343 _progresssingleton = progress.progbar(ui)
2344 2344 return _progresssingleton
2345 2345
2346 2346
2347 2347 def haveprogbar():
2348 2348 return _progresssingleton is not None
2349 2349
2350 2350
2351 2351 def _selectmsgdests(ui):
2352 2352 name = ui.config(b'ui', b'message-output')
2353 2353 if name == b'channel':
2354 2354 if ui.fmsg:
2355 2355 return ui.fmsg, ui.fmsg
2356 2356 else:
2357 2357 # fall back to ferr if channel isn't ready so that status/error
2358 2358 # messages can be printed
2359 2359 return ui.ferr, ui.ferr
2360 2360 if name == b'stdio':
2361 2361 return ui.fout, ui.ferr
2362 2362 if name == b'stderr':
2363 2363 return ui.ferr, ui.ferr
2364 2364 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2365 2365
2366 2366
2367 2367 def _writemsgwith(write, dest, *args, **opts):
2368 2368 """Write ui message with the given ui._write*() function
2369 2369
2370 2370 The specified message type is translated to 'ui.<type>' label if the dest
2371 2371 isn't a structured channel, so that the message will be colorized.
2372 2372 """
2373 2373 # TODO: maybe change 'type' to a mandatory option
2374 2374 if 'type' in opts and not getattr(dest, 'structured', False):
2375 2375 opts['label'] = opts.get('label', b'') + b' ui.%s' % opts.pop('type')
2376 2376 write(dest, *args, **opts)
@@ -1,332 +1,332 b''
1 1 $ hg init a
2 2 $ cd a
3 3 $ echo a > a
4 4 $ hg add -n
5 5 adding a
6 6 $ hg st
7 7 ? a
8 8 $ hg add
9 9 adding a
10 10 $ hg st
11 11 A a
12 12 $ hg forget a
13 13 $ hg add
14 14 adding a
15 15 $ hg forget a
16 16 $ hg add --color debug
17 17 [ui.addremove.added ui.status|adding a]
18 18 $ hg st
19 19 A a
20 20 $ mkdir dir
21 21 $ cd dir
22 22 $ hg add ../a
23 23 ../a already tracked!
24 24 $ cd ..
25 25
26 26 $ echo b > b
27 27 $ hg add -n b
28 28 $ hg st
29 29 A a
30 30 ? b
31 31 $ hg add b
32 32 $ hg st
33 33 A a
34 34 A b
35 35
36 36 should fail
37 37
38 38 $ hg add b
39 39 b already tracked!
40 40 $ hg st
41 41 A a
42 42 A b
43 43
44 44 #if no-windows
45 45 $ echo foo > con.xml
46 46 $ hg --config ui.portablefilenames=jump add con.xml
47 abort: ui.portablefilenames value is invalid ('jump')
47 config error: ui.portablefilenames value is invalid ('jump')
48 48 [30]
49 49 $ hg --config ui.portablefilenames=abort add con.xml
50 50 abort: filename contains 'con', which is reserved on Windows: con.xml
51 51 [10]
52 52 $ hg st
53 53 A a
54 54 A b
55 55 ? con.xml
56 56 $ hg add con.xml
57 57 warning: filename contains 'con', which is reserved on Windows: con.xml
58 58 $ hg st
59 59 A a
60 60 A b
61 61 A con.xml
62 62 $ hg forget con.xml
63 63 $ rm con.xml
64 64 #endif
65 65
66 66 #if eol-in-paths
67 67 $ echo bla > 'hello:world'
68 68 $ hg --config ui.portablefilenames=abort add
69 69 adding hello:world
70 70 abort: filename contains ':', which is reserved on Windows: 'hello:world'
71 71 [10]
72 72 $ hg st
73 73 A a
74 74 A b
75 75 ? hello:world
76 76 $ hg --config ui.portablefilenames=ignore add
77 77 adding hello:world
78 78 $ hg st
79 79 A a
80 80 A b
81 81 A hello:world
82 82 #endif
83 83
84 84 $ hg ci -m 0 --traceback
85 85
86 86 $ hg log -r "heads(. or wdir() & file('**'))"
87 87 changeset: 0:* (glob)
88 88 tag: tip
89 89 user: test
90 90 date: Thu Jan 01 00:00:00 1970 +0000
91 91 summary: 0
92 92
93 93 should fail
94 94
95 95 $ hg add a
96 96 a already tracked!
97 97
98 98 $ echo aa > a
99 99 $ hg ci -m 1
100 100 $ hg up 0
101 101 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
102 102 $ echo aaa > a
103 103 $ hg ci -m 2
104 104 created new head
105 105
106 106 $ hg merge
107 107 merging a
108 108 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
109 109 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
110 110 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
111 111 [1]
112 112 $ hg st
113 113 M a
114 114 ? a.orig
115 115
116 116 wdir doesn't cause a crash, and can be dynamically selected if dirty
117 117
118 118 $ hg log -r "heads(. or wdir() & file('**'))"
119 119 changeset: 2147483647:ffffffffffff
120 120 parent: 2:* (glob)
121 121 parent: 1:* (glob)
122 122 user: test
123 123 date: * (glob)
124 124
125 125 should fail
126 126
127 127 $ hg add a
128 128 a already tracked!
129 129 $ hg st
130 130 M a
131 131 ? a.orig
132 132 $ hg resolve -m a
133 133 (no more unresolved files)
134 134 $ hg ci -m merge
135 135
136 136 Issue683: peculiarity with hg revert of an removed then added file
137 137
138 138 $ hg forget a
139 139 $ hg add a
140 140 $ hg st
141 141 ? a.orig
142 142 $ hg rm a
143 143 $ hg st
144 144 R a
145 145 ? a.orig
146 146 $ echo a > a
147 147 $ hg add a
148 148 $ hg st
149 149 M a
150 150 ? a.orig
151 151
152 152 excluded file shouldn't be added even if it is explicitly specified
153 153
154 154 $ hg add a.orig -X '*.orig'
155 155 $ hg st
156 156 M a
157 157 ? a.orig
158 158
159 159 Forgotten file can be added back (as either clean or modified)
160 160
161 161 $ hg forget b
162 162 $ hg add b
163 163 $ hg st -A b
164 164 C b
165 165 $ hg forget b
166 166 $ echo modified > b
167 167 $ hg add b
168 168 $ hg st -A b
169 169 M b
170 170 $ hg revert -qC b
171 171
172 172 $ hg add c && echo "unexpected addition of missing file"
173 173 c: * (glob)
174 174 [1]
175 175 $ echo c > c
176 176 $ hg add d c && echo "unexpected addition of missing file"
177 177 d: * (glob)
178 178 [1]
179 179 $ hg st
180 180 M a
181 181 A c
182 182 ? a.orig
183 183 $ hg up -C
184 184 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
185 185
186 186 forget and get should have the right order: added but missing dir should be
187 187 forgotten before file with same name is added
188 188
189 189 $ echo file d > d
190 190 $ hg add d
191 191 $ hg ci -md
192 192 $ hg rm d
193 193 $ mkdir d
194 194 $ echo a > d/a
195 195 $ hg add d/a
196 196 $ rm -r d
197 197 $ hg up -C
198 198 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
199 199 $ cat d
200 200 file d
201 201
202 202 Test that adding a directory doesn't require case matching (issue4578)
203 203 #if icasefs
204 204 $ mkdir -p CapsDir1/CapsDir
205 205 $ echo abc > CapsDir1/CapsDir/AbC.txt
206 206 $ mkdir CapsDir1/CapsDir/SubDir
207 207 $ echo def > CapsDir1/CapsDir/SubDir/Def.txt
208 208
209 209 $ hg add capsdir1/capsdir
210 210 adding CapsDir1/CapsDir/AbC.txt
211 211 adding CapsDir1/CapsDir/SubDir/Def.txt
212 212
213 213 $ hg forget capsdir1/capsdir/abc.txt
214 214
215 215 $ hg forget capsdir1/capsdir
216 216 removing CapsDir1/CapsDir/SubDir/Def.txt
217 217
218 218 $ hg add capsdir1
219 219 adding CapsDir1/CapsDir/AbC.txt
220 220 adding CapsDir1/CapsDir/SubDir/Def.txt
221 221
222 222 $ hg ci -m "AbCDef" capsdir1/capsdir
223 223
224 224 $ hg status -A capsdir1/capsdir
225 225 C CapsDir1/CapsDir/AbC.txt
226 226 C CapsDir1/CapsDir/SubDir/Def.txt
227 227
228 228 $ hg files capsdir1/capsdir
229 229 CapsDir1/CapsDir/AbC.txt
230 230 CapsDir1/CapsDir/SubDir/Def.txt
231 231
232 232 $ echo xyz > CapsDir1/CapsDir/SubDir/Def.txt
233 233 $ hg ci -m xyz capsdir1/capsdir/subdir/def.txt
234 234
235 235 $ hg revert -r '.^' capsdir1/capsdir
236 236 reverting CapsDir1/CapsDir/SubDir/Def.txt
237 237
238 238 The conditional tests above mean the hash on the diff line differs on Windows
239 239 and OS X
240 240 $ hg diff capsdir1/capsdir
241 241 diff -r * CapsDir1/CapsDir/SubDir/Def.txt (glob)
242 242 --- a/CapsDir1/CapsDir/SubDir/Def.txt Thu Jan 01 00:00:00 1970 +0000
243 243 +++ b/CapsDir1/CapsDir/SubDir/Def.txt * (glob)
244 244 @@ -1,1 +1,1 @@
245 245 -xyz
246 246 +def
247 247
248 248 $ hg mv CapsDir1/CapsDir/abc.txt CapsDir1/CapsDir/ABC.txt
249 249 $ hg ci -m "case changing rename" CapsDir1/CapsDir/AbC.txt CapsDir1/CapsDir/ABC.txt
250 250
251 251 $ hg status -A capsdir1/capsdir
252 252 M CapsDir1/CapsDir/SubDir/Def.txt
253 253 C CapsDir1/CapsDir/ABC.txt
254 254
255 255 $ hg remove -f 'glob:**.txt' -X capsdir1/capsdir
256 256 $ hg remove -f 'glob:**.txt' -I capsdir1/capsdir
257 257 removing CapsDir1/CapsDir/ABC.txt
258 258 removing CapsDir1/CapsDir/SubDir/Def.txt
259 259 #endif
260 260
261 261 $ cd ..
262 262
263 263 test --dry-run mode in forget
264 264
265 265 $ hg init testdir_forget
266 266 $ cd testdir_forget
267 267 $ echo foo > foo
268 268 $ hg add foo
269 269 $ hg commit -m "foo"
270 270 $ hg forget foo --dry-run -v
271 271 removing foo
272 272 $ hg diff
273 273 $ hg forget not_exist -n
274 274 not_exist: $ENOENT$
275 275 [1]
276 276
277 277 $ cd ..
278 278
279 279 test --interactive mode in forget
280 280
281 281 $ hg init interactiveforget
282 282 $ cd interactiveforget
283 283 $ echo foo > foo
284 284 $ hg commit -qAm "foo"
285 285 $ echo bar > bar
286 286 $ hg commit -qAm "bar"
287 287 $ hg forget foo --dry-run -i
288 288 abort: cannot specify both --dry-run and --interactive
289 289 [10]
290 290
291 291 $ hg forget foo --config ui.interactive=True -i << EOF
292 292 > ?
293 293 > n
294 294 > EOF
295 295 forget foo [Ynsa?] ?
296 296 y - yes, forget this file
297 297 n - no, skip this file
298 298 s - skip remaining files
299 299 a - include all remaining files
300 300 ? - ? (display help)
301 301 forget foo [Ynsa?] n
302 302
303 303 $ hg forget foo bar --config ui.interactive=True -i << EOF
304 304 > y
305 305 > n
306 306 > EOF
307 307 forget bar [Ynsa?] y
308 308 forget foo [Ynsa?] n
309 309 removing bar
310 310 $ hg status
311 311 R bar
312 312 $ hg up -qC .
313 313
314 314 $ hg forget foo bar --config ui.interactive=True -i << EOF
315 315 > s
316 316 > EOF
317 317 forget bar [Ynsa?] s
318 318 $ hg st
319 319 $ hg up -qC .
320 320
321 321 $ hg forget foo bar --config ui.interactive=True -i << EOF
322 322 > a
323 323 > EOF
324 324 forget bar [Ynsa?] a
325 325 removing bar
326 326 removing foo
327 327 $ hg status
328 328 R bar
329 329 R foo
330 330 $ hg up -qC .
331 331
332 332 $ cd ..
@@ -1,732 +1,732 b''
1 1 Test illegal name
2 2 -----------------
3 3
4 4 on commit:
5 5
6 6 $ hg init hgname
7 7 $ cd hgname
8 8 $ mkdir sub
9 9 $ hg init sub/.hg
10 10 $ echo 'sub/.hg = sub/.hg' >> .hgsub
11 11 $ hg ci -qAm 'add subrepo "sub/.hg"'
12 12 abort: path 'sub/.hg' is inside nested repo 'sub'
13 13 [255]
14 14
15 15 prepare tampered repo (including the commit above):
16 16
17 17 $ hg import --bypass -qm 'add subrepo "sub/.hg"' - <<'EOF'
18 18 > diff --git a/.hgsub b/.hgsub
19 19 > new file mode 100644
20 20 > --- /dev/null
21 21 > +++ b/.hgsub
22 22 > @@ -0,0 +1,1 @@
23 23 > +sub/.hg = sub/.hg
24 24 > diff --git a/.hgsubstate b/.hgsubstate
25 25 > new file mode 100644
26 26 > --- /dev/null
27 27 > +++ b/.hgsubstate
28 28 > @@ -0,0 +1,1 @@
29 29 > +0000000000000000000000000000000000000000 sub/.hg
30 30 > EOF
31 31 $ cd ..
32 32
33 33 on clone (and update):
34 34
35 35 $ hg clone -q hgname hgname2
36 36 abort: path 'sub/.hg' is inside nested repo 'sub'
37 37 [255]
38 38
39 39 Test absolute path
40 40 ------------------
41 41
42 42 on commit:
43 43
44 44 $ hg init absolutepath
45 45 $ cd absolutepath
46 46 $ hg init sub
47 47 $ echo '/sub = sub' >> .hgsub
48 48 $ hg ci -qAm 'add subrepo "/sub"'
49 49 abort: path contains illegal component: /sub
50 50 [255]
51 51
52 52 prepare tampered repo (including the commit above):
53 53
54 54 $ hg import --bypass -qm 'add subrepo "/sub"' - <<'EOF'
55 55 > diff --git a/.hgsub b/.hgsub
56 56 > new file mode 100644
57 57 > --- /dev/null
58 58 > +++ b/.hgsub
59 59 > @@ -0,0 +1,1 @@
60 60 > +/sub = sub
61 61 > diff --git a/.hgsubstate b/.hgsubstate
62 62 > new file mode 100644
63 63 > --- /dev/null
64 64 > +++ b/.hgsubstate
65 65 > @@ -0,0 +1,1 @@
66 66 > +0000000000000000000000000000000000000000 /sub
67 67 > EOF
68 68 $ cd ..
69 69
70 70 on clone (and update):
71 71
72 72 $ hg clone -q absolutepath absolutepath2
73 73 abort: path contains illegal component: /sub
74 74 [255]
75 75
76 76 Test root path
77 77 --------------
78 78
79 79 on commit:
80 80
81 81 $ hg init rootpath
82 82 $ cd rootpath
83 83 $ hg init sub
84 84 $ echo '/ = sub' >> .hgsub
85 85 $ hg ci -qAm 'add subrepo "/"'
86 86 abort: path ends in directory separator: /
87 87 [255]
88 88
89 89 prepare tampered repo (including the commit above):
90 90
91 91 $ hg import --bypass -qm 'add subrepo "/"' - <<'EOF'
92 92 > diff --git a/.hgsub b/.hgsub
93 93 > new file mode 100644
94 94 > --- /dev/null
95 95 > +++ b/.hgsub
96 96 > @@ -0,0 +1,1 @@
97 97 > +/ = sub
98 98 > diff --git a/.hgsubstate b/.hgsubstate
99 99 > new file mode 100644
100 100 > --- /dev/null
101 101 > +++ b/.hgsubstate
102 102 > @@ -0,0 +1,1 @@
103 103 > +0000000000000000000000000000000000000000 /
104 104 > EOF
105 105 $ cd ..
106 106
107 107 on clone (and update):
108 108
109 109 $ hg clone -q rootpath rootpath2
110 110 abort: path ends in directory separator: /
111 111 [255]
112 112
113 113 Test empty path
114 114 ---------------
115 115
116 116 on commit:
117 117
118 118 $ hg init emptypath
119 119 $ cd emptypath
120 120 $ hg init sub
121 121 $ echo '= sub' >> .hgsub
122 122 $ hg ci -qAm 'add subrepo ""'
123 hg: parse error at .hgsub:1: = sub
124 [255]
123 config error at .hgsub:1: = sub
124 [30]
125 125
126 126 prepare tampered repo (including the commit above):
127 127
128 128 $ hg import --bypass -qm 'add subrepo ""' - <<'EOF'
129 129 > diff --git a/.hgsub b/.hgsub
130 130 > new file mode 100644
131 131 > --- /dev/null
132 132 > +++ b/.hgsub
133 133 > @@ -0,0 +1,1 @@
134 134 > += sub
135 135 > diff --git a/.hgsubstate b/.hgsubstate
136 136 > new file mode 100644
137 137 > --- /dev/null
138 138 > +++ b/.hgsubstate
139 139 > @@ -0,0 +1,1 @@
140 140 > +0000000000000000000000000000000000000000
141 141 > EOF
142 142 $ cd ..
143 143
144 144 on clone (and update):
145 145
146 146 $ hg clone -q emptypath emptypath2
147 hg: parse error at .hgsub:1: = sub
148 [255]
147 config error at .hgsub:1: = sub
148 [30]
149 149
150 150 Test current path
151 151 -----------------
152 152
153 153 on commit:
154 154
155 155 $ hg init currentpath
156 156 $ cd currentpath
157 157 $ hg init sub
158 158 $ echo '. = sub' >> .hgsub
159 159 $ hg ci -qAm 'add subrepo "."'
160 160 abort: subrepo path contains illegal component: .
161 161 [255]
162 162
163 163 prepare tampered repo (including the commit above):
164 164
165 165 $ hg import --bypass -qm 'add subrepo "."' - <<'EOF'
166 166 > diff --git a/.hgsub b/.hgsub
167 167 > new file mode 100644
168 168 > --- /dev/null
169 169 > +++ b/.hgsub
170 170 > @@ -0,0 +1,1 @@
171 171 > +.= sub
172 172 > diff --git a/.hgsubstate b/.hgsubstate
173 173 > new file mode 100644
174 174 > --- /dev/null
175 175 > +++ b/.hgsubstate
176 176 > @@ -0,0 +1,1 @@
177 177 > +0000000000000000000000000000000000000000 .
178 178 > EOF
179 179 $ cd ..
180 180
181 181 on clone (and update):
182 182
183 183 $ hg clone -q currentpath currentpath2
184 184 abort: subrepo path contains illegal component: .
185 185 [255]
186 186
187 187 Test outer path
188 188 ---------------
189 189
190 190 on commit:
191 191
192 192 $ mkdir outerpath
193 193 $ cd outerpath
194 194 $ hg init main
195 195 $ cd main
196 196 $ hg init ../sub
197 197 $ echo '../sub = ../sub' >> .hgsub
198 198 $ hg ci -qAm 'add subrepo "../sub"'
199 199 abort: path contains illegal component: ../sub
200 200 [255]
201 201
202 202 prepare tampered repo (including the commit above):
203 203
204 204 $ hg import --bypass -qm 'add subrepo "../sub"' - <<'EOF'
205 205 > diff --git a/.hgsub b/.hgsub
206 206 > new file mode 100644
207 207 > --- /dev/null
208 208 > +++ b/.hgsub
209 209 > @@ -0,0 +1,1 @@
210 210 > +../sub = ../sub
211 211 > diff --git a/.hgsubstate b/.hgsubstate
212 212 > new file mode 100644
213 213 > --- /dev/null
214 214 > +++ b/.hgsubstate
215 215 > @@ -0,0 +1,1 @@
216 216 > +0000000000000000000000000000000000000000 ../sub
217 217 > EOF
218 218 $ cd ..
219 219
220 220 on clone (and update):
221 221
222 222 $ hg clone -q main main2
223 223 abort: path contains illegal component: ../sub
224 224 [255]
225 225 $ cd ..
226 226
227 227 Test variable expansion
228 228 -----------------------
229 229
230 230 Subrepository paths shouldn't be expanded, but we fail to handle them
231 231 properly. Any local repository paths are expanded.
232 232
233 233 on commit:
234 234
235 235 $ mkdir envvar
236 236 $ cd envvar
237 237 $ hg init main
238 238 $ cd main
239 239 $ hg init sub1
240 240 $ cat <<'EOF' > sub1/hgrc
241 241 > [hooks]
242 242 > log = echo pwned
243 243 > EOF
244 244 $ hg -R sub1 ci -qAm 'add sub1 files'
245 245 $ hg -R sub1 log -r. -T '{node}\n'
246 246 39eb4b4d3e096527668784893a9280578a8f38b8
247 247 $ echo '$SUB = sub1' >> .hgsub
248 248 $ SUB=sub1 hg ci -qAm 'add subrepo "$SUB"'
249 249 abort: subrepo path contains illegal component: $SUB
250 250 [255]
251 251
252 252 prepare tampered repo (including the changes above as two commits):
253 253
254 254 $ hg import --bypass -qm 'add subrepo "$SUB"' - <<'EOF'
255 255 > diff --git a/.hgsub b/.hgsub
256 256 > new file mode 100644
257 257 > --- /dev/null
258 258 > +++ b/.hgsub
259 259 > @@ -0,0 +1,1 @@
260 260 > +$SUB = sub1
261 261 > diff --git a/.hgsubstate b/.hgsubstate
262 262 > new file mode 100644
263 263 > --- /dev/null
264 264 > +++ b/.hgsubstate
265 265 > @@ -0,0 +1,1 @@
266 266 > +0000000000000000000000000000000000000000 $SUB
267 267 > EOF
268 268 $ hg debugsetparents 0
269 269 $ hg import --bypass -qm 'update subrepo "$SUB"' - <<'EOF'
270 270 > diff --git a/.hgsubstate b/.hgsubstate
271 271 > --- a/.hgsubstate
272 272 > +++ b/.hgsubstate
273 273 > @@ -1,1 +1,1 @@
274 274 > -0000000000000000000000000000000000000000 $SUB
275 275 > +39eb4b4d3e096527668784893a9280578a8f38b8 $SUB
276 276 > EOF
277 277 $ cd ..
278 278
279 279 on clone (and update) with various substitutions:
280 280
281 281 $ hg clone -q main main2
282 282 abort: subrepo path contains illegal component: $SUB
283 283 [255]
284 284 $ ls -A main2
285 285 .hg
286 286 .hgsub
287 287 .hgsubstate
288 288
289 289 $ SUB=sub1 hg clone -q main main3
290 290 abort: subrepo path contains illegal component: $SUB
291 291 [255]
292 292 $ ls -A main3
293 293 .hg
294 294 .hgsub
295 295 .hgsubstate
296 296
297 297 $ SUB=sub2 hg clone -q main main4
298 298 abort: subrepo path contains illegal component: $SUB
299 299 [255]
300 300 $ ls -A main4
301 301 .hg
302 302 .hgsub
303 303 .hgsubstate
304 304
305 305 on clone empty subrepo into .hg, then pull (and update), which at least fails:
306 306
307 307 $ SUB=.hg hg clone -qr0 main main5
308 308 abort: subrepo path contains illegal component: $SUB
309 309 [255]
310 310 $ ls -A main5
311 311 .hg
312 312 .hgsub
313 313 .hgsubstate
314 314 $ test -d main5/.hg/.hg
315 315 [1]
316 316 $ SUB=.hg hg -R main5 pull -u
317 317 pulling from $TESTTMP/envvar/main
318 318 searching for changes
319 319 adding changesets
320 320 adding manifests
321 321 adding file changes
322 322 added 1 changesets with 1 changes to 1 files
323 323 new changesets 7a2f0e59146f
324 324 .hgsubstate: untracked file differs
325 325 abort: untracked files in working directory differ from files in requested revision
326 326 [255]
327 327 $ cat main5/.hg/hgrc | grep pwned
328 328 [1]
329 329
330 330 on clone (and update) into .hg, which at least fails:
331 331
332 332 $ SUB=.hg hg clone -q main main6
333 333 abort: subrepo path contains illegal component: $SUB
334 334 [255]
335 335 $ ls -A main6
336 336 .hg
337 337 .hgsub
338 338 .hgsubstate
339 339 $ cat main6/.hg/hgrc | grep pwned
340 340 [1]
341 341
342 342 on clone (and update) into .hg/* subdir:
343 343
344 344 $ SUB=.hg/foo hg clone -q main main7
345 345 abort: subrepo path contains illegal component: $SUB
346 346 [255]
347 347 $ ls -A main7
348 348 .hg
349 349 .hgsub
350 350 .hgsubstate
351 351 $ test -d main7/.hg/.hg
352 352 [1]
353 353
354 354 on clone (and update) into outer tree:
355 355
356 356 $ SUB=../out-of-tree-write hg clone -q main main8
357 357 abort: subrepo path contains illegal component: $SUB
358 358 [255]
359 359 $ ls -A main8
360 360 .hg
361 361 .hgsub
362 362 .hgsubstate
363 363
364 364 on clone (and update) into e.g. $HOME, which doesn't work since subrepo paths
365 365 are concatenated prior to variable expansion:
366 366
367 367 $ SUB="$TESTTMP/envvar/fakehome" hg clone -q main main9
368 368 abort: subrepo path contains illegal component: $SUB
369 369 [255]
370 370 $ ls -A main9 | wc -l
371 371 \s*3 (re)
372 372
373 373 $ ls
374 374 main
375 375 main2
376 376 main3
377 377 main4
378 378 main5
379 379 main6
380 380 main7
381 381 main8
382 382 main9
383 383 $ cd ..
384 384
385 385 Test tilde
386 386 ----------
387 387
388 388 The leading tilde may be expanded to $HOME, but it can be a valid subrepo
389 389 path in theory. However, we want to prohibit it as there might be unsafe
390 390 handling of such paths.
391 391
392 392 on commit:
393 393
394 394 $ hg init tilde
395 395 $ cd tilde
396 396 $ hg init './~'
397 397 $ echo '~ = ~' >> .hgsub
398 398 $ hg ci -qAm 'add subrepo "~"'
399 399 abort: subrepo path contains illegal component: ~
400 400 [255]
401 401
402 402 prepare tampered repo (including the commit above):
403 403
404 404 $ hg import --bypass -qm 'add subrepo "~"' - <<'EOF'
405 405 > diff --git a/.hgsub b/.hgsub
406 406 > new file mode 100644
407 407 > --- /dev/null
408 408 > +++ b/.hgsub
409 409 > @@ -0,0 +1,1 @@
410 410 > +~ = ~
411 411 > diff --git a/.hgsubstate b/.hgsubstate
412 412 > new file mode 100644
413 413 > --- /dev/null
414 414 > +++ b/.hgsubstate
415 415 > @@ -0,0 +1,1 @@
416 416 > +0000000000000000000000000000000000000000 ~
417 417 > EOF
418 418 $ cd ..
419 419
420 420 on clone (and update):
421 421
422 422 $ hg clone -q tilde tilde2
423 423 abort: subrepo path contains illegal component: ~
424 424 [255]
425 425
426 426 Test direct symlink traversal
427 427 -----------------------------
428 428
429 429 #if symlink
430 430
431 431 on commit:
432 432
433 433 $ mkdir hgsymdir
434 434 $ hg init hgsymdir/root
435 435 $ cd hgsymdir/root
436 436 $ ln -s ../out
437 437 $ hg ci -qAm 'add symlink "out"'
438 438 $ hg init ../out
439 439 $ echo 'out = out' >> .hgsub
440 440 $ hg ci -qAm 'add subrepo "out"'
441 441 abort: subrepo 'out' traverses symbolic link
442 442 [255]
443 443
444 444 prepare tampered repo (including the commit above):
445 445
446 446 $ hg import --bypass -qm 'add subrepo "out"' - <<'EOF'
447 447 > diff --git a/.hgsub b/.hgsub
448 448 > new file mode 100644
449 449 > --- /dev/null
450 450 > +++ b/.hgsub
451 451 > @@ -0,0 +1,1 @@
452 452 > +out = out
453 453 > diff --git a/.hgsubstate b/.hgsubstate
454 454 > new file mode 100644
455 455 > --- /dev/null
456 456 > +++ b/.hgsubstate
457 457 > @@ -0,0 +1,1 @@
458 458 > +0000000000000000000000000000000000000000 out
459 459 > EOF
460 460 $ cd ../..
461 461
462 462 on clone (and update):
463 463
464 464 $ mkdir hgsymdir2
465 465 $ hg clone -q hgsymdir/root hgsymdir2/root
466 466 abort: subrepo 'out' traverses symbolic link
467 467 [255]
468 468 $ ls hgsymdir2
469 469 root
470 470
471 471 #endif
472 472
473 473 Test indirect symlink traversal
474 474 -------------------------------
475 475
476 476 #if symlink
477 477
478 478 on commit:
479 479
480 480 $ mkdir hgsymin
481 481 $ hg init hgsymin/root
482 482 $ cd hgsymin/root
483 483 $ ln -s ../out
484 484 $ hg ci -qAm 'add symlink "out"'
485 485 $ mkdir ../out
486 486 $ hg init ../out/sub
487 487 $ echo 'out/sub = out/sub' >> .hgsub
488 488 $ hg ci -qAm 'add subrepo "out/sub"'
489 489 abort: path 'out/sub' traverses symbolic link 'out'
490 490 [255]
491 491
492 492 prepare tampered repo (including the commit above):
493 493
494 494 $ hg import --bypass -qm 'add subrepo "out/sub"' - <<'EOF'
495 495 > diff --git a/.hgsub b/.hgsub
496 496 > new file mode 100644
497 497 > --- /dev/null
498 498 > +++ b/.hgsub
499 499 > @@ -0,0 +1,1 @@
500 500 > +out/sub = out/sub
501 501 > diff --git a/.hgsubstate b/.hgsubstate
502 502 > new file mode 100644
503 503 > --- /dev/null
504 504 > +++ b/.hgsubstate
505 505 > @@ -0,0 +1,1 @@
506 506 > +0000000000000000000000000000000000000000 out/sub
507 507 > EOF
508 508 $ cd ../..
509 509
510 510 on clone (and update):
511 511
512 512 $ mkdir hgsymin2
513 513 $ hg clone -q hgsymin/root hgsymin2/root
514 514 abort: path 'out/sub' traverses symbolic link 'out'
515 515 [255]
516 516 $ ls hgsymin2
517 517 root
518 518
519 519 #endif
520 520
521 521 Test symlink traversal by variable expansion
522 522 --------------------------------------------
523 523
524 524 #if symlink
525 525
526 526 $ FAKEHOME="$TESTTMP/envvarsym/fakehome"
527 527
528 528 on commit:
529 529
530 530 $ mkdir envvarsym
531 531 $ cd envvarsym
532 532 $ hg init main
533 533 $ cd main
534 534 $ ln -s "`echo "$FAKEHOME" | sed 's|\(.\)/.*|\1|'`"
535 535 $ hg ci -qAm 'add symlink to top-level system directory'
536 536
537 537 $ hg init sub1
538 538 $ echo pwned > sub1/pwned
539 539 $ hg -R sub1 ci -qAm 'add sub1 files'
540 540 $ hg -R sub1 log -r. -T '{node}\n'
541 541 f40c9134ba1b6961e12f250868823f0092fb68a8
542 542 $ echo '$SUB = sub1' >> .hgsub
543 543 $ SUB="$FAKEHOME" hg ci -qAm 'add subrepo "$SUB"'
544 544 abort: subrepo path contains illegal component: $SUB
545 545 [255]
546 546
547 547 prepare tampered repo (including the changes above as two commits):
548 548
549 549 $ hg import --bypass -qm 'add subrepo "$SUB"' - <<'EOF'
550 550 > diff --git a/.hgsub b/.hgsub
551 551 > new file mode 100644
552 552 > --- /dev/null
553 553 > +++ b/.hgsub
554 554 > @@ -0,0 +1,1 @@
555 555 > +$SUB = sub1
556 556 > diff --git a/.hgsubstate b/.hgsubstate
557 557 > new file mode 100644
558 558 > --- /dev/null
559 559 > +++ b/.hgsubstate
560 560 > @@ -0,0 +1,1 @@
561 561 > +0000000000000000000000000000000000000000 $SUB
562 562 > EOF
563 563 $ hg debugsetparents 1
564 564 $ hg import --bypass -qm 'update subrepo "$SUB"' - <<'EOF'
565 565 > diff --git a/.hgsubstate b/.hgsubstate
566 566 > --- a/.hgsubstate
567 567 > +++ b/.hgsubstate
568 568 > @@ -1,1 +1,1 @@
569 569 > -0000000000000000000000000000000000000000 $SUB
570 570 > +f40c9134ba1b6961e12f250868823f0092fb68a8 $SUB
571 571 > EOF
572 572 $ cd ..
573 573
574 574 on clone (and update) without fakehome directory:
575 575
576 576 $ rm -fR "$FAKEHOME"
577 577 $ SUB="$FAKEHOME" hg clone -q main main2
578 578 abort: subrepo path contains illegal component: $SUB
579 579 [255]
580 580 $ test -d "$FAKEHOME"
581 581 [1]
582 582
583 583 on clone (and update) with empty fakehome directory:
584 584
585 585 $ rm -fR "$FAKEHOME"
586 586 $ mkdir "$FAKEHOME"
587 587 $ SUB="$FAKEHOME" hg clone -q main main3
588 588 abort: subrepo path contains illegal component: $SUB
589 589 [255]
590 590 $ ls "$FAKEHOME"
591 591
592 592 on clone (and update) with non-empty fakehome directory:
593 593
594 594 $ rm -fR "$FAKEHOME"
595 595 $ mkdir "$FAKEHOME"
596 596 $ touch "$FAKEHOME/a"
597 597 $ SUB="$FAKEHOME" hg clone -q main main4
598 598 abort: subrepo path contains illegal component: $SUB
599 599 [255]
600 600 $ ls "$FAKEHOME"
601 601 a
602 602
603 603 on clone empty subrepo with non-empty fakehome directory,
604 604 then pull (and update):
605 605
606 606 $ rm -fR "$FAKEHOME"
607 607 $ mkdir "$FAKEHOME"
608 608 $ touch "$FAKEHOME/a"
609 609 $ SUB="$FAKEHOME" hg clone -qr1 main main5
610 610 abort: subrepo path contains illegal component: $SUB
611 611 [255]
612 612 $ ls "$FAKEHOME"
613 613 a
614 614 $ test -d "$FAKEHOME/.hg"
615 615 [1]
616 616 $ SUB="$FAKEHOME" hg -R main5 pull -u
617 617 pulling from $TESTTMP/envvarsym/main
618 618 searching for changes
619 619 adding changesets
620 620 adding manifests
621 621 adding file changes
622 622 added 1 changesets with 1 changes to 1 files
623 623 new changesets * (glob)
624 624 .hgsubstate: untracked file differs
625 625 abort: untracked files in working directory differ from files in requested revision
626 626 [255]
627 627 $ ls "$FAKEHOME"
628 628 a
629 629 $ test -d "$FAKEHOME/.hg"
630 630 [1]
631 631
632 632 on clone empty subrepo with hg-managed fakehome directory,
633 633 then pull (and update):
634 634
635 635 $ rm -fR "$FAKEHOME"
636 636 $ hg init "$FAKEHOME"
637 637 $ touch "$FAKEHOME/a"
638 638 $ hg -R "$FAKEHOME" ci -qAm 'add fakehome file'
639 639 $ SUB="$FAKEHOME" hg clone -qr1 main main6
640 640 abort: subrepo path contains illegal component: $SUB
641 641 [255]
642 642 $ ls -A "$FAKEHOME"
643 643 .hg
644 644 a
645 645 $ SUB="$FAKEHOME" hg -R main6 pull -u
646 646 pulling from $TESTTMP/envvarsym/main
647 647 searching for changes
648 648 adding changesets
649 649 adding manifests
650 650 adding file changes
651 651 added 1 changesets with 1 changes to 1 files
652 652 new changesets * (glob)
653 653 .hgsubstate: untracked file differs
654 654 abort: untracked files in working directory differ from files in requested revision
655 655 [255]
656 656 $ ls -A "$FAKEHOME"
657 657 .hg
658 658 a
659 659
660 660 on clone only symlink with hg-managed fakehome directory,
661 661 then pull (and update):
662 662
663 663 $ rm -fR "$FAKEHOME"
664 664 $ hg init "$FAKEHOME"
665 665 $ touch "$FAKEHOME/a"
666 666 $ hg -R "$FAKEHOME" ci -qAm 'add fakehome file'
667 667 $ SUB="$FAKEHOME" hg clone -qr0 main main7
668 668 $ ls -A "$FAKEHOME"
669 669 .hg
670 670 a
671 671 $ SUB="$FAKEHOME" hg -R main7 pull -uf
672 672 pulling from $TESTTMP/envvarsym/main
673 673 searching for changes
674 674 adding changesets
675 675 adding manifests
676 676 adding file changes
677 677 added 2 changesets with 3 changes to 2 files
678 678 new changesets * (glob)
679 679 abort: subrepo path contains illegal component: $SUB
680 680 [255]
681 681 $ ls -A "$FAKEHOME"
682 682 .hg
683 683 a
684 684
685 685 $ cd ..
686 686
687 687 #endif
688 688
689 689 Test drive letter
690 690 -----------------
691 691
692 692 Windows has a weird relative path that can change the drive letter, which
693 693 should also be prohibited on Windows.
694 694
695 695 prepare tampered repo:
696 696
697 697 $ hg init driveletter
698 698 $ cd driveletter
699 699 $ hg import --bypass -qm 'add subrepo "X:"' - <<'EOF'
700 700 > diff --git a/.hgsub b/.hgsub
701 701 > new file mode 100644
702 702 > --- /dev/null
703 703 > +++ b/.hgsub
704 704 > @@ -0,0 +1,1 @@
705 705 > +X: = foo
706 706 > diff --git a/.hgsubstate b/.hgsubstate
707 707 > new file mode 100644
708 708 > --- /dev/null
709 709 > +++ b/.hgsubstate
710 710 > @@ -0,0 +1,1 @@
711 711 > +0000000000000000000000000000000000000000 X:
712 712 > EOF
713 713 $ cd ..
714 714
715 715 on clone (and update):
716 716
717 717 #if windows
718 718
719 719 $ hg clone -q driveletter driveletter2
720 720 abort: path contains illegal component: X:
721 721 [255]
722 722
723 723 #else
724 724
725 725 $ hg clone -q driveletter driveletter2
726 726 $ ls -A driveletter2
727 727 .hg
728 728 .hgsub
729 729 .hgsubstate
730 730 X:
731 731
732 732 #endif
@@ -1,390 +1,390 b''
1 1 hide outer repo
2 2 $ hg init
3 3
4 4 Invalid syntax: no value
5 5
6 6 $ cat > .hg/hgrc << EOF
7 7 > novaluekey
8 8 > EOF
9 9 $ hg showconfig
10 hg: parse error at $TESTTMP/.hg/hgrc:1: novaluekey
11 [255]
10 config error at $TESTTMP/.hg/hgrc:1: novaluekey
11 [30]
12 12
13 13 Invalid syntax: no key
14 14
15 15 $ cat > .hg/hgrc << EOF
16 16 > =nokeyvalue
17 17 > EOF
18 18 $ hg showconfig
19 hg: parse error at $TESTTMP/.hg/hgrc:1: =nokeyvalue
20 [255]
19 config error at $TESTTMP/.hg/hgrc:1: =nokeyvalue
20 [30]
21 21
22 22 Test hint about invalid syntax from leading white space
23 23
24 24 $ cat > .hg/hgrc << EOF
25 25 > key=value
26 26 > EOF
27 27 $ hg showconfig
28 hg: parse error at $TESTTMP/.hg/hgrc:1: unexpected leading whitespace: key=value
29 [255]
28 config error at $TESTTMP/.hg/hgrc:1: unexpected leading whitespace: key=value
29 [30]
30 30
31 31 $ cat > .hg/hgrc << EOF
32 32 > [section]
33 33 > key=value
34 34 > EOF
35 35 $ hg showconfig
36 hg: parse error at $TESTTMP/.hg/hgrc:1: unexpected leading whitespace: [section]
37 [255]
36 config error at $TESTTMP/.hg/hgrc:1: unexpected leading whitespace: [section]
37 [30]
38 38
39 39 Reset hgrc
40 40
41 41 $ echo > .hg/hgrc
42 42
43 43 Test case sensitive configuration
44 44
45 45 $ cat <<EOF >> $HGRCPATH
46 46 > [Section]
47 47 > KeY = Case Sensitive
48 48 > key = lower case
49 49 > EOF
50 50
51 51 $ hg showconfig Section
52 52 Section.KeY=Case Sensitive
53 53 Section.key=lower case
54 54
55 55 $ hg showconfig Section -Tjson
56 56 [
57 57 {
58 58 "defaultvalue": null,
59 59 "name": "Section.KeY",
60 60 "source": "*.hgrc:*", (glob)
61 61 "value": "Case Sensitive"
62 62 },
63 63 {
64 64 "defaultvalue": null,
65 65 "name": "Section.key",
66 66 "source": "*.hgrc:*", (glob)
67 67 "value": "lower case"
68 68 }
69 69 ]
70 70 $ hg showconfig Section.KeY -Tjson
71 71 [
72 72 {
73 73 "defaultvalue": null,
74 74 "name": "Section.KeY",
75 75 "source": "*.hgrc:*", (glob)
76 76 "value": "Case Sensitive"
77 77 }
78 78 ]
79 79 $ hg showconfig -Tjson | tail -7
80 80 {
81 81 "defaultvalue": null,
82 82 "name": "*", (glob)
83 83 "source": "*", (glob)
84 84 "value": "*" (glob)
85 85 }
86 86 ]
87 87
88 88 Test config default of various types:
89 89
90 90 {"defaultvalue": ""} for -T'json(defaultvalue)' looks weird, but that's
91 91 how the templater works. Unknown keywords are evaluated to "".
92 92
93 93 dynamicdefault
94 94
95 95 $ hg config --config alias.foo= alias -Tjson
96 96 [
97 97 {
98 98 "name": "alias.foo",
99 99 "source": "--config",
100 100 "value": ""
101 101 }
102 102 ]
103 103 $ hg config --config alias.foo= alias -T'json(defaultvalue)'
104 104 [
105 105 {"defaultvalue": ""}
106 106 ]
107 107 $ hg config --config alias.foo= alias -T'{defaultvalue}\n'
108 108
109 109
110 110 null
111 111
112 112 $ hg config --config auth.cookiefile= auth -Tjson
113 113 [
114 114 {
115 115 "defaultvalue": null,
116 116 "name": "auth.cookiefile",
117 117 "source": "--config",
118 118 "value": ""
119 119 }
120 120 ]
121 121 $ hg config --config auth.cookiefile= auth -T'json(defaultvalue)'
122 122 [
123 123 {"defaultvalue": null}
124 124 ]
125 125 $ hg config --config auth.cookiefile= auth -T'{defaultvalue}\n'
126 126
127 127
128 128 false
129 129
130 130 $ hg config --config commands.commit.post-status= commands -Tjson
131 131 [
132 132 {
133 133 "defaultvalue": false,
134 134 "name": "commands.commit.post-status",
135 135 "source": "--config",
136 136 "value": ""
137 137 }
138 138 ]
139 139 $ hg config --config commands.commit.post-status= commands -T'json(defaultvalue)'
140 140 [
141 141 {"defaultvalue": false}
142 142 ]
143 143 $ hg config --config commands.commit.post-status= commands -T'{defaultvalue}\n'
144 144 False
145 145
146 146 true
147 147
148 148 $ hg config --config format.dotencode= format -Tjson
149 149 [
150 150 {
151 151 "defaultvalue": true,
152 152 "name": "format.dotencode",
153 153 "source": "--config",
154 154 "value": ""
155 155 }
156 156 ]
157 157 $ hg config --config format.dotencode= format -T'json(defaultvalue)'
158 158 [
159 159 {"defaultvalue": true}
160 160 ]
161 161 $ hg config --config format.dotencode= format -T'{defaultvalue}\n'
162 162 True
163 163
164 164 bytes
165 165
166 166 $ hg config --config commands.resolve.mark-check= commands -Tjson
167 167 [
168 168 {
169 169 "defaultvalue": "none",
170 170 "name": "commands.resolve.mark-check",
171 171 "source": "--config",
172 172 "value": ""
173 173 }
174 174 ]
175 175 $ hg config --config commands.resolve.mark-check= commands -T'json(defaultvalue)'
176 176 [
177 177 {"defaultvalue": "none"}
178 178 ]
179 179 $ hg config --config commands.resolve.mark-check= commands -T'{defaultvalue}\n'
180 180 none
181 181
182 182 empty list
183 183
184 184 $ hg config --config commands.show.aliasprefix= commands -Tjson
185 185 [
186 186 {
187 187 "defaultvalue": [],
188 188 "name": "commands.show.aliasprefix",
189 189 "source": "--config",
190 190 "value": ""
191 191 }
192 192 ]
193 193 $ hg config --config commands.show.aliasprefix= commands -T'json(defaultvalue)'
194 194 [
195 195 {"defaultvalue": []}
196 196 ]
197 197 $ hg config --config commands.show.aliasprefix= commands -T'{defaultvalue}\n'
198 198
199 199
200 200 nonempty list
201 201
202 202 $ hg config --config progress.format= progress -Tjson
203 203 [
204 204 {
205 205 "defaultvalue": ["topic", "bar", "number", "estimate"],
206 206 "name": "progress.format",
207 207 "source": "--config",
208 208 "value": ""
209 209 }
210 210 ]
211 211 $ hg config --config progress.format= progress -T'json(defaultvalue)'
212 212 [
213 213 {"defaultvalue": ["topic", "bar", "number", "estimate"]}
214 214 ]
215 215 $ hg config --config progress.format= progress -T'{defaultvalue}\n'
216 216 topic bar number estimate
217 217
218 218 int
219 219
220 220 $ hg config --config profiling.freq= profiling -Tjson
221 221 [
222 222 {
223 223 "defaultvalue": 1000,
224 224 "name": "profiling.freq",
225 225 "source": "--config",
226 226 "value": ""
227 227 }
228 228 ]
229 229 $ hg config --config profiling.freq= profiling -T'json(defaultvalue)'
230 230 [
231 231 {"defaultvalue": 1000}
232 232 ]
233 233 $ hg config --config profiling.freq= profiling -T'{defaultvalue}\n'
234 234 1000
235 235
236 236 float
237 237
238 238 $ hg config --config profiling.showmax= profiling -Tjson
239 239 [
240 240 {
241 241 "defaultvalue": 0.999,
242 242 "name": "profiling.showmax",
243 243 "source": "--config",
244 244 "value": ""
245 245 }
246 246 ]
247 247 $ hg config --config profiling.showmax= profiling -T'json(defaultvalue)'
248 248 [
249 249 {"defaultvalue": 0.999}
250 250 ]
251 251 $ hg config --config profiling.showmax= profiling -T'{defaultvalue}\n'
252 252 0.999
253 253
254 254 Test empty config source:
255 255
256 256 $ cat <<EOF > emptysource.py
257 257 > def reposetup(ui, repo):
258 258 > ui.setconfig(b'empty', b'source', b'value')
259 259 > EOF
260 260 $ cp .hg/hgrc .hg/hgrc.orig
261 261 $ cat <<EOF >> .hg/hgrc
262 262 > [extensions]
263 263 > emptysource = `pwd`/emptysource.py
264 264 > EOF
265 265
266 266 $ hg config --debug empty.source
267 267 read config from: * (glob)
268 268 none: value
269 269 $ hg config empty.source -Tjson
270 270 [
271 271 {
272 272 "defaultvalue": null,
273 273 "name": "empty.source",
274 274 "source": "",
275 275 "value": "value"
276 276 }
277 277 ]
278 278
279 279 $ cp .hg/hgrc.orig .hg/hgrc
280 280
281 281 Test "%unset"
282 282
283 283 $ cat >> $HGRCPATH <<EOF
284 284 > [unsettest]
285 285 > local-hgrcpath = should be unset (HGRCPATH)
286 286 > %unset local-hgrcpath
287 287 >
288 288 > global = should be unset (HGRCPATH)
289 289 >
290 290 > both = should be unset (HGRCPATH)
291 291 >
292 292 > set-after-unset = should be unset (HGRCPATH)
293 293 > EOF
294 294
295 295 $ cat >> .hg/hgrc <<EOF
296 296 > [unsettest]
297 297 > local-hgrc = should be unset (.hg/hgrc)
298 298 > %unset local-hgrc
299 299 >
300 300 > %unset global
301 301 >
302 302 > both = should be unset (.hg/hgrc)
303 303 > %unset both
304 304 >
305 305 > set-after-unset = should be unset (.hg/hgrc)
306 306 > %unset set-after-unset
307 307 > set-after-unset = should be set (.hg/hgrc)
308 308 > EOF
309 309
310 310 $ hg showconfig unsettest
311 311 unsettest.set-after-unset=should be set (.hg/hgrc)
312 312
313 313 Test exit code when no config matches
314 314
315 315 $ hg config Section.idontexist
316 316 [1]
317 317
318 318 sub-options in [paths] aren't expanded
319 319
320 320 $ cat > .hg/hgrc << EOF
321 321 > [paths]
322 322 > foo = ~/foo
323 323 > foo:suboption = ~/foo
324 324 > EOF
325 325
326 326 $ hg showconfig paths
327 327 paths.foo:suboption=~/foo
328 328 paths.foo=$TESTTMP/foo
329 329
330 330 edit failure
331 331
332 332 $ HGEDITOR=false hg config --edit
333 333 abort: edit failed: false exited with status 1
334 334 [10]
335 335
336 336 config affected by environment variables
337 337
338 338 $ EDITOR=e1 VISUAL=e2 hg config --debug | grep 'ui\.editor'
339 339 $VISUAL: ui.editor=e2
340 340
341 341 $ VISUAL=e2 hg config --debug --config ui.editor=e3 | grep 'ui\.editor'
342 342 --config: ui.editor=e3
343 343
344 344 $ PAGER=p1 hg config --debug | grep 'pager\.pager'
345 345 $PAGER: pager.pager=p1
346 346
347 347 $ PAGER=p1 hg config --debug --config pager.pager=p2 | grep 'pager\.pager'
348 348 --config: pager.pager=p2
349 349
350 350 verify that aliases are evaluated as well
351 351
352 352 $ hg init aliastest
353 353 $ cd aliastest
354 354 $ cat > .hg/hgrc << EOF
355 355 > [ui]
356 356 > user = repo user
357 357 > EOF
358 358 $ touch index
359 359 $ unset HGUSER
360 360 $ hg ci -Am test
361 361 adding index
362 362 $ hg log --template '{author}\n'
363 363 repo user
364 364 $ cd ..
365 365
366 366 alias has lower priority
367 367
368 368 $ hg init aliaspriority
369 369 $ cd aliaspriority
370 370 $ cat > .hg/hgrc << EOF
371 371 > [ui]
372 372 > user = alias user
373 373 > username = repo user
374 374 > EOF
375 375 $ touch index
376 376 $ unset HGUSER
377 377 $ hg ci -Am test
378 378 adding index
379 379 $ hg log --template '{author}\n'
380 380 repo user
381 381 $ cd ..
382 382
383 383 configs should be read in lexicographical order
384 384
385 385 $ mkdir configs
386 386 $ for i in `$TESTDIR/seq.py 10 99`; do
387 387 > printf "[section]\nkey=$i" > configs/$i.rc
388 388 > done
389 389 $ HGRCPATH=configs hg config section.key
390 390 99
@@ -1,1169 +1,1169 b''
1 1 #require git
2 2
3 3 $ git config -f $HOME/.gitconfig init.defaultBranch master
4 4 $ git config -f $HOME/.gitconfig core.autocrlf false
5 5 $ echo "[extensions]" >> $HGRCPATH
6 6 $ echo "convert=" >> $HGRCPATH
7 7 $ cat >> $HGRCPATH <<EOF
8 8 > [subrepos]
9 9 > git:allowed = true
10 10 > EOF
11 11 $ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
12 12 $ GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL
13 13 $ GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0000"; export GIT_AUTHOR_DATE
14 14 $ GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME
15 15 $ GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL
16 16 $ GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE
17 17 $ INVALIDID1=afd12345af
18 18 $ INVALIDID2=28173x36ddd1e67bf7098d541130558ef5534a86
19 19 $ VALIDID1=39b3d83f9a69a9ba4ebb111461071a0af0027357
20 20 $ VALIDID2=8dd6476bd09d9c7776355dc454dafe38efaec5da
21 21 $ count=10
22 22 $ commit()
23 23 > {
24 24 > GIT_AUTHOR_DATE="2007-01-01 00:00:$count +0000"
25 25 > GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
26 26 > git commit "$@" >/dev/null 2>/dev/null || echo "git commit error"
27 27 > count=`expr $count + 1`
28 28 > }
29 29 $ mkdir git-repo
30 30 $ cd git-repo
31 31 $ git init >/dev/null 2>/dev/null
32 32 $ echo a > a
33 33 $ mkdir d
34 34 $ echo b > d/b
35 35 $ git add a d
36 36 $ commit -a -m t1
37 37
38 38 Remove the directory, then try to replace it with a file (issue754)
39 39
40 40 $ git rm -f d/b
41 41 rm 'd/b'
42 42 $ commit -m t2
43 43 $ echo d > d
44 44 $ git add d
45 45 $ commit -m t3
46 46 $ echo b >> a
47 47 $ commit -a -m t4.1
48 48 $ git checkout -b other HEAD~ >/dev/null 2>/dev/null
49 49 $ echo c > a
50 50 $ echo a >> a
51 51 $ commit -a -m t4.2
52 52 $ git checkout master >/dev/null 2>/dev/null
53 53 $ git pull --no-commit . other > /dev/null 2>/dev/null
54 54 $ commit -m 'Merge branch other'
55 55 $ cd ..
56 56 $ hg convert --config extensions.progress= --config progress.assume-tty=1 \
57 57 > --config progress.delay=0 --config progress.changedelay=0 \
58 58 > --config progress.refresh=0 --config progress.width=60 \
59 59 > --config progress.format='topic, bar, number' --datesort git-repo
60 60 \r (no-eol) (esc)
61 61 scanning [======> ] 1/6\r (no-eol) (esc)
62 62 scanning [=============> ] 2/6\r (no-eol) (esc)
63 63 scanning [=====================> ] 3/6\r (no-eol) (esc)
64 64 scanning [============================> ] 4/6\r (no-eol) (esc)
65 65 scanning [===================================> ] 5/6\r (no-eol) (esc)
66 66 scanning [===========================================>] 6/6\r (no-eol) (esc)
67 67 \r (no-eol) (esc)
68 68 \r (no-eol) (esc)
69 69 converting [ ] 0/6\r (no-eol) (esc)
70 70 getting files [==================> ] 1/2\r (no-eol) (esc)
71 71 getting files [======================================>] 2/2\r (no-eol) (esc)
72 72 \r (no-eol) (esc)
73 73 \r (no-eol) (esc)
74 74 converting [======> ] 1/6\r (no-eol) (esc)
75 75 getting files [======================================>] 1/1\r (no-eol) (esc)
76 76 \r (no-eol) (esc)
77 77 \r (no-eol) (esc)
78 78 converting [=============> ] 2/6\r (no-eol) (esc)
79 79 getting files [======================================>] 1/1\r (no-eol) (esc)
80 80 \r (no-eol) (esc)
81 81 \r (no-eol) (esc)
82 82 converting [====================> ] 3/6\r (no-eol) (esc)
83 83 getting files [======================================>] 1/1\r (no-eol) (esc)
84 84 \r (no-eol) (esc)
85 85 \r (no-eol) (esc)
86 86 converting [===========================> ] 4/6\r (no-eol) (esc)
87 87 getting files [======================================>] 1/1\r (no-eol) (esc)
88 88 \r (no-eol) (esc)
89 89 \r (no-eol) (esc)
90 90 converting [==================================> ] 5/6\r (no-eol) (esc)
91 91 getting files [======================================>] 1/1\r (no-eol) (esc)
92 92 \r (no-eol) (esc)
93 93 assuming destination git-repo-hg
94 94 initializing destination git-repo-hg repository
95 95 scanning source...
96 96 sorting...
97 97 converting...
98 98 5 t1
99 99 4 t2
100 100 3 t3
101 101 2 t4.1
102 102 1 t4.2
103 103 0 Merge branch other
104 104 updating bookmarks
105 105 $ hg up -q -R git-repo-hg
106 106 $ hg -R git-repo-hg tip -v
107 107 changeset: 5:c78094926be2
108 108 bookmark: master
109 109 tag: tip
110 110 parent: 3:f5f5cb45432b
111 111 parent: 4:4e174f80c67c
112 112 user: test <test@example.org>
113 113 date: Mon Jan 01 00:00:15 2007 +0000
114 114 files: a
115 115 description:
116 116 Merge branch other
117 117
118 118
119 119 $ count=10
120 120 $ mkdir git-repo2
121 121 $ cd git-repo2
122 122 $ git init >/dev/null 2>/dev/null
123 123 $ echo foo > foo
124 124 $ git add foo
125 125 $ commit -a -m 'add foo'
126 126 $ echo >> foo
127 127 $ commit -a -m 'change foo'
128 128 $ git checkout -b Bar HEAD~ >/dev/null 2>/dev/null
129 129 $ echo quux >> quux
130 130 $ git add quux
131 131 $ commit -a -m 'add quux'
132 132 $ echo bar > bar
133 133 $ git add bar
134 134 $ commit -a -m 'add bar'
135 135 $ git checkout -b Baz HEAD~ >/dev/null 2>/dev/null
136 136 $ echo baz > baz
137 137 $ git add baz
138 138 $ commit -a -m 'add baz'
139 139 $ git checkout master >/dev/null 2>/dev/null
140 140 $ git pull --no-commit . Bar Baz > /dev/null 2>/dev/null
141 141 $ commit -m 'Octopus merge'
142 142 $ echo bar >> bar
143 143 $ commit -a -m 'change bar'
144 144 $ git checkout -b Foo HEAD~ >/dev/null 2>/dev/null
145 145 $ echo >> foo
146 146 $ commit -a -m 'change foo'
147 147 $ git checkout master >/dev/null 2>/dev/null
148 148 $ git pull --no-commit -s ours . Foo > /dev/null 2>/dev/null
149 149 $ commit -m 'Discard change to foo'
150 150 $ cd ..
151 151 $ glog()
152 152 > {
153 153 > hg log -G --template '{rev} "{desc|firstline}" files: {files}\n' "$@"
154 154 > }
155 155 $ splitrepo()
156 156 > {
157 157 > msg="$1"
158 158 > files="$2"
159 159 > opts=$3
160 160 > echo "% $files: $msg"
161 161 > prefix=`echo "$files" | sed -e 's/ /-/g'`
162 162 > fmap="$prefix.fmap"
163 163 > repo="$prefix.repo"
164 164 > for i in $files; do
165 165 > echo "include $i" >> "$fmap"
166 166 > done
167 167 > hg -q convert $opts --filemap "$fmap" --datesort git-repo2 "$repo"
168 168 > hg up -q -R "$repo"
169 169 > glog -R "$repo"
170 170 > hg -R "$repo" manifest --debug
171 171 > }
172 172
173 173 full conversion
174 174
175 175 $ hg convert --datesort git-repo2 fullrepo \
176 176 > --config extensions.progress= --config progress.assume-tty=1 \
177 177 > --config progress.delay=0 --config progress.changedelay=0 \
178 178 > --config progress.refresh=0 --config progress.width=60 \
179 179 > --config progress.format='topic, bar, number'
180 180 \r (no-eol) (esc)
181 181 scanning [===> ] 1/9\r (no-eol) (esc)
182 182 scanning [========> ] 2/9\r (no-eol) (esc)
183 183 scanning [=============> ] 3/9\r (no-eol) (esc)
184 184 scanning [==================> ] 4/9\r (no-eol) (esc)
185 185 scanning [=======================> ] 5/9\r (no-eol) (esc)
186 186 scanning [============================> ] 6/9\r (no-eol) (esc)
187 187 scanning [=================================> ] 7/9\r (no-eol) (esc)
188 188 scanning [======================================> ] 8/9\r (no-eol) (esc)
189 189 scanning [===========================================>] 9/9\r (no-eol) (esc)
190 190 \r (no-eol) (esc)
191 191 \r (no-eol) (esc)
192 192 converting [ ] 0/9\r (no-eol) (esc)
193 193 getting files [======================================>] 1/1\r (no-eol) (esc)
194 194 \r (no-eol) (esc)
195 195 \r (no-eol) (esc)
196 196 converting [===> ] 1/9\r (no-eol) (esc)
197 197 getting files [======================================>] 1/1\r (no-eol) (esc)
198 198 \r (no-eol) (esc)
199 199 \r (no-eol) (esc)
200 200 converting [========> ] 2/9\r (no-eol) (esc)
201 201 getting files [======================================>] 1/1\r (no-eol) (esc)
202 202 \r (no-eol) (esc)
203 203 \r (no-eol) (esc)
204 204 converting [=============> ] 3/9\r (no-eol) (esc)
205 205 getting files [======================================>] 1/1\r (no-eol) (esc)
206 206 \r (no-eol) (esc)
207 207 \r (no-eol) (esc)
208 208 converting [=================> ] 4/9\r (no-eol) (esc)
209 209 getting files [======================================>] 1/1\r (no-eol) (esc)
210 210 \r (no-eol) (esc)
211 211 \r (no-eol) (esc)
212 212 converting [======================> ] 5/9\r (no-eol) (esc)
213 213 getting files [===> ] 1/8\r (no-eol) (esc)
214 214 getting files [========> ] 2/8\r (no-eol) (esc)
215 215 getting files [=============> ] 3/8\r (no-eol) (esc)
216 216 getting files [==================> ] 4/8\r (no-eol) (esc)
217 217 getting files [=======================> ] 5/8\r (no-eol) (esc)
218 218 getting files [============================> ] 6/8\r (no-eol) (esc)
219 219 getting files [=================================> ] 7/8\r (no-eol) (esc)
220 220 getting files [======================================>] 8/8\r (no-eol) (esc)
221 221 \r (no-eol) (esc)
222 222 \r (no-eol) (esc)
223 223 converting [===========================> ] 6/9\r (no-eol) (esc)
224 224 getting files [======================================>] 1/1\r (no-eol) (esc)
225 225 \r (no-eol) (esc)
226 226 \r (no-eol) (esc)
227 227 converting [===============================> ] 7/9\r (no-eol) (esc)
228 228 getting files [======================================>] 1/1\r (no-eol) (esc)
229 229 \r (no-eol) (esc)
230 230 \r (no-eol) (esc)
231 231 converting [====================================> ] 8/9\r (no-eol) (esc)
232 232 getting files [==================> ] 1/2\r (no-eol) (esc)
233 233 getting files [======================================>] 2/2\r (no-eol) (esc)
234 234 \r (no-eol) (esc)
235 235 initializing destination fullrepo repository
236 236 scanning source...
237 237 sorting...
238 238 converting...
239 239 8 add foo
240 240 7 change foo
241 241 6 add quux
242 242 5 add bar
243 243 4 add baz
244 244 3 Octopus merge
245 245 2 change bar
246 246 1 change foo
247 247 0 Discard change to foo
248 248 updating bookmarks
249 249 $ hg up -q -R fullrepo
250 250 $ glog -R fullrepo
251 251 @ 9 "Discard change to foo" files: foo
252 252 |\
253 253 | o 8 "change foo" files: foo
254 254 | |
255 255 o | 7 "change bar" files: bar
256 256 |/
257 257 o 6 "(octopus merge fixup)" files:
258 258 |\
259 259 | o 5 "Octopus merge" files: baz
260 260 | |\
261 261 o | | 4 "add baz" files: baz
262 262 | | |
263 263 +---o 3 "add bar" files: bar
264 264 | |
265 265 o | 2 "add quux" files: quux
266 266 | |
267 267 | o 1 "change foo" files: foo
268 268 |/
269 269 o 0 "add foo" files: foo
270 270
271 271 $ hg -R fullrepo manifest --debug
272 272 245a3b8bc653999c2b22cdabd517ccb47aecafdf 644 bar
273 273 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
274 274 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
275 275 88dfeab657e8cf2cef3dec67b914f49791ae76b1 644 quux
276 276 $ splitrepo 'octopus merge' 'foo bar baz'
277 277 % foo bar baz: octopus merge
278 278 @ 8 "Discard change to foo" files: foo
279 279 |\
280 280 | o 7 "change foo" files: foo
281 281 | |
282 282 o | 6 "change bar" files: bar
283 283 |/
284 284 o 5 "(octopus merge fixup)" files:
285 285 |\
286 286 | o 4 "Octopus merge" files: baz
287 287 | |\
288 288 o | | 3 "add baz" files: baz
289 289 | | |
290 290 +---o 2 "add bar" files: bar
291 291 | |
292 292 | o 1 "change foo" files: foo
293 293 |/
294 294 o 0 "add foo" files: foo
295 295
296 296 245a3b8bc653999c2b22cdabd517ccb47aecafdf 644 bar
297 297 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
298 298 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
299 299 $ splitrepo 'only some parents of an octopus merge; "discard" a head' 'foo baz quux'
300 300 % foo baz quux: only some parents of an octopus merge; "discard" a head
301 301 @ 6 "Discard change to foo" files: foo
302 302 |
303 303 o 5 "change foo" files: foo
304 304 |
305 305 o 4 "Octopus merge" files:
306 306 |\
307 307 | o 3 "add baz" files: baz
308 308 | |
309 309 | o 2 "add quux" files: quux
310 310 | |
311 311 o | 1 "change foo" files: foo
312 312 |/
313 313 o 0 "add foo" files: foo
314 314
315 315 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
316 316 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
317 317 88dfeab657e8cf2cef3dec67b914f49791ae76b1 644 quux
318 318
319 319 test importing git renames and copies
320 320
321 321 $ cd git-repo2
322 322 $ git mv foo foo-renamed
323 323 since bar is not touched in this commit, this copy will not be detected
324 324 $ cp bar bar-copied
325 325 $ cp baz baz-copied
326 326 $ cp baz baz-copied2
327 327 $ cp baz ba-copy
328 328 $ echo baz2 >> baz
329 329 $ git add bar-copied baz-copied baz-copied2 ba-copy
330 330 $ commit -a -m 'rename and copy'
331 331 $ cd ..
332 332
333 333 input validation
334 334 $ hg convert --config convert.git.similarity=foo --datesort git-repo2 fullrepo
335 abort: convert.git.similarity is not a valid integer ('foo')
335 config error: convert.git.similarity is not a valid integer ('foo')
336 336 [30]
337 337 $ hg convert --config convert.git.similarity=-1 --datesort git-repo2 fullrepo
338 338 abort: similarity must be between 0 and 100
339 339 [255]
340 340 $ hg convert --config convert.git.similarity=101 --datesort git-repo2 fullrepo
341 341 abort: similarity must be between 0 and 100
342 342 [255]
343 343
344 344 $ hg -q convert --config convert.git.similarity=100 --datesort git-repo2 fullrepo
345 345 $ hg -R fullrepo status -C --change master
346 346 M baz
347 347 A ba-copy
348 348 baz
349 349 A bar-copied
350 350 A baz-copied
351 351 baz
352 352 A baz-copied2
353 353 baz
354 354 A foo-renamed
355 355 foo
356 356 R foo
357 357
358 358 Ensure that the modification to the copy source was preserved
359 359 (there was a bug where if the copy dest was alphabetically prior to the copy
360 360 source, the copy source took the contents of the copy dest)
361 361 $ hg cat -r tip fullrepo/baz
362 362 baz
363 363 baz2
364 364
365 365 $ cd git-repo2
366 366 $ echo bar2 >> bar
367 367 $ commit -a -m 'change bar'
368 368 $ cp bar bar-copied2
369 369 $ git add bar-copied2
370 370 $ commit -a -m 'copy with no changes'
371 371 $ cd ..
372 372
373 373 $ hg -q convert --config convert.git.similarity=100 \
374 374 > --config convert.git.findcopiesharder=1 --datesort git-repo2 fullrepo
375 375 $ hg -R fullrepo status -C --change master
376 376 A bar-copied2
377 377 bar
378 378
379 379 renamelimit config option works
380 380
381 381 $ cd git-repo2
382 382 $ cat >> copy-source << EOF
383 383 > sc0
384 384 > sc1
385 385 > sc2
386 386 > sc3
387 387 > sc4
388 388 > sc5
389 389 > sc6
390 390 > EOF
391 391 $ git add copy-source
392 392 $ commit -m 'add copy-source'
393 393 $ cp copy-source source-copy0
394 394 $ echo 0 >> source-copy0
395 395 $ cp copy-source source-copy1
396 396 $ echo 1 >> source-copy1
397 397 $ git add source-copy0 source-copy1
398 398 $ commit -a -m 'copy copy-source 2 times'
399 399 $ cd ..
400 400
401 401 $ hg -q convert --config convert.git.renamelimit=1 \
402 402 > --config convert.git.findcopiesharder=true --datesort git-repo2 fullrepo2
403 403 $ hg -R fullrepo2 status -C --change master
404 404 A source-copy0
405 405 A source-copy1
406 406
407 407 $ hg -q convert --config convert.git.renamelimit=100 \
408 408 > --config convert.git.findcopiesharder=true --datesort git-repo2 fullrepo3
409 409 $ hg -R fullrepo3 status -C --change master
410 410 A source-copy0
411 411 copy-source
412 412 A source-copy1
413 413 copy-source
414 414
415 415 test binary conversion (issue1359)
416 416
417 417 $ count=19
418 418 $ mkdir git-repo3
419 419 $ cd git-repo3
420 420 $ git init >/dev/null 2>/dev/null
421 421 $ "$PYTHON" -c 'import struct; open("b", "wb").write(b"".join([struct.Struct(">B").pack(i) for i in range(256)])*16)'
422 422 $ git add b
423 423 $ commit -a -m addbinary
424 424 $ cd ..
425 425
426 426 convert binary file
427 427
428 428 $ hg convert git-repo3 git-repo3-hg
429 429 initializing destination git-repo3-hg repository
430 430 scanning source...
431 431 sorting...
432 432 converting...
433 433 0 addbinary
434 434 updating bookmarks
435 435 $ cd git-repo3-hg
436 436 $ hg up -C
437 437 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
438 438 $ "$PYTHON" -c 'from __future__ import print_function; print(len(open("b", "rb").read()))'
439 439 4096
440 440 $ cd ..
441 441
442 442 test author vs committer
443 443
444 444 $ mkdir git-repo4
445 445 $ cd git-repo4
446 446 $ git init >/dev/null 2>/dev/null
447 447 $ echo >> foo
448 448 $ git add foo
449 449 $ commit -a -m addfoo
450 450 $ echo >> foo
451 451 $ GIT_AUTHOR_NAME="nottest"
452 452 $ commit -a -m addfoo2
453 453 $ cd ..
454 454
455 455 convert author committer
456 456
457 457 $ hg convert git-repo4 git-repo4-hg
458 458 initializing destination git-repo4-hg repository
459 459 scanning source...
460 460 sorting...
461 461 converting...
462 462 1 addfoo
463 463 0 addfoo2
464 464 updating bookmarks
465 465 $ hg -R git-repo4-hg log -v
466 466 changeset: 1:d63e967f93da
467 467 bookmark: master
468 468 tag: tip
469 469 user: nottest <test@example.org>
470 470 date: Mon Jan 01 00:00:21 2007 +0000
471 471 files: foo
472 472 description:
473 473 addfoo2
474 474
475 475 committer: test <test@example.org>
476 476
477 477
478 478 changeset: 0:0735477b0224
479 479 user: test <test@example.org>
480 480 date: Mon Jan 01 00:00:20 2007 +0000
481 481 files: foo
482 482 description:
483 483 addfoo
484 484
485 485
486 486
487 487 Various combinations of committeractions fail
488 488
489 489 $ hg --config convert.git.committeractions=messagedifferent,messagealways convert git-repo4 bad-committer
490 490 initializing destination bad-committer repository
491 491 abort: committeractions cannot define both messagedifferent and messagealways
492 492 [255]
493 493
494 494 $ hg --config convert.git.committeractions=dropcommitter,replaceauthor convert git-repo4 bad-committer
495 495 initializing destination bad-committer repository
496 496 abort: committeractions cannot define both dropcommitter and replaceauthor
497 497 [255]
498 498
499 499 $ hg --config convert.git.committeractions=dropcommitter,messagealways convert git-repo4 bad-committer
500 500 initializing destination bad-committer repository
501 501 abort: committeractions cannot define both dropcommitter and messagealways
502 502 [255]
503 503
504 504 custom prefix on messagedifferent works
505 505
506 506 $ hg --config convert.git.committeractions=messagedifferent=different: convert git-repo4 git-repo4-hg-messagedifferentprefix
507 507 initializing destination git-repo4-hg-messagedifferentprefix repository
508 508 scanning source...
509 509 sorting...
510 510 converting...
511 511 1 addfoo
512 512 0 addfoo2
513 513 updating bookmarks
514 514
515 515 $ hg -R git-repo4-hg-messagedifferentprefix log -v
516 516 changeset: 1:2fe0c98a109d
517 517 bookmark: master
518 518 tag: tip
519 519 user: nottest <test@example.org>
520 520 date: Mon Jan 01 00:00:21 2007 +0000
521 521 files: foo
522 522 description:
523 523 addfoo2
524 524
525 525 different: test <test@example.org>
526 526
527 527
528 528 changeset: 0:0735477b0224
529 529 user: test <test@example.org>
530 530 date: Mon Jan 01 00:00:20 2007 +0000
531 531 files: foo
532 532 description:
533 533 addfoo
534 534
535 535
536 536
537 537 messagealways will always add the "committer: " line even if committer identical
538 538
539 539 $ hg --config convert.git.committeractions=messagealways convert git-repo4 git-repo4-hg-messagealways
540 540 initializing destination git-repo4-hg-messagealways repository
541 541 scanning source...
542 542 sorting...
543 543 converting...
544 544 1 addfoo
545 545 0 addfoo2
546 546 updating bookmarks
547 547
548 548 $ hg -R git-repo4-hg-messagealways log -v
549 549 changeset: 1:8db057d8cd37
550 550 bookmark: master
551 551 tag: tip
552 552 user: nottest <test@example.org>
553 553 date: Mon Jan 01 00:00:21 2007 +0000
554 554 files: foo
555 555 description:
556 556 addfoo2
557 557
558 558 committer: test <test@example.org>
559 559
560 560
561 561 changeset: 0:8f71fe9c98be
562 562 user: test <test@example.org>
563 563 date: Mon Jan 01 00:00:20 2007 +0000
564 564 files: foo
565 565 description:
566 566 addfoo
567 567
568 568 committer: test <test@example.org>
569 569
570 570
571 571
572 572 custom prefix on messagealways works
573 573
574 574 $ hg --config convert.git.committeractions=messagealways=always: convert git-repo4 git-repo4-hg-messagealwaysprefix
575 575 initializing destination git-repo4-hg-messagealwaysprefix repository
576 576 scanning source...
577 577 sorting...
578 578 converting...
579 579 1 addfoo
580 580 0 addfoo2
581 581 updating bookmarks
582 582
583 583 $ hg -R git-repo4-hg-messagealwaysprefix log -v
584 584 changeset: 1:83c17174de79
585 585 bookmark: master
586 586 tag: tip
587 587 user: nottest <test@example.org>
588 588 date: Mon Jan 01 00:00:21 2007 +0000
589 589 files: foo
590 590 description:
591 591 addfoo2
592 592
593 593 always: test <test@example.org>
594 594
595 595
596 596 changeset: 0:2ac9bcb3534a
597 597 user: test <test@example.org>
598 598 date: Mon Jan 01 00:00:20 2007 +0000
599 599 files: foo
600 600 description:
601 601 addfoo
602 602
603 603 always: test <test@example.org>
604 604
605 605
606 606
607 607 replaceauthor replaces author with committer
608 608
609 609 $ hg --config convert.git.committeractions=replaceauthor convert git-repo4 git-repo4-hg-replaceauthor
610 610 initializing destination git-repo4-hg-replaceauthor repository
611 611 scanning source...
612 612 sorting...
613 613 converting...
614 614 1 addfoo
615 615 0 addfoo2
616 616 updating bookmarks
617 617
618 618 $ hg -R git-repo4-hg-replaceauthor log -v
619 619 changeset: 1:122c1d8999ea
620 620 bookmark: master
621 621 tag: tip
622 622 user: test <test@example.org>
623 623 date: Mon Jan 01 00:00:21 2007 +0000
624 624 files: foo
625 625 description:
626 626 addfoo2
627 627
628 628
629 629 changeset: 0:0735477b0224
630 630 user: test <test@example.org>
631 631 date: Mon Jan 01 00:00:20 2007 +0000
632 632 files: foo
633 633 description:
634 634 addfoo
635 635
636 636
637 637
638 638 dropcommitter removes the committer
639 639
640 640 $ hg --config convert.git.committeractions=dropcommitter convert git-repo4 git-repo4-hg-dropcommitter
641 641 initializing destination git-repo4-hg-dropcommitter repository
642 642 scanning source...
643 643 sorting...
644 644 converting...
645 645 1 addfoo
646 646 0 addfoo2
647 647 updating bookmarks
648 648
649 649 $ hg -R git-repo4-hg-dropcommitter log -v
650 650 changeset: 1:190b2da396cc
651 651 bookmark: master
652 652 tag: tip
653 653 user: nottest <test@example.org>
654 654 date: Mon Jan 01 00:00:21 2007 +0000
655 655 files: foo
656 656 description:
657 657 addfoo2
658 658
659 659
660 660 changeset: 0:0735477b0224
661 661 user: test <test@example.org>
662 662 date: Mon Jan 01 00:00:20 2007 +0000
663 663 files: foo
664 664 description:
665 665 addfoo
666 666
667 667
668 668
669 669 --sourceorder should fail
670 670
671 671 $ hg convert --sourcesort git-repo4 git-repo4-sourcesort-hg
672 672 initializing destination git-repo4-sourcesort-hg repository
673 673 abort: --sourcesort is not supported by this data source
674 674 [255]
675 675
676 676 test converting certain branches
677 677
678 678 $ mkdir git-testrevs
679 679 $ cd git-testrevs
680 680 $ git init
681 681 Initialized empty Git repository in $TESTTMP/git-testrevs/.git/
682 682 $ echo a >> a ; git add a > /dev/null; git commit -m 'first' > /dev/null
683 683 $ echo a >> a ; git add a > /dev/null; git commit -m 'master commit' > /dev/null
684 684 $ git checkout -b goodbranch 'HEAD^'
685 685 Switched to a new branch 'goodbranch'
686 686 $ echo a >> b ; git add b > /dev/null; git commit -m 'good branch commit' > /dev/null
687 687 $ git checkout -b badbranch 'HEAD^'
688 688 Switched to a new branch 'badbranch'
689 689 $ echo a >> c ; git add c > /dev/null; git commit -m 'bad branch commit' > /dev/null
690 690 $ cd ..
691 691 $ hg convert git-testrevs hg-testrevs --rev master --rev goodbranch
692 692 initializing destination hg-testrevs repository
693 693 scanning source...
694 694 sorting...
695 695 converting...
696 696 2 first
697 697 1 good branch commit
698 698 0 master commit
699 699 updating bookmarks
700 700 $ cd hg-testrevs
701 701 $ hg log -G -T '{rev} {bookmarks}'
702 702 o 2 master
703 703 |
704 704 | o 1 goodbranch
705 705 |/
706 706 o 0
707 707
708 708 $ cd ..
709 709
710 710 test sub modules
711 711
712 712 $ mkdir git-repo5
713 713 $ cd git-repo5
714 714 $ git init >/dev/null 2>/dev/null
715 715 $ echo 'sub' >> foo
716 716 $ git add foo
717 717 $ commit -a -m 'addfoo'
718 718 $ BASE=`pwd`
719 719 $ cd ..
720 720 $ mkdir git-repo6
721 721 $ cd git-repo6
722 722 $ git init >/dev/null 2>/dev/null
723 723 $ git submodule add ${BASE} >/dev/null 2>/dev/null
724 724 $ commit -a -m 'addsubmodule' >/dev/null 2>/dev/null
725 725
726 726 test non-tab whitespace .gitmodules
727 727
728 728 $ cat >> .gitmodules <<EOF
729 729 > [submodule "git-repo5"]
730 730 > path = git-repo5
731 731 > url = git-repo5
732 732 > EOF
733 733 $ git commit -q -a -m "weird white space submodule"
734 734 $ cd ..
735 735 $ hg convert git-repo6 hg-repo6
736 736 initializing destination hg-repo6 repository
737 737 scanning source...
738 738 sorting...
739 739 converting...
740 740 1 addsubmodule
741 741 0 weird white space submodule
742 742 updating bookmarks
743 743
744 744 $ rm -rf hg-repo6
745 745 $ cd git-repo6
746 746 $ git reset --hard 'HEAD^' > /dev/null
747 747
748 748 test missing .gitmodules
749 749
750 750 $ git submodule add ../git-repo4 >/dev/null 2>/dev/null
751 751 $ git checkout HEAD -- .gitmodules
752 752 $ git rm .gitmodules
753 753 rm '.gitmodules'
754 754 $ git commit -q -m "remove .gitmodules" .gitmodules
755 755 $ git commit -q -m "missing .gitmodules"
756 756 $ cd ..
757 757 $ hg convert git-repo6 hg-repo6 --traceback 2>&1 | grep -v "fatal: Path '.gitmodules' does not exist"
758 758 initializing destination hg-repo6 repository
759 759 scanning source...
760 760 sorting...
761 761 converting...
762 762 2 addsubmodule
763 763 1 remove .gitmodules
764 764 0 missing .gitmodules
765 765 warning: cannot read submodules config file in * (glob)
766 766 updating bookmarks
767 767 $ rm -rf hg-repo6
768 768 $ cd git-repo6
769 769 $ rm -rf git-repo4
770 770 $ git reset --hard 'HEAD^^' > /dev/null
771 771 $ cd ..
772 772
773 773 test invalid splicemap1
774 774
775 775 $ cat > splicemap <<EOF
776 776 > $VALIDID1
777 777 > EOF
778 778 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap1-hg
779 779 initializing destination git-repo2-splicemap1-hg repository
780 780 abort: syntax error in splicemap(1): child parent1[,parent2] expected
781 781 [255]
782 782
783 783 test invalid splicemap2
784 784
785 785 $ cat > splicemap <<EOF
786 786 > $VALIDID1 $VALIDID2, $VALIDID2, $VALIDID2
787 787 > EOF
788 788 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap2-hg
789 789 initializing destination git-repo2-splicemap2-hg repository
790 790 abort: syntax error in splicemap(1): child parent1[,parent2] expected
791 791 [255]
792 792
793 793 test invalid splicemap3
794 794
795 795 $ cat > splicemap <<EOF
796 796 > $INVALIDID1 $INVALIDID2
797 797 > EOF
798 798 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap3-hg
799 799 initializing destination git-repo2-splicemap3-hg repository
800 800 abort: splicemap entry afd12345af is not a valid revision identifier
801 801 [255]
802 802
803 803 convert sub modules
804 804 $ hg convert git-repo6 git-repo6-hg
805 805 initializing destination git-repo6-hg repository
806 806 scanning source...
807 807 sorting...
808 808 converting...
809 809 0 addsubmodule
810 810 updating bookmarks
811 811 $ hg -R git-repo6-hg log -v
812 812 changeset: 0:* (glob)
813 813 bookmark: master
814 814 tag: tip
815 815 user: nottest <test@example.org>
816 816 date: Mon Jan 01 00:00:23 2007 +0000
817 817 files: .hgsub .hgsubstate
818 818 description:
819 819 addsubmodule
820 820
821 821 committer: test <test@example.org>
822 822
823 823
824 824
825 825 $ cd git-repo6-hg
826 826 $ hg up >/dev/null 2>/dev/null
827 827 $ cat .hgsubstate
828 828 * git-repo5 (glob)
829 829 $ cd git-repo5
830 830 $ cat foo
831 831 sub
832 832
833 833 $ cd ../..
834 834
835 835 make sure rename detection doesn't break removing and adding gitmodules
836 836
837 837 $ cd git-repo6
838 838 $ git mv .gitmodules .gitmodules-renamed
839 839 $ commit -a -m 'rename .gitmodules'
840 840 $ git mv .gitmodules-renamed .gitmodules
841 841 $ commit -a -m 'rename .gitmodules back'
842 842 $ cd ..
843 843
844 844 $ hg --config convert.git.similarity=100 convert -q git-repo6 git-repo6-hg
845 845 $ hg -R git-repo6-hg log -r 'tip^' -T "{desc|firstline}\n"
846 846 rename .gitmodules
847 847 $ hg -R git-repo6-hg status -C --change 'tip^'
848 848 A .gitmodules-renamed
849 849 R .hgsub
850 850 R .hgsubstate
851 851 $ hg -R git-repo6-hg log -r tip -T "{desc|firstline}\n"
852 852 rename .gitmodules back
853 853 $ hg -R git-repo6-hg status -C --change tip
854 854 A .hgsub
855 855 A .hgsubstate
856 856 R .gitmodules-renamed
857 857
858 858 convert the revision removing '.gitmodules' itself (and related
859 859 submodules)
860 860
861 861 $ cd git-repo6
862 862 $ git rm .gitmodules
863 863 rm '.gitmodules'
864 864 $ git rm --cached git-repo5
865 865 rm 'git-repo5'
866 866 $ commit -a -m 'remove .gitmodules and submodule git-repo5'
867 867 $ cd ..
868 868
869 869 $ hg convert -q git-repo6 git-repo6-hg
870 870 $ hg -R git-repo6-hg tip -T "{desc|firstline}\n"
871 871 remove .gitmodules and submodule git-repo5
872 872 $ hg -R git-repo6-hg tip -T "{file_dels}\n"
873 873 .hgsub .hgsubstate
874 874
875 875 skip submodules in the conversion
876 876
877 877 $ hg convert -q git-repo6 no-submodules --config convert.git.skipsubmodules=True
878 878 $ hg -R no-submodules manifest --all
879 879 .gitmodules-renamed
880 880
881 881 convert using a different remote prefix
882 882 $ git init git-repo7
883 883 Initialized empty Git repository in $TESTTMP/git-repo7/.git/
884 884 $ cd git-repo7
885 885 TODO: it'd be nice to use (?) lines instead of grep -v to handle the
886 886 git output variance, but that doesn't currently work in the middle of
887 887 a block, so do this for now.
888 888 $ touch a && git add a && git commit -am "commit a" | grep -v changed
889 889 [master (root-commit) 8ae5f69] commit a
890 890 Author: nottest <test@example.org>
891 891 create mode 100644 a
892 892 $ cd ..
893 893 $ git clone git-repo7 git-repo7-client
894 894 Cloning into 'git-repo7-client'...
895 895 done.
896 896 $ hg convert --config convert.git.remoteprefix=origin git-repo7-client hg-repo7
897 897 initializing destination hg-repo7 repository
898 898 scanning source...
899 899 sorting...
900 900 converting...
901 901 0 commit a
902 902 updating bookmarks
903 903 $ hg -R hg-repo7 bookmarks
904 904 master 0:03bf38caa4c6
905 905 origin/master 0:03bf38caa4c6
906 906
907 907 Run convert when the remote branches have changed
908 908 (there was an old bug where the local convert read branches from the server)
909 909
910 910 $ cd git-repo7
911 911 $ echo a >> a
912 912 $ git commit -q -am "move master forward"
913 913 $ cd ..
914 914 $ rm -rf hg-repo7
915 915 $ hg convert --config convert.git.remoteprefix=origin git-repo7-client hg-repo7
916 916 initializing destination hg-repo7 repository
917 917 scanning source...
918 918 sorting...
919 919 converting...
920 920 0 commit a
921 921 updating bookmarks
922 922 $ hg -R hg-repo7 bookmarks
923 923 master 0:03bf38caa4c6
924 924 origin/master 0:03bf38caa4c6
925 925
926 926 damaged git repository tests:
927 927 In case the hard-coded hashes change, the following commands can be used to
928 928 list the hashes and their corresponding types in the repository:
929 929 cd git-repo4/.git/objects
930 930 find . -type f | cut -c 3- | sed 's_/__' | xargs -n 1 -t git cat-file -t
931 931 cd ../../..
932 932
933 933 damage git repository by renaming a commit object
934 934 $ COMMIT_OBJ=1c/0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd
935 935 $ mv git-repo4/.git/objects/$COMMIT_OBJ git-repo4/.git/objects/$COMMIT_OBJ.tmp
936 936 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
937 937 abort: cannot retrieve number of commits in $TESTTMP/git-repo4/.git
938 938 $ mv git-repo4/.git/objects/$COMMIT_OBJ.tmp git-repo4/.git/objects/$COMMIT_OBJ
939 939 damage git repository by renaming a blob object
940 940
941 941 $ BLOB_OBJ=8b/137891791fe96927ad78e64b0aad7bded08bdc
942 942 $ mv git-repo4/.git/objects/$BLOB_OBJ git-repo4/.git/objects/$BLOB_OBJ.tmp
943 943 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
944 944 abort: cannot read 'blob' object at 8b137891791fe96927ad78e64b0aad7bded08bdc
945 945 $ mv git-repo4/.git/objects/$BLOB_OBJ.tmp git-repo4/.git/objects/$BLOB_OBJ
946 946 damage git repository by renaming a tree object
947 947
948 948 $ TREE_OBJ=72/49f083d2a63a41cc737764a86981eb5f3e4635
949 949 $ mv git-repo4/.git/objects/$TREE_OBJ git-repo4/.git/objects/$TREE_OBJ.tmp
950 950 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
951 951 abort: cannot read changes in 1c0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd
952 952
953 953 #if no-windows git19
954 954
955 955 test for escaping the repo name (CVE-2016-3069)
956 956
957 957 $ git init '`echo pwned >COMMAND-INJECTION`'
958 958 Initialized empty Git repository in $TESTTMP/`echo pwned >COMMAND-INJECTION`/.git/
959 959 $ cd '`echo pwned >COMMAND-INJECTION`'
960 960 $ git commit -q --allow-empty -m 'empty'
961 961 $ cd ..
962 962 $ hg convert '`echo pwned >COMMAND-INJECTION`' 'converted'
963 963 initializing destination converted repository
964 964 scanning source...
965 965 sorting...
966 966 converting...
967 967 0 empty
968 968 updating bookmarks
969 969 $ test -f COMMAND-INJECTION
970 970 [1]
971 971
972 972 test for safely passing paths to git (CVE-2016-3105)
973 973
974 974 $ git init 'ext::sh -c echo% pwned% >GIT-EXT-COMMAND-INJECTION% #'
975 975 Initialized empty Git repository in $TESTTMP/ext::sh -c echo% pwned% >GIT-EXT-COMMAND-INJECTION% #/.git/
976 976 $ cd 'ext::sh -c echo% pwned% >GIT-EXT-COMMAND-INJECTION% #'
977 977 $ git commit -q --allow-empty -m 'empty'
978 978 $ cd ..
979 979 $ hg convert 'ext::sh -c echo% pwned% >GIT-EXT-COMMAND-INJECTION% #' 'converted-git-ext'
980 980 initializing destination converted-git-ext repository
981 981 scanning source...
982 982 sorting...
983 983 converting...
984 984 0 empty
985 985 updating bookmarks
986 986 $ test -f GIT-EXT-COMMAND-INJECTION
987 987 [1]
988 988
989 989 #endif
990 990
991 991 Conversion of extra commit metadata to extras works
992 992
993 993 $ git init gitextras >/dev/null 2>/dev/null
994 994 $ cd gitextras
995 995 $ touch foo
996 996 $ git add foo
997 997 $ commit -m initial
998 998 $ echo 1 > foo
999 999 $ tree=`git write-tree`
1000 1000
1001 1001 Git doesn't provider a user-facing API to write extra metadata into the
1002 1002 commit, so create the commit object by hand
1003 1003
1004 1004 $ git hash-object -t commit -w --stdin << EOF
1005 1005 > tree ${tree}
1006 1006 > parent ba6b1344e977ece9e00958dbbf17f1f09384b2c1
1007 1007 > author test <test@example.com> 1000000000 +0000
1008 1008 > committer test <test@example.com> 1000000000 +0000
1009 1009 > extra-1 extra-1
1010 1010 > extra-2 extra-2 with space
1011 1011 > convert_revision 0000aaaabbbbccccddddeeee
1012 1012 >
1013 1013 > message with extras
1014 1014 > EOF
1015 1015 8123727c8361a4117d1a2d80e0c4e7d70c757f18
1016 1016
1017 1017 $ git reset --hard 8123727c8361a4117d1a2d80e0c4e7d70c757f18 > /dev/null
1018 1018
1019 1019 $ cd ..
1020 1020
1021 1021 convert will not retain custom metadata keys by default
1022 1022
1023 1023 $ hg convert gitextras hgextras1
1024 1024 initializing destination hgextras1 repository
1025 1025 scanning source...
1026 1026 sorting...
1027 1027 converting...
1028 1028 1 initial
1029 1029 0 message with extras
1030 1030 updating bookmarks
1031 1031
1032 1032 $ hg -R hgextras1 log --debug -r 1
1033 1033 changeset: 1:e13a39880f68479127b2a80fa0b448cc8524aa09
1034 1034 bookmark: master
1035 1035 tag: tip
1036 1036 phase: draft
1037 1037 parent: 0:dcb68977c55cd02cbd13b901df65c4b6e7b9c4b9
1038 1038 parent: -1:0000000000000000000000000000000000000000
1039 1039 manifest: 0:6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50
1040 1040 user: test <test@example.com>
1041 1041 date: Sun Sep 09 01:46:40 2001 +0000
1042 1042 extra: branch=default
1043 1043 extra: convert_revision=8123727c8361a4117d1a2d80e0c4e7d70c757f18
1044 1044 description:
1045 1045 message with extras
1046 1046
1047 1047
1048 1048
1049 1049 Attempting to convert a banned extra is disallowed
1050 1050
1051 1051 $ hg convert --config convert.git.extrakeys=tree,parent gitextras hgextras-banned
1052 1052 initializing destination hgextras-banned repository
1053 1053 abort: copying of extra key is forbidden: parent, tree
1054 1054 [255]
1055 1055
1056 1056 Converting a specific extra works
1057 1057
1058 1058 $ hg convert --config convert.git.extrakeys=extra-1 gitextras hgextras2
1059 1059 initializing destination hgextras2 repository
1060 1060 scanning source...
1061 1061 sorting...
1062 1062 converting...
1063 1063 1 initial
1064 1064 0 message with extras
1065 1065 updating bookmarks
1066 1066
1067 1067 $ hg -R hgextras2 log --debug -r 1
1068 1068 changeset: 1:d40fb205d58597e6ecfd55b16f198be5bf436391
1069 1069 bookmark: master
1070 1070 tag: tip
1071 1071 phase: draft
1072 1072 parent: 0:dcb68977c55cd02cbd13b901df65c4b6e7b9c4b9
1073 1073 parent: -1:0000000000000000000000000000000000000000
1074 1074 manifest: 0:6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50
1075 1075 user: test <test@example.com>
1076 1076 date: Sun Sep 09 01:46:40 2001 +0000
1077 1077 extra: branch=default
1078 1078 extra: convert_revision=8123727c8361a4117d1a2d80e0c4e7d70c757f18
1079 1079 extra: extra-1=extra-1
1080 1080 description:
1081 1081 message with extras
1082 1082
1083 1083
1084 1084
1085 1085 Converting multiple extras works
1086 1086
1087 1087 $ hg convert --config convert.git.extrakeys=extra-1,extra-2 gitextras hgextras3
1088 1088 initializing destination hgextras3 repository
1089 1089 scanning source...
1090 1090 sorting...
1091 1091 converting...
1092 1092 1 initial
1093 1093 0 message with extras
1094 1094 updating bookmarks
1095 1095
1096 1096 $ hg -R hgextras3 log --debug -r 1
1097 1097 changeset: 1:0105af33379e7b6491501fd34141b7af700fe125
1098 1098 bookmark: master
1099 1099 tag: tip
1100 1100 phase: draft
1101 1101 parent: 0:dcb68977c55cd02cbd13b901df65c4b6e7b9c4b9
1102 1102 parent: -1:0000000000000000000000000000000000000000
1103 1103 manifest: 0:6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50
1104 1104 user: test <test@example.com>
1105 1105 date: Sun Sep 09 01:46:40 2001 +0000
1106 1106 extra: branch=default
1107 1107 extra: convert_revision=8123727c8361a4117d1a2d80e0c4e7d70c757f18
1108 1108 extra: extra-1=extra-1
1109 1109 extra: extra-2=extra-2 with space
1110 1110 description:
1111 1111 message with extras
1112 1112
1113 1113
1114 1114
1115 1115 convert.git.saverev can be disabled to prevent convert_revision from being written
1116 1116
1117 1117 $ hg convert --config convert.git.saverev=false gitextras hgextras4
1118 1118 initializing destination hgextras4 repository
1119 1119 scanning source...
1120 1120 sorting...
1121 1121 converting...
1122 1122 1 initial
1123 1123 0 message with extras
1124 1124 updating bookmarks
1125 1125
1126 1126 $ hg -R hgextras4 log --debug -r 1
1127 1127 changeset: 1:1dcaf4ffe5bee43fa86db2800821f6f0af212c5c
1128 1128 bookmark: master
1129 1129 tag: tip
1130 1130 phase: draft
1131 1131 parent: 0:a13935fec4daf06a5a87a7307ccb0fc94f98d06d
1132 1132 parent: -1:0000000000000000000000000000000000000000
1133 1133 manifest: 0:6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50
1134 1134 user: test <test@example.com>
1135 1135 date: Sun Sep 09 01:46:40 2001 +0000
1136 1136 extra: branch=default
1137 1137 description:
1138 1138 message with extras
1139 1139
1140 1140
1141 1141
1142 1142 convert.git.saverev and convert.git.extrakeys can be combined to preserve
1143 1143 convert_revision from source
1144 1144
1145 1145 $ hg convert --config convert.git.saverev=false --config convert.git.extrakeys=convert_revision gitextras hgextras5
1146 1146 initializing destination hgextras5 repository
1147 1147 scanning source...
1148 1148 sorting...
1149 1149 converting...
1150 1150 1 initial
1151 1151 0 message with extras
1152 1152 updating bookmarks
1153 1153
1154 1154 $ hg -R hgextras5 log --debug -r 1
1155 1155 changeset: 1:574d85931544d4542007664fee3747360e85ee28
1156 1156 bookmark: master
1157 1157 tag: tip
1158 1158 phase: draft
1159 1159 parent: 0:a13935fec4daf06a5a87a7307ccb0fc94f98d06d
1160 1160 parent: -1:0000000000000000000000000000000000000000
1161 1161 manifest: 0:6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50
1162 1162 user: test <test@example.com>
1163 1163 date: Sun Sep 09 01:46:40 2001 +0000
1164 1164 extra: branch=default
1165 1165 extra: convert_revision=0000aaaabbbbccccddddeeee
1166 1166 description:
1167 1167 message with extras
1168 1168
1169 1169
@@ -1,149 +1,149 b''
1 1 $ hg init a
2 2
3 3 $ echo a > a/a
4 4 $ hg --cwd a ci -Ama
5 5 adding a
6 6
7 7 $ hg clone a c
8 8 updating to branch default
9 9 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
10 10
11 11 $ hg clone a b
12 12 updating to branch default
13 13 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
14 14
15 15 $ echo b >> b/a
16 16 $ hg --cwd b ci -mb
17 17
18 18 Push should provide a hint when both 'default' and 'default-push' not set:
19 19 $ cd c
20 20 $ hg push --config paths.default=
21 abort: default repository not configured!
21 config error: default repository not configured!
22 22 (see 'hg help config.paths')
23 23 [30]
24 24
25 25 $ cd ..
26 26
27 27 Push should push to 'default' when 'default-push' not set:
28 28
29 29 $ hg --cwd b push
30 30 pushing to $TESTTMP/a
31 31 searching for changes
32 32 adding changesets
33 33 adding manifests
34 34 adding file changes
35 35 added 1 changesets with 1 changes to 1 files
36 36
37 37 Push should push to 'default-push' when set:
38 38
39 39 $ echo '[paths]' >> b/.hg/hgrc
40 40 $ echo 'default-push = ../c' >> b/.hg/hgrc
41 41 $ hg --cwd b push
42 42 pushing to $TESTTMP/c
43 43 searching for changes
44 44 adding changesets
45 45 adding manifests
46 46 adding file changes
47 47 added 1 changesets with 1 changes to 1 files
48 48
49 49 But push should push to 'default' if explicitly specified (issue5000):
50 50
51 51 $ hg --cwd b push default
52 52 pushing to $TESTTMP/a
53 53 searching for changes
54 54 no changes found
55 55 [1]
56 56
57 57 Push should push to 'default-push' when 'default' is not set
58 58
59 59 $ hg -q clone a push-default-only
60 60 $ cd push-default-only
61 61 $ rm .hg/hgrc
62 62
63 63 $ touch foo
64 64 $ hg -q commit -A -m 'add foo'
65 65 $ hg --config paths.default-push=../a push
66 66 pushing to $TESTTMP/a
67 67 searching for changes
68 68 adding changesets
69 69 adding manifests
70 70 adding file changes
71 71 added 1 changesets with 1 changes to 1 files
72 72
73 73 $ cd ..
74 74
75 75 Pushing to a path that isn't defined should not fall back to default
76 76
77 77 $ hg --cwd b push doesnotexist
78 78 abort: repository doesnotexist does not exist!
79 79 [255]
80 80
81 81 :pushurl is used when defined
82 82
83 83 $ hg -q clone a pushurlsource
84 84 $ hg -q clone a pushurldest
85 85 $ cd pushurlsource
86 86
87 87 Windows needs a leading slash to make a URL that passes all of the checks
88 88 $ WD=`pwd`
89 89 #if windows
90 90 $ WD="/$WD"
91 91 #endif
92 92 $ cat > .hg/hgrc << EOF
93 93 > [paths]
94 94 > default = https://example.com/not/relevant
95 95 > default:pushurl = file://$WD/../pushurldest
96 96 > EOF
97 97
98 98 $ touch pushurl
99 99 $ hg -q commit -A -m 'add pushurl'
100 100 $ hg push
101 101 pushing to file:/*/$TESTTMP/pushurlsource/../pushurldest (glob)
102 102 searching for changes
103 103 adding changesets
104 104 adding manifests
105 105 adding file changes
106 106 added 1 changesets with 1 changes to 1 files
107 107
108 108 :pushrev is used when no -r is passed
109 109
110 110 $ cat >> .hg/hgrc << EOF
111 111 > default:pushrev = .
112 112 > EOF
113 113 $ hg -q up -r 0
114 114 $ echo head1 > foo
115 115 $ hg -q commit -A -m head1
116 116 $ hg -q up -r 0
117 117 $ echo head2 > foo
118 118 $ hg -q commit -A -m head2
119 119 $ hg push -f
120 120 pushing to file:/*/$TESTTMP/pushurlsource/../pushurldest (glob)
121 121 searching for changes
122 122 adding changesets
123 123 adding manifests
124 124 adding file changes
125 125 added 1 changesets with 1 changes to 1 files (+1 heads)
126 126
127 127 $ hg --config 'paths.default:pushrev=draft()' push -f
128 128 pushing to file:/*/$TESTTMP/pushurlsource/../pushurldest (glob)
129 129 searching for changes
130 130 adding changesets
131 131 adding manifests
132 132 adding file changes
133 133 added 1 changesets with 1 changes to 1 files (+1 heads)
134 134
135 135 Invalid :pushrev raises appropriately
136 136
137 137 $ hg --config 'paths.default:pushrev=notdefined()' push
138 138 pushing to file:/*/$TESTTMP/pushurlsource/../pushurldest (glob)
139 139 hg: parse error: unknown identifier: notdefined
140 140 [255]
141 141
142 142 $ hg --config 'paths.default:pushrev=(' push
143 143 pushing to file:/*/$TESTTMP/pushurlsource/../pushurldest (glob)
144 144 hg: parse error at 1: not a prefix: end
145 145 ((
146 146 ^ here)
147 147 [255]
148 148
149 149 $ cd ..
@@ -1,221 +1,221 b''
1 1 test command parsing and dispatch
2 2
3 3 $ hg init a
4 4 $ cd a
5 5
6 6 Redundant options used to crash (issue436):
7 7 $ hg -v log -v
8 8 $ hg -v log -v x
9 9
10 10 $ echo a > a
11 11 $ hg ci -Ama
12 12 adding a
13 13
14 14 Missing arg:
15 15
16 16 $ hg cat
17 17 hg cat: invalid arguments
18 18 hg cat [OPTION]... FILE...
19 19
20 20 output the current or given revision of files
21 21
22 22 options ([+] can be repeated):
23 23
24 24 -o --output FORMAT print output to file with formatted name
25 25 -r --rev REV print the given revision
26 26 --decode apply any matching decode filter
27 27 -I --include PATTERN [+] include names matching the given patterns
28 28 -X --exclude PATTERN [+] exclude names matching the given patterns
29 29 -T --template TEMPLATE display with template
30 30
31 31 (use 'hg cat -h' to show more help)
32 32 [255]
33 33
34 34 Missing parameter for early option:
35 35
36 36 $ hg log -R 2>&1 | grep 'hg log'
37 37 hg log: option -R requires argument
38 38 hg log [OPTION]... [FILE]
39 39 (use 'hg log -h' to show more help)
40 40
41 41 "--" may be an option value:
42 42
43 43 $ hg -R -- log
44 44 abort: repository -- not found!
45 45 [255]
46 46 $ hg log -R --
47 47 abort: repository -- not found!
48 48 [255]
49 49 $ hg log -T --
50 50 -- (no-eol)
51 51 $ hg log -T -- -k nomatch
52 52
53 53 Parsing of early options should stop at "--":
54 54
55 55 $ hg cat -- --config=hooks.pre-cat=false
56 56 --config=hooks.pre-cat=false: no such file in rev cb9a9f314b8b
57 57 [1]
58 58 $ hg cat -- --debugger
59 59 --debugger: no such file in rev cb9a9f314b8b
60 60 [1]
61 61
62 62 Unparsable form of early options:
63 63
64 64 $ hg cat --debugg
65 65 abort: option --debugger may not be abbreviated!
66 66 [255]
67 67
68 68 Parsing failure of early options should be detected before executing the
69 69 command:
70 70
71 71 $ hg log -b '--config=hooks.pre-log=false' default
72 72 abort: option --config may not be abbreviated!
73 73 [255]
74 74 $ hg log -b -R. default
75 75 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
76 76 [255]
77 77 $ hg log --cwd .. -b --cwd=. default
78 78 abort: option --cwd may not be abbreviated!
79 79 [255]
80 80
81 81 However, we can't prevent it from loading extensions and configs:
82 82
83 83 $ cat <<EOF > bad.py
84 84 > raise Exception('bad')
85 85 > EOF
86 86 $ hg log -b '--config=extensions.bad=bad.py' default
87 87 *** failed to import extension bad from bad.py: bad
88 88 abort: option --config may not be abbreviated!
89 89 [255]
90 90
91 91 $ mkdir -p badrepo/.hg
92 92 $ echo 'invalid-syntax' > badrepo/.hg/hgrc
93 93 $ hg log -b -Rbadrepo default
94 hg: parse error at badrepo/.hg/hgrc:1: invalid-syntax
95 [255]
94 config error at badrepo/.hg/hgrc:1: invalid-syntax
95 [30]
96 96
97 97 $ hg log -b --cwd=inexistent default
98 98 abort: $ENOENT$: 'inexistent'
99 99 [255]
100 100
101 101 $ hg log -b '--config=ui.traceback=yes' 2>&1 | grep '^Traceback'
102 102 Traceback (most recent call last):
103 103 $ hg log -b '--config=profiling.enabled=yes' 2>&1 | grep -i sample
104 104 Sample count: .*|No samples recorded\. (re)
105 105
106 106 Early options can't be specified in [aliases] and [defaults] because they are
107 107 applied before the command name is resolved:
108 108
109 109 $ hg log -b '--config=alias.log=log --config=hooks.pre-log=false'
110 110 hg log: option -b not recognized
111 111 error in definition for alias 'log': --config may only be given on the command
112 112 line
113 113 [255]
114 114
115 115 $ hg log -b '--config=defaults.log=--config=hooks.pre-log=false'
116 116 abort: option --config may not be abbreviated!
117 117 [255]
118 118
119 119 Shell aliases bypass any command parsing rules but for the early one:
120 120
121 121 $ hg log -b '--config=alias.log=!echo howdy'
122 122 howdy
123 123
124 124 Early options must come first if HGPLAIN=+strictflags is specified:
125 125 (BUG: chg cherry-picks early options to pass them as a server command)
126 126
127 127 #if no-chg
128 128 $ HGPLAIN=+strictflags hg log -b --config='hooks.pre-log=false' default
129 129 abort: unknown revision '--config=hooks.pre-log=false'!
130 130 [255]
131 131 $ HGPLAIN=+strictflags hg log -b -R. default
132 132 abort: unknown revision '-R.'!
133 133 [255]
134 134 $ HGPLAIN=+strictflags hg log -b --cwd=. default
135 135 abort: unknown revision '--cwd=.'!
136 136 [255]
137 137 #endif
138 138 $ HGPLAIN=+strictflags hg log -b --debugger default
139 139 abort: unknown revision '--debugger'!
140 140 [255]
141 141 $ HGPLAIN=+strictflags hg log -b --config='alias.log=!echo pwned' default
142 142 abort: unknown revision '--config=alias.log=!echo pwned'!
143 143 [255]
144 144
145 145 $ HGPLAIN=+strictflags hg log --config='hooks.pre-log=false' -b default
146 146 abort: option --config may not be abbreviated!
147 147 [255]
148 148 $ HGPLAIN=+strictflags hg log -q --cwd=.. -b default
149 149 abort: option --cwd may not be abbreviated!
150 150 [255]
151 151 $ HGPLAIN=+strictflags hg log -q -R . -b default
152 152 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
153 153 [255]
154 154
155 155 $ HGPLAIN=+strictflags hg --config='hooks.pre-log=false' log -b default
156 156 abort: pre-log hook exited with status 1
157 157 [255]
158 158 $ HGPLAIN=+strictflags hg --cwd .. -q -Ra log -b default
159 159 0:cb9a9f314b8b
160 160 $ HGPLAIN=+strictflags hg --cwd .. -q --repository a log -b default
161 161 0:cb9a9f314b8b
162 162 $ HGPLAIN=+strictflags hg --cwd .. -q --repo a log -b default
163 163 0:cb9a9f314b8b
164 164
165 165 For compatibility reasons, HGPLAIN=+strictflags is not enabled by plain HGPLAIN:
166 166
167 167 $ HGPLAIN= hg log --config='hooks.pre-log=false' -b default
168 168 abort: pre-log hook exited with status 1
169 169 [255]
170 170 $ HGPLAINEXCEPT= hg log --cwd .. -q -Ra -b default
171 171 0:cb9a9f314b8b
172 172
173 173 [defaults]
174 174
175 175 $ hg cat a
176 176 a
177 177 $ cat >> $HGRCPATH <<EOF
178 178 > [defaults]
179 179 > cat = -r null
180 180 > EOF
181 181 $ hg cat a
182 182 a: no such file in rev 000000000000
183 183 [1]
184 184
185 185 $ cd "$TESTTMP"
186 186
187 187 OSError "No such file or directory" / "The system cannot find the path
188 188 specified" should include filename even when it is empty
189 189
190 190 $ hg -R a archive ''
191 191 abort: $ENOENT$: '' (no-windows !)
192 192 abort: $ENOTDIR$: '' (windows !)
193 193 [255]
194 194
195 195 #if no-outer-repo
196 196
197 197 No repo:
198 198
199 199 $ hg cat
200 200 abort: no repository found in '$TESTTMP' (.hg not found)!
201 201 [255]
202 202
203 203 #endif
204 204
205 205 #if rmcwd
206 206
207 207 Current directory removed:
208 208
209 209 $ mkdir $TESTTMP/repo1
210 210 $ cd $TESTTMP/repo1
211 211 $ rm -rf $TESTTMP/repo1
212 212
213 213 The output could be one of the following and something else:
214 214 chg: abort: failed to getcwd (errno = *) (glob)
215 215 abort: error getting current working directory: * (glob)
216 216 sh: 0: getcwd() failed: $ENOENT$
217 217 Since the exact behavior depends on the shell, only check it returns non-zero.
218 218 $ HGDEMANDIMPORT=disable hg version -q 2>/dev/null || false
219 219 [1]
220 220
221 221 #endif
@@ -1,308 +1,308 b''
1 1 Use hgrc within $TESTTMP
2 2
3 3 $ HGRCPATH=`pwd`/hgrc
4 4 $ export HGRCPATH
5 5
6 6 hide outer repo
7 7 $ hg init
8 8
9 9 Use an alternate var for scribbling on hgrc to keep check-code from
10 10 complaining about the important settings we may be overwriting:
11 11
12 12 $ HGRC=`pwd`/hgrc
13 13 $ export HGRC
14 14
15 15 Basic syntax error
16 16
17 17 $ echo "invalid" > $HGRC
18 18 $ hg version
19 hg: parse error at $TESTTMP/hgrc:1: invalid
19 config error at $TESTTMP/hgrc:1: invalid
20 20 [255]
21 21 $ echo "" > $HGRC
22 22
23 23 Issue1199: Can't use '%' in hgrc (eg url encoded username)
24 24
25 25 $ hg init "foo%bar"
26 26 $ hg clone "foo%bar" foobar
27 27 updating to branch default
28 28 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
29 29 $ cd foobar
30 30 $ cat .hg/hgrc
31 31 # example repository config (see 'hg help config' for more info)
32 32 [paths]
33 33 default = $TESTTMP/foo%bar
34 34
35 35 # path aliases to other clones of this repo in URLs or filesystem paths
36 36 # (see 'hg help config.paths' for more info)
37 37 #
38 38 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
39 39 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
40 40 # my-clone = /home/jdoe/jdoes-clone
41 41
42 42 [ui]
43 43 # name and email (local to this repository, optional), e.g.
44 44 # username = Jane Doe <jdoe@example.com>
45 45 $ hg paths
46 46 default = $TESTTMP/foo%bar
47 47 $ hg showconfig
48 48 bundle.mainreporoot=$TESTTMP/foobar
49 49 paths.default=$TESTTMP/foo%bar
50 50 $ cd ..
51 51
52 52 Check %include
53 53
54 54 $ echo '[section]' > $TESTTMP/included
55 55 $ echo 'option = value' >> $TESTTMP/included
56 56 $ echo '%include $TESTTMP/included' >> $HGRC
57 57 $ hg showconfig section
58 58 section.option=value
59 59 #if unix-permissions no-root
60 60 $ chmod u-r $TESTTMP/included
61 61 $ hg showconfig section
62 hg: parse error at $TESTTMP/hgrc:2: cannot include $TESTTMP/included (Permission denied)
62 config error at $TESTTMP/hgrc:2: cannot include $TESTTMP/included (Permission denied)
63 63 [255]
64 64 #endif
65 65
66 66 issue1829: wrong indentation
67 67
68 68 $ echo '[foo]' > $HGRC
69 69 $ echo ' x = y' >> $HGRC
70 70 $ hg version
71 hg: parse error at $TESTTMP/hgrc:2: unexpected leading whitespace: x = y
71 config error at $TESTTMP/hgrc:2: unexpected leading whitespace: x = y
72 72 [255]
73 73
74 74 $ "$PYTHON" -c "from __future__ import print_function; print('[foo]\nbar = a\n b\n c \n de\n fg \nbaz = bif cb \n')" \
75 75 > > $HGRC
76 76 $ hg showconfig foo
77 77 foo.bar=a\nb\nc\nde\nfg
78 78 foo.baz=bif cb
79 79
80 80 $ FAKEPATH=/path/to/nowhere
81 81 $ export FAKEPATH
82 82 $ echo '%include $FAKEPATH/no-such-file' > $HGRC
83 83 $ hg version
84 84 Mercurial Distributed SCM (version *) (glob)
85 85 (see https://mercurial-scm.org for more information)
86 86
87 87 Copyright (C) 2005-* Matt Mackall and others (glob)
88 88 This is free software; see the source for copying conditions. There is NO
89 89 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
90 90 $ unset FAKEPATH
91 91
92 92 make sure global options given on the cmdline take precedence
93 93
94 94 $ hg showconfig --config ui.verbose=True --quiet
95 95 bundle.mainreporoot=$TESTTMP
96 96 ui.verbose=False
97 97 ui.debug=False
98 98 ui.quiet=True
99 99
100 100 $ touch foobar/untracked
101 101 $ cat >> foobar/.hg/hgrc <<EOF
102 102 > [ui]
103 103 > verbose=True
104 104 > EOF
105 105 $ hg -R foobar st -q
106 106
107 107 username expansion
108 108
109 109 $ olduser=$HGUSER
110 110 $ unset HGUSER
111 111
112 112 $ FAKEUSER='John Doe'
113 113 $ export FAKEUSER
114 114 $ echo '[ui]' > $HGRC
115 115 $ echo 'username = $FAKEUSER' >> $HGRC
116 116
117 117 $ hg init usertest
118 118 $ cd usertest
119 119 $ touch bar
120 120 $ hg commit --addremove --quiet -m "added bar"
121 121 $ hg log --template "{author}\n"
122 122 John Doe
123 123 $ cd ..
124 124
125 125 $ hg showconfig
126 126 bundle.mainreporoot=$TESTTMP
127 127 ui.username=$FAKEUSER
128 128
129 129 $ unset FAKEUSER
130 130 $ HGUSER=$olduser
131 131 $ export HGUSER
132 132
133 133 showconfig with multiple arguments
134 134
135 135 $ echo "[alias]" > $HGRC
136 136 $ echo "log = log -g" >> $HGRC
137 137 $ echo "[defaults]" >> $HGRC
138 138 $ echo "identify = -n" >> $HGRC
139 139 $ hg showconfig alias defaults
140 140 alias.log=log -g
141 141 defaults.identify=-n
142 142 $ hg showconfig alias alias
143 143 alias.log=log -g
144 144 $ hg showconfig alias.log alias.log
145 145 alias.log=log -g
146 146 $ hg showconfig alias defaults.identify
147 147 alias.log=log -g
148 148 defaults.identify=-n
149 149 $ hg showconfig alias.log defaults.identify
150 150 alias.log=log -g
151 151 defaults.identify=-n
152 152
153 153 HGPLAIN
154 154
155 155 $ echo "[ui]" > $HGRC
156 156 $ echo "debug=true" >> $HGRC
157 157 $ echo "fallbackencoding=ASCII" >> $HGRC
158 158 $ echo "quiet=true" >> $HGRC
159 159 $ echo "slash=true" >> $HGRC
160 160 $ echo "traceback=true" >> $HGRC
161 161 $ echo "verbose=true" >> $HGRC
162 162 $ echo "style=~/.hgstyle" >> $HGRC
163 163 $ echo "logtemplate={node}" >> $HGRC
164 164 $ echo "[defaults]" >> $HGRC
165 165 $ echo "identify=-n" >> $HGRC
166 166 $ echo "[alias]" >> $HGRC
167 167 $ echo "log=log -g" >> $HGRC
168 168
169 169 customized hgrc
170 170
171 171 $ hg showconfig
172 172 read config from: $TESTTMP/hgrc
173 173 $TESTTMP/hgrc:13: alias.log=log -g
174 174 repo: bundle.mainreporoot=$TESTTMP
175 175 $TESTTMP/hgrc:11: defaults.identify=-n
176 176 $TESTTMP/hgrc:2: ui.debug=true
177 177 $TESTTMP/hgrc:3: ui.fallbackencoding=ASCII
178 178 $TESTTMP/hgrc:4: ui.quiet=true
179 179 $TESTTMP/hgrc:5: ui.slash=true
180 180 $TESTTMP/hgrc:6: ui.traceback=true
181 181 $TESTTMP/hgrc:7: ui.verbose=true
182 182 $TESTTMP/hgrc:8: ui.style=~/.hgstyle
183 183 $TESTTMP/hgrc:9: ui.logtemplate={node}
184 184
185 185 plain hgrc
186 186
187 187 $ HGPLAIN=; export HGPLAIN
188 188 $ hg showconfig --config ui.traceback=True --debug
189 189 read config from: $TESTTMP/hgrc
190 190 repo: bundle.mainreporoot=$TESTTMP
191 191 --config: ui.traceback=True
192 192 --verbose: ui.verbose=False
193 193 --debug: ui.debug=True
194 194 --quiet: ui.quiet=False
195 195
196 196 with environment variables
197 197
198 198 $ PAGER=p1 EDITOR=e1 VISUAL=e2 hg showconfig --debug
199 199 read config from: $TESTTMP/hgrc
200 200 repo: bundle.mainreporoot=$TESTTMP
201 201 $PAGER: pager.pager=p1
202 202 $VISUAL: ui.editor=e2
203 203 --verbose: ui.verbose=False
204 204 --debug: ui.debug=True
205 205 --quiet: ui.quiet=False
206 206
207 207 plain mode with exceptions
208 208
209 209 $ cat > plain.py <<EOF
210 210 > from mercurial import commands, extensions
211 211 > def _config(orig, ui, repo, *values, **opts):
212 212 > ui.write(b'plain: %r\n' % ui.plain())
213 213 > return orig(ui, repo, *values, **opts)
214 214 > def uisetup(ui):
215 215 > extensions.wrapcommand(commands.table, b'config', _config)
216 216 > EOF
217 217 $ echo "[extensions]" >> $HGRC
218 218 $ echo "plain=./plain.py" >> $HGRC
219 219 $ HGPLAINEXCEPT=; export HGPLAINEXCEPT
220 220 $ hg showconfig --config ui.traceback=True --debug
221 221 plain: True
222 222 read config from: $TESTTMP/hgrc
223 223 repo: bundle.mainreporoot=$TESTTMP
224 224 $TESTTMP/hgrc:15: extensions.plain=./plain.py
225 225 --config: ui.traceback=True
226 226 --verbose: ui.verbose=False
227 227 --debug: ui.debug=True
228 228 --quiet: ui.quiet=False
229 229 $ unset HGPLAIN
230 230 $ hg showconfig --config ui.traceback=True --debug
231 231 plain: True
232 232 read config from: $TESTTMP/hgrc
233 233 repo: bundle.mainreporoot=$TESTTMP
234 234 $TESTTMP/hgrc:15: extensions.plain=./plain.py
235 235 --config: ui.traceback=True
236 236 --verbose: ui.verbose=False
237 237 --debug: ui.debug=True
238 238 --quiet: ui.quiet=False
239 239 $ HGPLAINEXCEPT=i18n; export HGPLAINEXCEPT
240 240 $ hg showconfig --config ui.traceback=True --debug
241 241 plain: True
242 242 read config from: $TESTTMP/hgrc
243 243 repo: bundle.mainreporoot=$TESTTMP
244 244 $TESTTMP/hgrc:15: extensions.plain=./plain.py
245 245 --config: ui.traceback=True
246 246 --verbose: ui.verbose=False
247 247 --debug: ui.debug=True
248 248 --quiet: ui.quiet=False
249 249
250 250 source of paths is not mangled
251 251
252 252 $ cat >> $HGRCPATH <<EOF
253 253 > [paths]
254 254 > foo = bar
255 255 > EOF
256 256 $ hg showconfig --debug paths
257 257 plain: True
258 258 read config from: $TESTTMP/hgrc
259 259 $TESTTMP/hgrc:17: paths.foo=$TESTTMP/bar
260 260
261 261 Test we can skip the user configuration
262 262
263 263 $ cat >> .hg/hgrc <<EOF
264 264 > [paths]
265 265 > elephant = babar
266 266 > EOF
267 267 $ hg path
268 268 elephant = $TESTTMP/babar
269 269 foo = $TESTTMP/bar
270 270 $ HGRCSKIPREPO=1 hg path
271 271 foo = $TESTTMP/bar
272 272
273 273 $ cat >> .hg/hgrc <<EOF
274 274 > [broken
275 275 > EOF
276 276
277 277 $ hg path
278 hg: parse error at $TESTTMP/.hg/hgrc:3: [broken
278 config error at $TESTTMP/.hg/hgrc:3: [broken
279 279 [255]
280 280 $ HGRCSKIPREPO=1 hg path
281 281 foo = $TESTTMP/bar
282 282
283 283 Check that hgweb respect HGRCSKIPREPO=1
284 284
285 285 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
286 hg: parse error at $TESTTMP/.hg/hgrc:3: [broken
286 config error at $TESTTMP/.hg/hgrc:3: [broken
287 287 [255]
288 288 $ test -f hg.pid && (cat hg.pid >> $DAEMON_PIDS)
289 289 [1]
290 290 $ killdaemons.py
291 291 $ test -f access.log && cat access.log
292 292 [1]
293 293 $ test -f errors.log && cat errors.log
294 294 [1]
295 295
296 296 $ HGRCSKIPREPO=1 hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
297 297 $ cat hg.pid >> $DAEMON_PIDS
298 298 $ killdaemons.py
299 299 $ cat access.log
300 300 $ cat errors.log
301 301
302 302 Check that zeroconf respect HGRCSKIPREPO=1
303 303
304 304 $ hg paths --config extensions.zeroconf=
305 hg: parse error at $TESTTMP/.hg/hgrc:3: [broken
305 config error at $TESTTMP/.hg/hgrc:3: [broken
306 306 [255]
307 307 $ HGRCSKIPREPO=1 hg paths --config extensions.zeroconf=
308 308 foo = $TESTTMP/bar
@@ -1,605 +1,605 b''
1 1 #testcases abortcommand abortflag
2 2
3 3 #if abortflag
4 4 $ cat >> $HGRCPATH <<EOF
5 5 > [alias]
6 6 > abort = histedit --abort
7 7 > EOF
8 8 #endif
9 9
10 10 Test argument handling and various data parsing
11 11 ==================================================
12 12
13 13
14 14 Enable extensions used by this test.
15 15 $ cat >>$HGRCPATH <<EOF
16 16 > [extensions]
17 17 > histedit=
18 18 > EOF
19 19
20 20 Repo setup.
21 21 $ hg init foo
22 22 $ cd foo
23 23 $ echo alpha >> alpha
24 24 $ hg addr
25 25 adding alpha
26 26 $ hg ci -m one
27 27 $ echo alpha >> alpha
28 28 $ hg ci -m two
29 29 $ echo alpha >> alpha
30 30 $ hg ci -m three
31 31 $ echo alpha >> alpha
32 32 $ hg ci -m four
33 33 $ echo alpha >> alpha
34 34 $ hg ci -m five
35 35
36 36 $ hg log --style compact --graph
37 37 @ 4[tip] 08d98a8350f3 1970-01-01 00:00 +0000 test
38 38 | five
39 39 |
40 40 o 3 c8e68270e35a 1970-01-01 00:00 +0000 test
41 41 | four
42 42 |
43 43 o 2 eb57da33312f 1970-01-01 00:00 +0000 test
44 44 | three
45 45 |
46 46 o 1 579e40513370 1970-01-01 00:00 +0000 test
47 47 | two
48 48 |
49 49 o 0 6058cbb6cfd7 1970-01-01 00:00 +0000 test
50 50 one
51 51
52 52
53 53 histedit --continue/--abort with no existing state
54 54 --------------------------------------------------
55 55
56 56 $ hg histedit --continue
57 57 abort: no histedit in progress
58 58 [20]
59 59 $ hg abort
60 60 abort: no histedit in progress (abortflag !)
61 61 abort: no operation in progress (abortcommand !)
62 62 [20]
63 63
64 64 Run a dummy edit to make sure we get tip^^ correctly via revsingle.
65 65 --------------------------------------------------------------------
66 66
67 67 $ HGEDITOR=cat hg histedit "tip^^"
68 68 pick eb57da33312f 2 three
69 69 pick c8e68270e35a 3 four
70 70 pick 08d98a8350f3 4 five
71 71
72 72 # Edit history between eb57da33312f and 08d98a8350f3
73 73 #
74 74 # Commits are listed from least to most recent
75 75 #
76 76 # You can reorder changesets by reordering the lines
77 77 #
78 78 # Commands:
79 79 #
80 80 # e, edit = use commit, but stop for amending
81 81 # m, mess = edit commit message without changing commit content
82 82 # p, pick = use commit
83 83 # b, base = checkout changeset and apply further changesets from there
84 84 # d, drop = remove commit from history
85 85 # f, fold = use commit, but combine it with the one above
86 86 # r, roll = like fold, but discard this commit's description and date
87 87 #
88 88
89 89 Run on a revision not ancestors of the current working directory.
90 90 --------------------------------------------------------------------
91 91
92 92 $ hg up 2
93 93 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
94 94 $ hg histedit -r 4
95 95 abort: 08d98a8350f3 is not an ancestor of working directory
96 96 [255]
97 97 $ hg up --quiet
98 98
99 99
100 100 Test that we pick the minimum of a revrange
101 101 ---------------------------------------
102 102
103 103 $ HGEDITOR=cat hg histedit '2::' --commands - << EOF
104 104 > pick eb57da33312f 2 three
105 105 > pick c8e68270e35a 3 four
106 106 > pick 08d98a8350f3 4 five
107 107 > EOF
108 108 $ hg up --quiet
109 109
110 110 $ HGEDITOR=cat hg histedit 'tip:2' --commands - << EOF
111 111 > pick eb57da33312f 2 three
112 112 > pick c8e68270e35a 3 four
113 113 > pick 08d98a8350f3 4 five
114 114 > EOF
115 115 $ hg up --quiet
116 116
117 117 Test config specified default
118 118 -----------------------------
119 119
120 120 $ HGEDITOR=cat hg histedit --config "histedit.defaultrev=only(.) - ::eb57da33312f" --commands - << EOF
121 121 > pick c8e68270e35a 3 four
122 122 > pick 08d98a8350f3 4 five
123 123 > EOF
124 124
125 125 Test invalid config default
126 126 ---------------------------
127 127
128 128 $ hg histedit --config "histedit.defaultrev="
129 abort: config option histedit.defaultrev can't be empty
129 config error: config option histedit.defaultrev can't be empty
130 130 [30]
131 131
132 132 Run on a revision not descendants of the initial parent
133 133 --------------------------------------------------------------------
134 134
135 135 Test the message shown for inconsistent histedit state, which may be
136 136 created (and forgotten) by Mercurial earlier than 2.7. This emulates
137 137 Mercurial earlier than 2.7 by renaming ".hg/histedit-state"
138 138 temporarily.
139 139
140 140 $ hg log -G -T '{rev} {shortest(node)} {desc}\n' -r 2::
141 141 @ 4 08d9 five
142 142 |
143 143 o 3 c8e6 four
144 144 |
145 145 o 2 eb57 three
146 146 |
147 147 ~
148 148 $ HGEDITOR=cat hg histedit -r 4 --commands - << EOF
149 149 > edit 08d98a8350f3 4 five
150 150 > EOF
151 151 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
152 152 Editing (08d98a8350f3), you may commit or record as needed now.
153 153 (hg histedit --continue to resume)
154 154 [240]
155 155
156 156 $ hg graft --continue
157 157 abort: no graft in progress
158 158 (continue: hg histedit --continue)
159 159 [20]
160 160
161 161 $ mv .hg/histedit-state .hg/histedit-state.back
162 162 $ hg update --quiet --clean 2
163 163 $ echo alpha >> alpha
164 164 $ mv .hg/histedit-state.back .hg/histedit-state
165 165
166 166 $ hg histedit --continue
167 167 saved backup bundle to $TESTTMP/foo/.hg/strip-backup/08d98a8350f3-02594089-histedit.hg
168 168 $ hg log -G -T '{rev} {shortest(node)} {desc}\n' -r 2::
169 169 @ 4 f5ed five
170 170 |
171 171 | o 3 c8e6 four
172 172 |/
173 173 o 2 eb57 three
174 174 |
175 175 ~
176 176
177 177 $ hg unbundle -q $TESTTMP/foo/.hg/strip-backup/08d98a8350f3-02594089-histedit.hg
178 178 $ hg strip -q -r f5ed --config extensions.strip=
179 179 $ hg up -q 08d98a8350f3
180 180
181 181 Test that missing revisions are detected
182 182 ---------------------------------------
183 183
184 184 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
185 185 > pick eb57da33312f 2 three
186 186 > pick 08d98a8350f3 4 five
187 187 > EOF
188 188 hg: parse error: missing rules for changeset c8e68270e35a
189 189 (use "drop c8e68270e35a" to discard, see also: 'hg help -e histedit.config')
190 190 [255]
191 191
192 192 Test that extra revisions are detected
193 193 ---------------------------------------
194 194
195 195 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
196 196 > pick 6058cbb6cfd7 0 one
197 197 > pick c8e68270e35a 3 four
198 198 > pick 08d98a8350f3 4 five
199 199 > EOF
200 200 hg: parse error: pick "6058cbb6cfd7" changeset was not a candidate
201 201 (only use listed changesets)
202 202 [255]
203 203
204 204 Test malformed line
205 205 ---------------------------------------
206 206
207 207 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
208 208 > pickeb57da33312f2three
209 209 > pick c8e68270e35a 3 four
210 210 > pick 08d98a8350f3 4 five
211 211 > EOF
212 212 hg: parse error: malformed line "pickeb57da33312f2three"
213 213 [255]
214 214
215 215 Test unknown changeset
216 216 ---------------------------------------
217 217
218 218 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
219 219 > pick 0123456789ab 2 three
220 220 > pick c8e68270e35a 3 four
221 221 > pick 08d98a8350f3 4 five
222 222 > EOF
223 223 hg: parse error: unknown changeset 0123456789ab listed
224 224 [255]
225 225
226 226 Test unknown command
227 227 ---------------------------------------
228 228
229 229 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
230 230 > coin eb57da33312f 2 three
231 231 > pick c8e68270e35a 3 four
232 232 > pick 08d98a8350f3 4 five
233 233 > EOF
234 234 hg: parse error: unknown action "coin"
235 235 [255]
236 236
237 237 Test duplicated changeset
238 238 ---------------------------------------
239 239
240 240 So one is missing and one appear twice.
241 241
242 242 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
243 243 > pick eb57da33312f 2 three
244 244 > pick eb57da33312f 2 three
245 245 > pick 08d98a8350f3 4 five
246 246 > EOF
247 247 hg: parse error: duplicated command for changeset eb57da33312f
248 248 [255]
249 249
250 250 Test bogus rev
251 251 ---------------------------------------
252 252
253 253 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
254 254 > pick eb57da33312f 2 three
255 255 > pick 0u98
256 256 > pick 08d98a8350f3 4 five
257 257 > EOF
258 258 hg: parse error: invalid changeset 0u98
259 259 [255]
260 260
261 261 Test short version of command
262 262 ---------------------------------------
263 263
264 264 Note: we use varying amounts of white space between command name and changeset
265 265 short hash. This tests issue3893.
266 266
267 267 $ HGEDITOR=cat hg histedit "tip^^" --commands - << EOF
268 268 > pick eb57da33312f 2 three
269 269 > p c8e68270e35a 3 four
270 270 > f 08d98a8350f3 4 five
271 271 > EOF
272 272 four
273 273 ***
274 274 five
275 275
276 276
277 277
278 278 HG: Enter commit message. Lines beginning with 'HG:' are removed.
279 279 HG: Leave message empty to abort commit.
280 280 HG: --
281 281 HG: user: test
282 282 HG: branch 'default'
283 283 HG: changed alpha
284 284 saved backup bundle to $TESTTMP/foo/.hg/strip-backup/c8e68270e35a-63d8b8d8-histedit.hg
285 285
286 286 $ hg update -q 2
287 287 $ echo x > x
288 288 $ hg add x
289 289 $ hg commit -m'x' x
290 290 created new head
291 291 $ hg histedit -r 'heads(all())'
292 292 abort: The specified revisions must have exactly one common root
293 293 [255]
294 294
295 295 Test that trimming description using multi-byte characters
296 296 --------------------------------------------------------------------
297 297
298 298 $ "$PYTHON" <<EOF
299 299 > fp = open('logfile', 'wb')
300 300 > fp.write(b'12345678901234567890123456789012345678901234567890' +
301 301 > b'12345') # there are 5 more columns for 80 columns
302 302 >
303 303 > # 2 x 4 = 8 columns, but 3 x 4 = 12 bytes
304 304 > fp.write(u'\u3042\u3044\u3046\u3048'.encode('utf-8'))
305 305 >
306 306 > fp.close()
307 307 > EOF
308 308 $ echo xx >> x
309 309 $ hg --encoding utf-8 commit --logfile logfile
310 310
311 311 $ HGEDITOR=cat hg --encoding utf-8 histedit tip
312 312 pick 3d3ea1f3a10b 5 1234567890123456789012345678901234567890123456789012345\xe3\x81\x82... (esc)
313 313
314 314 # Edit history between 3d3ea1f3a10b and 3d3ea1f3a10b
315 315 #
316 316 # Commits are listed from least to most recent
317 317 #
318 318 # You can reorder changesets by reordering the lines
319 319 #
320 320 # Commands:
321 321 #
322 322 # e, edit = use commit, but stop for amending
323 323 # m, mess = edit commit message without changing commit content
324 324 # p, pick = use commit
325 325 # b, base = checkout changeset and apply further changesets from there
326 326 # d, drop = remove commit from history
327 327 # f, fold = use commit, but combine it with the one above
328 328 # r, roll = like fold, but discard this commit's description and date
329 329 #
330 330
331 331 Test --continue with --keep
332 332
333 333 $ hg strip -q -r . --config extensions.strip=
334 334 $ hg histedit '.^' -q --keep --commands - << EOF
335 335 > edit eb57da33312f 2 three
336 336 > pick f3cfcca30c44 4 x
337 337 > EOF
338 338 Editing (eb57da33312f), you may commit or record as needed now.
339 339 (hg histedit --continue to resume)
340 340 [240]
341 341 $ echo edit >> alpha
342 342 $ hg histedit -q --continue
343 343 $ hg log -G -T '{rev}:{node|short} {desc}'
344 344 @ 6:8fda0c726bf2 x
345 345 |
346 346 o 5:63379946892c three
347 347 |
348 348 | o 4:f3cfcca30c44 x
349 349 | |
350 350 | | o 3:2a30f3cfee78 four
351 351 | |/ ***
352 352 | | five
353 353 | o 2:eb57da33312f three
354 354 |/
355 355 o 1:579e40513370 two
356 356 |
357 357 o 0:6058cbb6cfd7 one
358 358
359 359
360 360 Test that abort fails gracefully on exception
361 361 ----------------------------------------------
362 362 $ hg histedit . -q --commands - << EOF
363 363 > edit 8fda0c726bf2 6 x
364 364 > EOF
365 365 Editing (8fda0c726bf2), you may commit or record as needed now.
366 366 (hg histedit --continue to resume)
367 367 [240]
368 368 Corrupt histedit state file
369 369 $ sed 's/8fda0c726bf2/123456789012/' .hg/histedit-state > ../corrupt-histedit
370 370 $ mv ../corrupt-histedit .hg/histedit-state
371 371 $ hg abort
372 372 warning: encountered an exception during histedit --abort; the repository may not have been completely cleaned up
373 373 abort: $TESTTMP/foo/.hg/strip-backup/*-histedit.hg: $ENOENT$ (glob) (windows !)
374 374 abort: $ENOENT$: '$TESTTMP/foo/.hg/strip-backup/*-histedit.hg' (glob) (no-windows !)
375 375 [255]
376 376 Histedit state has been exited
377 377 $ hg summary -q
378 378 parent: 5:63379946892c
379 379 commit: 1 added, 1 unknown (new branch head)
380 380 update: 4 new changesets (update)
381 381
382 382 $ cd ..
383 383
384 384 Set up default base revision tests
385 385
386 386 $ hg init defaultbase
387 387 $ cd defaultbase
388 388 $ touch foo
389 389 $ hg -q commit -A -m root
390 390 $ echo 1 > foo
391 391 $ hg commit -m 'public 1'
392 392 $ hg phase --force --public -r .
393 393 $ echo 2 > foo
394 394 $ hg commit -m 'draft after public'
395 395 $ hg -q up -r 1
396 396 $ echo 3 > foo
397 397 $ hg commit -m 'head 1 public'
398 398 created new head
399 399 $ hg phase --force --public -r .
400 400 $ echo 4 > foo
401 401 $ hg commit -m 'head 1 draft 1'
402 402 $ echo 5 > foo
403 403 $ hg commit -m 'head 1 draft 2'
404 404 $ hg -q up -r 2
405 405 $ echo 6 > foo
406 406 $ hg commit -m 'head 2 commit 1'
407 407 $ echo 7 > foo
408 408 $ hg commit -m 'head 2 commit 2'
409 409 $ hg -q up -r 2
410 410 $ echo 8 > foo
411 411 $ hg commit -m 'head 3'
412 412 created new head
413 413 $ hg -q up -r 2
414 414 $ echo 9 > foo
415 415 $ hg commit -m 'head 4'
416 416 created new head
417 417 $ hg merge --tool :local -r 8
418 418 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
419 419 (branch merge, don't forget to commit)
420 420 $ hg commit -m 'merge head 3 into head 4'
421 421 $ echo 11 > foo
422 422 $ hg commit -m 'commit 1 after merge'
423 423 $ echo 12 > foo
424 424 $ hg commit -m 'commit 2 after merge'
425 425
426 426 $ hg log -G -T '{rev}:{node|short} {phase} {desc}\n'
427 427 @ 12:8cde254db839 draft commit 2 after merge
428 428 |
429 429 o 11:6f2f0241f119 draft commit 1 after merge
430 430 |
431 431 o 10:90506cc76b00 draft merge head 3 into head 4
432 432 |\
433 433 | o 9:f8607a373a97 draft head 4
434 434 | |
435 435 o | 8:0da92be05148 draft head 3
436 436 |/
437 437 | o 7:4c35cdf97d5e draft head 2 commit 2
438 438 | |
439 439 | o 6:931820154288 draft head 2 commit 1
440 440 |/
441 441 | o 5:8cdc02b9bc63 draft head 1 draft 2
442 442 | |
443 443 | o 4:463b8c0d2973 draft head 1 draft 1
444 444 | |
445 445 | o 3:23a0c4eefcbf public head 1 public
446 446 | |
447 447 o | 2:4117331c3abb draft draft after public
448 448 |/
449 449 o 1:4426d359ea59 public public 1
450 450 |
451 451 o 0:54136a8ddf32 public root
452 452
453 453
454 454 Default base revision should stop at public changesets
455 455
456 456 $ hg -q up 8cdc02b9bc63
457 457 $ hg histedit --commands - <<EOF
458 458 > pick 463b8c0d2973
459 459 > pick 8cdc02b9bc63
460 460 > EOF
461 461
462 462 Default base revision should stop at branchpoint
463 463
464 464 $ hg -q up 4c35cdf97d5e
465 465 $ hg histedit --commands - <<EOF
466 466 > pick 931820154288
467 467 > pick 4c35cdf97d5e
468 468 > EOF
469 469
470 470 Default base revision should stop at merge commit
471 471
472 472 $ hg -q up 8cde254db839
473 473 $ hg histedit --commands - <<EOF
474 474 > pick 6f2f0241f119
475 475 > pick 8cde254db839
476 476 > EOF
477 477
478 478 commit --amend should abort if histedit is in progress
479 479 (issue4800) and markers are not being created.
480 480 Eventually, histedit could perhaps look at `source` extra,
481 481 in which case this test should be revisited.
482 482
483 483 $ hg -q up 8cde254db839
484 484 $ hg histedit 6f2f0241f119 --commands - <<EOF
485 485 > pick 8cde254db839
486 486 > edit 6f2f0241f119
487 487 > EOF
488 488 merging foo
489 489 warning: conflicts while merging foo! (edit, then use 'hg resolve --mark')
490 490 Fix up the change (pick 8cde254db839)
491 491 (hg histedit --continue to resume)
492 492 [240]
493 493 $ hg resolve -m --all
494 494 (no more unresolved files)
495 495 continue: hg histedit --continue
496 496 $ hg histedit --cont
497 497 merging foo
498 498 warning: conflicts while merging foo! (edit, then use 'hg resolve --mark')
499 499 Editing (6f2f0241f119), you may commit or record as needed now.
500 500 (hg histedit --continue to resume)
501 501 [240]
502 502 $ hg resolve -m --all
503 503 (no more unresolved files)
504 504 continue: hg histedit --continue
505 505 $ hg commit --amend -m 'reject this fold'
506 506 abort: histedit in progress
507 507 (use 'hg histedit --continue' or 'hg histedit --abort')
508 508 [20]
509 509
510 510 With markers enabled, histedit does not get confused, and
511 511 amend should not be blocked by the ongoing histedit.
512 512
513 513 $ cat >>$HGRCPATH <<EOF
514 514 > [experimental]
515 515 > evolution.createmarkers=True
516 516 > evolution.allowunstable=True
517 517 > EOF
518 518 $ hg commit --amend -m 'allow this fold'
519 519 $ hg histedit --continue
520 520
521 521 $ cd ..
522 522
523 523 Test autoverb feature
524 524
525 525 $ hg init autoverb
526 526 $ cd autoverb
527 527 $ echo alpha >> alpha
528 528 $ hg ci -qAm one
529 529 $ echo alpha >> alpha
530 530 $ hg ci -qm two
531 531 $ echo beta >> beta
532 532 $ hg ci -qAm "roll! one"
533 533
534 534 $ hg log --style compact --graph
535 535 @ 2[tip] 4f34d0f8b5fa 1970-01-01 00:00 +0000 test
536 536 | roll! one
537 537 |
538 538 o 1 579e40513370 1970-01-01 00:00 +0000 test
539 539 | two
540 540 |
541 541 o 0 6058cbb6cfd7 1970-01-01 00:00 +0000 test
542 542 one
543 543
544 544
545 545 Check that 'roll' is selected by default
546 546
547 547 $ HGEDITOR=cat hg histedit 0 --config experimental.histedit.autoverb=True
548 548 pick 6058cbb6cfd7 0 one
549 549 roll 4f34d0f8b5fa 2 roll! one
550 550 pick 579e40513370 1 two
551 551
552 552 # Edit history between 6058cbb6cfd7 and 4f34d0f8b5fa
553 553 #
554 554 # Commits are listed from least to most recent
555 555 #
556 556 # You can reorder changesets by reordering the lines
557 557 #
558 558 # Commands:
559 559 #
560 560 # e, edit = use commit, but stop for amending
561 561 # m, mess = edit commit message without changing commit content
562 562 # p, pick = use commit
563 563 # b, base = checkout changeset and apply further changesets from there
564 564 # d, drop = remove commit from history
565 565 # f, fold = use commit, but combine it with the one above
566 566 # r, roll = like fold, but discard this commit's description and date
567 567 #
568 568
569 569 $ cd ..
570 570
571 571 Check that histedit's commands accept revsets
572 572 $ hg init bar
573 573 $ cd bar
574 574 $ echo w >> a
575 575 $ hg ci -qAm "adds a"
576 576 $ echo x >> b
577 577 $ hg ci -qAm "adds b"
578 578 $ echo y >> c
579 579 $ hg ci -qAm "adds c"
580 580 $ echo z >> d
581 581 $ hg ci -qAm "adds d"
582 582 $ hg log -G -T '{rev} {desc}\n'
583 583 @ 3 adds d
584 584 |
585 585 o 2 adds c
586 586 |
587 587 o 1 adds b
588 588 |
589 589 o 0 adds a
590 590
591 591 $ HGEDITOR=cat hg histedit "2" --commands - << EOF
592 592 > base -4 adds c
593 593 > pick 2 adds c
594 594 > pick tip adds d
595 595 > EOF
596 596 $ hg log -G -T '{rev} {desc}\n'
597 597 @ 5 adds d
598 598 |
599 599 o 4 adds c
600 600 |
601 601 | o 1 adds b
602 602 |/
603 603 o 0 adds a
604 604
605 605
@@ -1,1238 +1,1238 b''
1 1 #require no-reposimplestore no-chg
2 2
3 3 $ hg init requirements
4 4 $ cd requirements
5 5
6 6 # LFS not loaded by default.
7 7
8 8 $ hg config extensions
9 9 [1]
10 10
11 11 # Adding lfs to requires file will auto-load lfs extension.
12 12
13 13 $ echo lfs >> .hg/requires
14 14 $ hg config extensions
15 15 extensions.lfs=
16 16
17 17 # But only if there is no config entry for the extension already.
18 18
19 19 $ cat > .hg/hgrc << EOF
20 20 > [extensions]
21 21 > lfs=!
22 22 > EOF
23 23
24 24 $ hg config extensions
25 25 abort: repository requires features unknown to this Mercurial: lfs!
26 26 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
27 27 [255]
28 28
29 29 $ cat > .hg/hgrc << EOF
30 30 > [extensions]
31 31 > lfs=
32 32 > EOF
33 33
34 34 $ hg config extensions
35 35 extensions.lfs=
36 36
37 37 $ cat > .hg/hgrc << EOF
38 38 > [extensions]
39 39 > lfs = missing.py
40 40 > EOF
41 41
42 42 $ hg config extensions
43 43 \*\*\* failed to import extension lfs from missing.py: [Errno *] $ENOENT$: 'missing.py' (glob)
44 44 abort: repository requires features unknown to this Mercurial: lfs!
45 45 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
46 46 [255]
47 47
48 48 $ cd ..
49 49
50 50 # Initial setup
51 51
52 52 $ cat >> $HGRCPATH << EOF
53 53 > [extensions]
54 54 > lfs=
55 55 > [lfs]
56 56 > # Test deprecated config
57 57 > threshold=1000B
58 58 > EOF
59 59
60 60 $ LONG=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
61 61
62 62 # Prepare server and enable extension
63 63 $ hg init server
64 64 $ hg clone -q server client
65 65 $ cd client
66 66
67 67 # Commit small file
68 68 $ echo s > smallfile
69 69 $ echo '**.py = LF' > .hgeol
70 70 $ hg --config lfs.track='"size(\">1000B\")"' commit -Aqm "add small file"
71 71 hg: parse error: unsupported file pattern: size(">1000B")
72 72 (paths must be prefixed with "path:")
73 73 [255]
74 74 $ hg --config lfs.track='size(">1000B")' commit -Aqm "add small file"
75 75
76 76 # Commit large file
77 77 $ echo $LONG > largefile
78 78 $ grep lfs .hg/requires
79 79 [1]
80 80 $ hg commit --traceback -Aqm "add large file"
81 81 $ grep lfs .hg/requires
82 82 lfs
83 83
84 84 # Ensure metadata is stored
85 85 $ hg debugdata largefile 0
86 86 version https://git-lfs.github.com/spec/v1
87 87 oid sha256:f11e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
88 88 size 1501
89 89 x-is-binary 0
90 90
91 91 # Check the blobstore is populated
92 92 $ find .hg/store/lfs/objects | sort
93 93 .hg/store/lfs/objects
94 94 .hg/store/lfs/objects/f1
95 95 .hg/store/lfs/objects/f1/1e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
96 96
97 97 # Check the blob stored contains the actual contents of the file
98 98 $ cat .hg/store/lfs/objects/f1/1e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
99 99 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
100 100
101 101 # Push changes to the server
102 102
103 103 $ hg push
104 104 pushing to $TESTTMP/server
105 105 searching for changes
106 106 abort: lfs.url needs to be configured
107 107 [255]
108 108
109 109 $ cat >> $HGRCPATH << EOF
110 110 > [lfs]
111 111 > url=file:$TESTTMP/dummy-remote/
112 112 > EOF
113 113
114 114 Push to a local non-lfs repo with the extension enabled will add the
115 115 lfs requirement
116 116
117 117 $ grep lfs $TESTTMP/server/.hg/requires
118 118 [1]
119 119 $ hg push -v | egrep -v '^(uncompressed| )'
120 120 pushing to $TESTTMP/server
121 121 searching for changes
122 122 lfs: found f11e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b in the local lfs store
123 123 2 changesets found
124 124 adding changesets
125 125 adding manifests
126 126 adding file changes
127 127 calling hook pretxnchangegroup.lfs: hgext.lfs.checkrequireslfs
128 128 added 2 changesets with 3 changes to 3 files
129 129 $ grep lfs $TESTTMP/server/.hg/requires
130 130 lfs
131 131
132 132 # Unknown URL scheme
133 133
134 134 $ hg push --config lfs.url=ftp://foobar
135 135 abort: lfs: unknown url scheme: ftp
136 136 [255]
137 137
138 138 $ cd ../
139 139
140 140 # Initialize new client (not cloning) and setup extension
141 141 $ hg init client2
142 142 $ cd client2
143 143 $ cat >> .hg/hgrc <<EOF
144 144 > [paths]
145 145 > default = $TESTTMP/server
146 146 > EOF
147 147
148 148 # Pull from server
149 149
150 150 Pulling a local lfs repo into a local non-lfs repo with the extension
151 151 enabled adds the lfs requirement
152 152
153 153 $ grep lfs .hg/requires $TESTTMP/server/.hg/requires
154 154 $TESTTMP/server/.hg/requires:lfs
155 155 $ hg pull default
156 156 pulling from $TESTTMP/server
157 157 requesting all changes
158 158 adding changesets
159 159 adding manifests
160 160 adding file changes
161 161 added 2 changesets with 3 changes to 3 files
162 162 new changesets 0ead593177f7:b88141481348
163 163 (run 'hg update' to get a working copy)
164 164 $ grep lfs .hg/requires $TESTTMP/server/.hg/requires
165 165 .hg/requires:lfs
166 166 $TESTTMP/server/.hg/requires:lfs
167 167
168 168 # Check the blobstore is not yet populated
169 169 $ [ -d .hg/store/lfs/objects ]
170 170 [1]
171 171
172 172 # Update to the last revision containing the large file
173 173 $ hg update
174 174 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
175 175
176 176 # Check the blobstore has been populated on update
177 177 $ find .hg/store/lfs/objects | sort
178 178 .hg/store/lfs/objects
179 179 .hg/store/lfs/objects/f1
180 180 .hg/store/lfs/objects/f1/1e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
181 181
182 182 # Check the contents of the file are fetched from blobstore when requested
183 183 $ hg cat -r . largefile
184 184 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
185 185
186 186 # Check the file has been copied in the working copy
187 187 $ cat largefile
188 188 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
189 189
190 190 $ cd ..
191 191
192 192 # Check rename, and switch between large and small files
193 193
194 194 $ hg init repo3
195 195 $ cd repo3
196 196 $ cat >> .hg/hgrc << EOF
197 197 > [lfs]
198 198 > track=size(">10B")
199 199 > EOF
200 200
201 201 $ echo LONGER-THAN-TEN-BYTES-WILL-TRIGGER-LFS > large
202 202 $ echo SHORTER > small
203 203 $ hg add . -q
204 204 $ hg commit -m 'commit with lfs content'
205 205
206 206 $ hg files -r . 'set:added()'
207 207 large
208 208 small
209 209 $ hg files -r . 'set:added() & lfs()'
210 210 large
211 211
212 212 $ hg mv large l
213 213 $ hg mv small s
214 214 $ hg status 'set:removed()'
215 215 R large
216 216 R small
217 217 $ hg status 'set:removed() & lfs()'
218 218 R large
219 219 $ hg commit -m 'renames'
220 220
221 221 $ hg cat -r . l -T '{rawdata}\n'
222 222 version https://git-lfs.github.com/spec/v1
223 223 oid sha256:66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
224 224 size 39
225 225 x-hg-copy large
226 226 x-hg-copyrev 2c531e0992ff3107c511b53cb82a91b6436de8b2
227 227 x-is-binary 0
228 228
229 229
230 230 $ hg files -r . 'set:copied()'
231 231 l
232 232 s
233 233 $ hg files -r . 'set:copied() & lfs()'
234 234 l
235 235 $ hg status --change . 'set:removed()'
236 236 R large
237 237 R small
238 238 $ hg status --change . 'set:removed() & lfs()'
239 239 R large
240 240
241 241 $ echo SHORT > l
242 242 $ echo BECOME-LARGER-FROM-SHORTER > s
243 243 $ hg commit -m 'large to small, small to large'
244 244
245 245 $ echo 1 >> l
246 246 $ echo 2 >> s
247 247 $ hg commit -m 'random modifications'
248 248
249 249 $ echo RESTORE-TO-BE-LARGE > l
250 250 $ echo SHORTER > s
251 251 $ hg commit -m 'switch large and small again'
252 252
253 253 # Test lfs_files template
254 254
255 255 $ hg log -r 'all()' -T '{rev} {join(lfs_files, ", ")}\n'
256 256 0 large
257 257 1 l, large
258 258 2 s
259 259 3 s
260 260 4 l
261 261
262 262 # Push and pull the above repo
263 263
264 264 $ hg --cwd .. init repo4
265 265 $ hg push ../repo4
266 266 pushing to ../repo4
267 267 searching for changes
268 268 adding changesets
269 269 adding manifests
270 270 adding file changes
271 271 added 5 changesets with 10 changes to 4 files
272 272
273 273 $ hg --cwd .. init repo5
274 274 $ hg --cwd ../repo5 pull ../repo3
275 275 pulling from ../repo3
276 276 requesting all changes
277 277 adding changesets
278 278 adding manifests
279 279 adding file changes
280 280 added 5 changesets with 10 changes to 4 files
281 281 new changesets fd47a419c4f7:5adf850972b9
282 282 (run 'hg update' to get a working copy)
283 283
284 284 $ cd ..
285 285
286 286 # Test clone
287 287
288 288 $ hg init repo6
289 289 $ cd repo6
290 290 $ cat >> .hg/hgrc << EOF
291 291 > [lfs]
292 292 > track=size(">30B")
293 293 > EOF
294 294
295 295 $ echo LARGE-BECAUSE-IT-IS-MORE-THAN-30-BYTES > large
296 296 $ echo SMALL > small
297 297 $ hg commit -Aqm 'create a lfs file' large small
298 298 $ hg debuglfsupload -r 'all()' -v
299 299 lfs: found 8e92251415339ae9b148c8da89ed5ec665905166a1ab11b09dca8fad83344738 in the local lfs store
300 300
301 301 $ cd ..
302 302
303 303 $ hg clone repo6 repo7
304 304 updating to branch default
305 305 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
306 306 $ cd repo7
307 307 $ cat large
308 308 LARGE-BECAUSE-IT-IS-MORE-THAN-30-BYTES
309 309 $ cat small
310 310 SMALL
311 311
312 312 $ cd ..
313 313
314 314 $ hg --config extensions.share= share repo7 sharedrepo
315 315 updating working directory
316 316 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
317 317 $ grep lfs sharedrepo/.hg/requires
318 318 lfs
319 319
320 320 # Test rename and status
321 321
322 322 $ hg init repo8
323 323 $ cd repo8
324 324 $ cat >> .hg/hgrc << EOF
325 325 > [lfs]
326 326 > track=size(">10B")
327 327 > EOF
328 328
329 329 $ echo THIS-IS-LFS-BECAUSE-10-BYTES > a1
330 330 $ echo SMALL > a2
331 331 $ hg commit -m a -A a1 a2
332 332 $ hg status
333 333 $ hg mv a1 b1
334 334 $ hg mv a2 a1
335 335 $ hg mv b1 a2
336 336 $ hg commit -m b
337 337 $ hg status
338 338 >>> with open('a2', 'wb') as f:
339 339 ... f.write(b'\1\nSTART-WITH-HG-FILELOG-METADATA') and None
340 340 >>> with open('a1', 'wb') as f:
341 341 ... f.write(b'\1\nMETA\n') and None
342 342 $ hg commit -m meta
343 343 $ hg status
344 344 $ hg log -T '{rev}: {file_copies} | {file_dels} | {file_adds}\n'
345 345 2: | |
346 346 1: a1 (a2)a2 (a1) | |
347 347 0: | | a1 a2
348 348
349 349 $ for n in a1 a2; do
350 350 > for r in 0 1 2; do
351 351 > printf '\n%s @ %s\n' $n $r
352 352 > hg debugdata $n $r
353 353 > done
354 354 > done
355 355
356 356 a1 @ 0
357 357 version https://git-lfs.github.com/spec/v1
358 358 oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
359 359 size 29
360 360 x-is-binary 0
361 361
362 362 a1 @ 1
363 363 \x01 (esc)
364 364 copy: a2
365 365 copyrev: 50470ad23cf937b1f4b9f80bfe54df38e65b50d9
366 366 \x01 (esc)
367 367 SMALL
368 368
369 369 a1 @ 2
370 370 \x01 (esc)
371 371 \x01 (esc)
372 372 \x01 (esc)
373 373 META
374 374
375 375 a2 @ 0
376 376 SMALL
377 377
378 378 a2 @ 1
379 379 version https://git-lfs.github.com/spec/v1
380 380 oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
381 381 size 29
382 382 x-hg-copy a1
383 383 x-hg-copyrev be23af27908a582af43e5cda209a5a9b319de8d4
384 384 x-is-binary 0
385 385
386 386 a2 @ 2
387 387 version https://git-lfs.github.com/spec/v1
388 388 oid sha256:876dadc86a8542f9798048f2c47f51dbf8e4359aed883e8ec80c5db825f0d943
389 389 size 32
390 390 x-is-binary 0
391 391
392 392 # Verify commit hashes include rename metadata
393 393
394 394 $ hg log -T '{rev}:{node|short} {desc}\n'
395 395 2:0fae949de7fa meta
396 396 1:9cd6bdffdac0 b
397 397 0:7f96794915f7 a
398 398
399 399 $ cd ..
400 400
401 401 # Test bundle
402 402
403 403 $ hg init repo9
404 404 $ cd repo9
405 405 $ cat >> .hg/hgrc << EOF
406 406 > [lfs]
407 407 > track=size(">10B")
408 408 > [diff]
409 409 > git=1
410 410 > EOF
411 411
412 412 $ for i in 0 single two three 4; do
413 413 > echo 'THIS-IS-LFS-'$i > a
414 414 > hg commit -m a-$i -A a
415 415 > done
416 416
417 417 $ hg update 2 -q
418 418 $ echo 'THIS-IS-LFS-2-CHILD' > a
419 419 $ hg commit -m branching -q
420 420
421 421 $ hg bundle --base 1 bundle.hg -v
422 422 lfs: found 5ab7a3739a5feec94a562d070a14f36dba7cad17e5484a4a89eea8e5f3166888 in the local lfs store
423 423 lfs: found a9c7d1cd6ce2b9bbdf46ed9a862845228717b921c089d0d42e3bcaed29eb612e in the local lfs store
424 424 lfs: found f693890c49c409ec33673b71e53f297681f76c1166daf33b2ad7ebf8b1d3237e in the local lfs store
425 425 lfs: found fda198fea753eb66a252e9856915e1f5cddbe41723bd4b695ece2604ad3c9f75 in the local lfs store
426 426 4 changesets found
427 427 uncompressed size of bundle content:
428 428 * (changelog) (glob)
429 429 * (manifests) (glob)
430 430 * a (glob)
431 431 $ hg --config extensions.strip= strip -r 2 --no-backup --force -q
432 432 $ hg -R bundle.hg log -p -T '{rev} {desc}\n' a
433 433 5 branching
434 434 diff --git a/a b/a
435 435 --- a/a
436 436 +++ b/a
437 437 @@ -1,1 +1,1 @@
438 438 -THIS-IS-LFS-two
439 439 +THIS-IS-LFS-2-CHILD
440 440
441 441 4 a-4
442 442 diff --git a/a b/a
443 443 --- a/a
444 444 +++ b/a
445 445 @@ -1,1 +1,1 @@
446 446 -THIS-IS-LFS-three
447 447 +THIS-IS-LFS-4
448 448
449 449 3 a-three
450 450 diff --git a/a b/a
451 451 --- a/a
452 452 +++ b/a
453 453 @@ -1,1 +1,1 @@
454 454 -THIS-IS-LFS-two
455 455 +THIS-IS-LFS-three
456 456
457 457 2 a-two
458 458 diff --git a/a b/a
459 459 --- a/a
460 460 +++ b/a
461 461 @@ -1,1 +1,1 @@
462 462 -THIS-IS-LFS-single
463 463 +THIS-IS-LFS-two
464 464
465 465 1 a-single
466 466 diff --git a/a b/a
467 467 --- a/a
468 468 +++ b/a
469 469 @@ -1,1 +1,1 @@
470 470 -THIS-IS-LFS-0
471 471 +THIS-IS-LFS-single
472 472
473 473 0 a-0
474 474 diff --git a/a b/a
475 475 new file mode 100644
476 476 --- /dev/null
477 477 +++ b/a
478 478 @@ -0,0 +1,1 @@
479 479 +THIS-IS-LFS-0
480 480
481 481 $ hg bundle -R bundle.hg --base 1 bundle-again.hg -q
482 482 $ hg -R bundle-again.hg log -p -T '{rev} {desc}\n' a
483 483 5 branching
484 484 diff --git a/a b/a
485 485 --- a/a
486 486 +++ b/a
487 487 @@ -1,1 +1,1 @@
488 488 -THIS-IS-LFS-two
489 489 +THIS-IS-LFS-2-CHILD
490 490
491 491 4 a-4
492 492 diff --git a/a b/a
493 493 --- a/a
494 494 +++ b/a
495 495 @@ -1,1 +1,1 @@
496 496 -THIS-IS-LFS-three
497 497 +THIS-IS-LFS-4
498 498
499 499 3 a-three
500 500 diff --git a/a b/a
501 501 --- a/a
502 502 +++ b/a
503 503 @@ -1,1 +1,1 @@
504 504 -THIS-IS-LFS-two
505 505 +THIS-IS-LFS-three
506 506
507 507 2 a-two
508 508 diff --git a/a b/a
509 509 --- a/a
510 510 +++ b/a
511 511 @@ -1,1 +1,1 @@
512 512 -THIS-IS-LFS-single
513 513 +THIS-IS-LFS-two
514 514
515 515 1 a-single
516 516 diff --git a/a b/a
517 517 --- a/a
518 518 +++ b/a
519 519 @@ -1,1 +1,1 @@
520 520 -THIS-IS-LFS-0
521 521 +THIS-IS-LFS-single
522 522
523 523 0 a-0
524 524 diff --git a/a b/a
525 525 new file mode 100644
526 526 --- /dev/null
527 527 +++ b/a
528 528 @@ -0,0 +1,1 @@
529 529 +THIS-IS-LFS-0
530 530
531 531 $ cd ..
532 532
533 533 # Test isbinary
534 534
535 535 $ hg init repo10
536 536 $ cd repo10
537 537 $ cat >> .hg/hgrc << EOF
538 538 > [extensions]
539 539 > lfs=
540 540 > [lfs]
541 541 > track=all()
542 542 > EOF
543 543 $ "$PYTHON" <<'EOF'
544 544 > def write(path, content):
545 545 > with open(path, 'wb') as f:
546 546 > f.write(content)
547 547 > write('a', b'\0\0')
548 548 > write('b', b'\1\n')
549 549 > write('c', b'\1\n\0')
550 550 > write('d', b'xx')
551 551 > EOF
552 552 $ hg add a b c d
553 553 $ hg diff --stat
554 554 a | Bin
555 555 b | 1 +
556 556 c | Bin
557 557 d | 1 +
558 558 4 files changed, 2 insertions(+), 0 deletions(-)
559 559 $ hg commit -m binarytest
560 560 $ cat > $TESTTMP/dumpbinary.py << EOF
561 561 > from mercurial.utils import (
562 562 > stringutil,
563 563 > )
564 564 > def reposetup(ui, repo):
565 565 > for n in (b'a', b'b', b'c', b'd'):
566 566 > ui.write((b'%s: binary=%s\n')
567 567 > % (n, stringutil.pprint(repo[b'.'][n].isbinary())))
568 568 > EOF
569 569 $ hg --config extensions.dumpbinary=$TESTTMP/dumpbinary.py id --trace
570 570 a: binary=True
571 571 b: binary=False
572 572 c: binary=True
573 573 d: binary=False
574 574 b55353847f02 tip
575 575
576 576 Binary blobs don't need to be present to be skipped in filesets. (And their
577 577 absence doesn't cause an abort.)
578 578
579 579 $ rm .hg/store/lfs/objects/96/a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7
580 580 $ rm .hg/store/lfs/objects/92/f76135a4baf4faccb8586a60faf830c2bdfce147cefa188aaf4b790bd01b7e
581 581
582 582 $ hg files --debug -r . 'set:eol("unix")' --config 'experimental.lfs.disableusercache=True'
583 583 lfs: found c04b5bb1a5b2eb3e9cd4805420dba5a9d133da5b7adeeafb5474c4adae9faa80 in the local lfs store
584 584 2 b
585 585 lfs: found 5dde896887f6754c9b15bfe3a441ae4806df2fde94001311e08bf110622e0bbe in the local lfs store
586 586
587 587 $ hg files --debug -r . 'set:binary()' --config 'experimental.lfs.disableusercache=True'
588 588 2 a
589 589 3 c
590 590
591 591 $ cd ..
592 592
593 593 # Test fctx.cmp fastpath - diff without LFS blobs
594 594
595 595 $ hg init repo12
596 596 $ cd repo12
597 597 $ cat >> .hg/hgrc <<EOF
598 598 > [lfs]
599 599 > threshold=1
600 600 > EOF
601 601 $ cat > ../patch.diff <<EOF
602 602 > # HG changeset patch
603 603 > 2
604 604 >
605 605 > diff --git a/a b/a
606 606 > old mode 100644
607 607 > new mode 100755
608 608 > EOF
609 609
610 610 $ for i in 1 2 3; do
611 611 > cp ../repo10/a a
612 612 > if [ $i = 3 ]; then
613 613 > # make a content-only change
614 614 > hg import -q --bypass ../patch.diff
615 615 > hg update -q
616 616 > rm ../patch.diff
617 617 > else
618 618 > echo $i >> a
619 619 > hg commit -m $i -A a
620 620 > fi
621 621 > done
622 622 $ [ -d .hg/store/lfs/objects ]
623 623
624 624 $ cd ..
625 625
626 626 $ hg clone repo12 repo13 --noupdate
627 627 $ cd repo13
628 628 $ hg log --removed -p a -T '{desc}\n' --config diff.nobinary=1 --git
629 629 2
630 630 diff --git a/a b/a
631 631 old mode 100644
632 632 new mode 100755
633 633
634 634 2
635 635 diff --git a/a b/a
636 636 Binary file a has changed
637 637
638 638 1
639 639 diff --git a/a b/a
640 640 new file mode 100644
641 641 Binary file a has changed
642 642
643 643 $ [ -d .hg/store/lfs/objects ]
644 644 [1]
645 645
646 646 $ cd ..
647 647
648 648 # Test filter
649 649
650 650 $ hg init repo11
651 651 $ cd repo11
652 652 $ cat >> .hg/hgrc << EOF
653 653 > [lfs]
654 654 > track=(**.a & size(">5B")) | (**.b & !size(">5B"))
655 655 > | (**.c & "path:d" & !"path:d/c.c") | size(">10B")
656 656 > EOF
657 657
658 658 $ mkdir a
659 659 $ echo aaaaaa > a/1.a
660 660 $ echo a > a/2.a
661 661 $ echo aaaaaa > 1.b
662 662 $ echo a > 2.b
663 663 $ echo a > 1.c
664 664 $ mkdir d
665 665 $ echo a > d/c.c
666 666 $ echo a > d/d.c
667 667 $ echo aaaaaaaaaaaa > x
668 668 $ hg add . -q
669 669 $ hg commit -m files
670 670
671 671 $ for p in a/1.a a/2.a 1.b 2.b 1.c d/c.c d/d.c x; do
672 672 > if hg debugdata $p 0 2>&1 | grep git-lfs >/dev/null; then
673 673 > echo "${p}: is lfs"
674 674 > else
675 675 > echo "${p}: not lfs"
676 676 > fi
677 677 > done
678 678 a/1.a: is lfs
679 679 a/2.a: not lfs
680 680 1.b: not lfs
681 681 2.b: is lfs
682 682 1.c: not lfs
683 683 d/c.c: not lfs
684 684 d/d.c: is lfs
685 685 x: is lfs
686 686
687 687 $ cd ..
688 688
689 689 # Verify the repos
690 690
691 691 $ cat > $TESTTMP/dumpflog.py << EOF
692 692 > # print raw revision sizes, flags, and hashes for certain files
693 693 > import hashlib
694 694 > from mercurial.node import short
695 695 > from mercurial import (
696 696 > pycompat,
697 697 > revlog,
698 698 > )
699 699 > from mercurial.utils import (
700 700 > procutil,
701 701 > stringutil,
702 702 > )
703 703 > def hash(rawtext):
704 704 > h = hashlib.sha512()
705 705 > h.update(rawtext)
706 706 > return pycompat.sysbytes(h.hexdigest()[:4])
707 707 > def reposetup(ui, repo):
708 708 > # these 2 files are interesting
709 709 > for name in [b'l', b's']:
710 710 > fl = repo.file(name)
711 711 > if len(fl) == 0:
712 712 > continue
713 713 > sizes = [fl._revlog.rawsize(i) for i in fl]
714 714 > texts = [fl.rawdata(i) for i in fl]
715 715 > flags = [int(fl._revlog.flags(i)) for i in fl]
716 716 > hashes = [hash(t) for t in texts]
717 717 > procutil.stdout.write(b' %s: rawsizes=%r flags=%r hashes=%s\n'
718 718 > % (name, sizes, flags, stringutil.pprint(hashes)))
719 719 > EOF
720 720
721 721 $ for i in client client2 server repo3 repo4 repo5 repo6 repo7 repo8 repo9 \
722 722 > repo10; do
723 723 > echo 'repo:' $i
724 724 > hg --cwd $i verify --config extensions.dumpflog=$TESTTMP/dumpflog.py -q
725 725 > done
726 726 repo: client
727 727 repo: client2
728 728 repo: server
729 729 repo: repo3
730 730 l: rawsizes=[211, 6, 8, 141] flags=[8192, 0, 0, 8192] hashes=['d2b8', '948c', 'cc88', '724d']
731 731 s: rawsizes=[74, 141, 141, 8] flags=[0, 8192, 8192, 0] hashes=['3c80', 'fce0', '874a', '826b']
732 732 repo: repo4
733 733 l: rawsizes=[211, 6, 8, 141] flags=[8192, 0, 0, 8192] hashes=['d2b8', '948c', 'cc88', '724d']
734 734 s: rawsizes=[74, 141, 141, 8] flags=[0, 8192, 8192, 0] hashes=['3c80', 'fce0', '874a', '826b']
735 735 repo: repo5
736 736 l: rawsizes=[211, 6, 8, 141] flags=[8192, 0, 0, 8192] hashes=['d2b8', '948c', 'cc88', '724d']
737 737 s: rawsizes=[74, 141, 141, 8] flags=[0, 8192, 8192, 0] hashes=['3c80', 'fce0', '874a', '826b']
738 738 repo: repo6
739 739 repo: repo7
740 740 repo: repo8
741 741 repo: repo9
742 742 repo: repo10
743 743
744 744 repo13 doesn't have any cached lfs files and its source never pushed its
745 745 files. Therefore, the files don't exist in the remote store. Use the files in
746 746 the user cache.
747 747
748 748 $ test -d $TESTTMP/repo13/.hg/store/lfs/objects
749 749 [1]
750 750
751 751 $ hg --config extensions.share= share repo13 repo14
752 752 updating working directory
753 753 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
754 754 $ hg -R repo14 -q verify
755 755
756 756 $ hg clone repo13 repo15
757 757 updating to branch default
758 758 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
759 759 $ hg -R repo15 -q verify
760 760
761 761 If the source repo doesn't have the blob (maybe it was pulled or cloned with
762 762 --noupdate), the blob is still accessible via the global cache to send to the
763 763 remote store.
764 764
765 765 $ rm -rf $TESTTMP/repo15/.hg/store/lfs
766 766 $ hg init repo16
767 767 $ hg -R repo15 push repo16
768 768 pushing to repo16
769 769 searching for changes
770 770 adding changesets
771 771 adding manifests
772 772 adding file changes
773 773 added 3 changesets with 2 changes to 1 files
774 774 $ hg -R repo15 -q verify
775 775
776 776 Test damaged file scenarios. (This also damages the usercache because of the
777 777 hardlinks.)
778 778
779 779 $ echo 'damage' >> repo5/.hg/store/lfs/objects/66/100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
780 780
781 781 Repo with damaged lfs objects in any revision will fail verification.
782 782
783 783 $ hg -R repo5 verify
784 784 checking changesets
785 785 checking manifests
786 786 crosschecking files in changesets and manifests
787 787 checking files
788 788 l@1: unpacking 46a2f24864bc: integrity check failed on data/l.i:0
789 789 large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0
790 790 checked 5 changesets with 10 changes to 4 files
791 791 2 integrity errors encountered!
792 792 (first damaged changeset appears to be 0)
793 793 [1]
794 794
795 795 Updates work after cloning a damaged repo, if the damaged lfs objects aren't in
796 796 the update destination. Those objects won't be added to the new repo's store
797 797 because they aren't accessed.
798 798
799 799 $ hg clone -v repo5 fromcorrupt
800 800 updating to branch default
801 801 resolving manifests
802 802 getting l
803 803 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the usercache
804 804 getting s
805 805 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
806 806 $ test -f fromcorrupt/.hg/store/lfs/objects/66/100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
807 807 [1]
808 808
809 809 Verify will not try to download lfs blobs, if told not to process lfs content.
810 810 The extension makes sure that the filelog.renamed() path is taken on a missing
811 811 blob, and the output shows that it isn't fetched.
812 812
813 813 $ cat > $TESTTMP/lfsrename.py <<EOF
814 814 > import sys
815 815 >
816 816 > from mercurial import (
817 817 > exthelper,
818 818 > pycompat,
819 819 > )
820 820 >
821 821 > from hgext.lfs import (
822 822 > pointer,
823 823 > wrapper,
824 824 > )
825 825 >
826 826 > eh = exthelper.exthelper()
827 827 > uisetup = eh.finaluisetup
828 828 >
829 829 > @eh.wrapfunction(wrapper, b'filelogrenamed')
830 830 > def filelogrenamed(orig, orig1, self, node):
831 831 > ret = orig(orig1, self, node)
832 832 > if wrapper._islfs(self._revlog, node) and ret:
833 833 > rawtext = self._revlog.rawdata(node)
834 834 > metadata = pointer.deserialize(rawtext)
835 835 > print('lfs blob %s renamed %s -> %s'
836 836 > % (pycompat.sysstr(metadata[b'oid']),
837 837 > pycompat.sysstr(ret[0]),
838 838 > pycompat.fsdecode(self._revlog.filename)))
839 839 > sys.stdout.flush()
840 840 > return ret
841 841 > EOF
842 842
843 843 $ hg -R fromcorrupt --config lfs.usercache=emptycache verify -v --no-lfs \
844 844 > --config extensions.x=$TESTTMP/lfsrename.py
845 845 repository uses revlog format 1
846 846 checking changesets
847 847 checking manifests
848 848 crosschecking files in changesets and manifests
849 849 checking files
850 850 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
851 851 lfs blob sha256:66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e renamed large -> l
852 852 checked 5 changesets with 10 changes to 4 files
853 853
854 854 Verify will not try to download lfs blobs, if told not to by the config option
855 855
856 856 $ hg -R fromcorrupt --config lfs.usercache=emptycache verify -v \
857 857 > --config verify.skipflags=8192 \
858 858 > --config extensions.x=$TESTTMP/lfsrename.py
859 859 repository uses revlog format 1
860 860 checking changesets
861 861 checking manifests
862 862 crosschecking files in changesets and manifests
863 863 checking files
864 864 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
865 865 lfs blob sha256:66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e renamed large -> l
866 866 checked 5 changesets with 10 changes to 4 files
867 867
868 868 Verify will copy/link all lfs objects into the local store that aren't already
869 869 present. Bypass the corrupted usercache to show that verify works when fed by
870 870 the (uncorrupted) remote store.
871 871
872 872 $ hg -R fromcorrupt --config lfs.usercache=emptycache verify -v
873 873 repository uses revlog format 1
874 874 checking changesets
875 875 checking manifests
876 876 crosschecking files in changesets and manifests
877 877 checking files
878 878 lfs: adding 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e to the usercache
879 879 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store
880 880 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
881 881 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store
882 882 lfs: adding 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 to the usercache
883 883 lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store
884 884 lfs: adding b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c to the usercache
885 885 lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store
886 886 checked 5 changesets with 10 changes to 4 files
887 887
888 888 Verify will not copy/link a corrupted file from the usercache into the local
889 889 store, and poison it. (The verify with a good remote now works.)
890 890
891 891 $ rm -r fromcorrupt/.hg/store/lfs/objects/66/100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
892 892 $ hg -R fromcorrupt verify -v
893 893 repository uses revlog format 1
894 894 checking changesets
895 895 checking manifests
896 896 crosschecking files in changesets and manifests
897 897 checking files
898 898 l@1: unpacking 46a2f24864bc: integrity check failed on data/l.i:0
899 899 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
900 900 large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0
901 901 lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store
902 902 lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store
903 903 checked 5 changesets with 10 changes to 4 files
904 904 2 integrity errors encountered!
905 905 (first damaged changeset appears to be 0)
906 906 [1]
907 907 $ hg -R fromcorrupt --config lfs.usercache=emptycache verify -v
908 908 repository uses revlog format 1
909 909 checking changesets
910 910 checking manifests
911 911 crosschecking files in changesets and manifests
912 912 checking files
913 913 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the usercache
914 914 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
915 915 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store
916 916 lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store
917 917 lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store
918 918 checked 5 changesets with 10 changes to 4 files
919 919
920 920 Damaging a file required by the update destination fails the update.
921 921
922 922 $ echo 'damage' >> $TESTTMP/dummy-remote/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b
923 923 $ hg --config lfs.usercache=emptycache clone -v repo5 fromcorrupt2
924 924 updating to branch default
925 925 resolving manifests
926 926 abort: corrupt remote lfs object: 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b
927 927 [255]
928 928
929 929 A corrupted lfs blob is not transferred from a file://remotestore to the
930 930 usercache or local store.
931 931
932 932 $ test -f emptycache/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b
933 933 [1]
934 934 $ test -f fromcorrupt2/.hg/store/lfs/objects/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b
935 935 [1]
936 936
937 937 $ hg -R fromcorrupt2 verify
938 938 checking changesets
939 939 checking manifests
940 940 crosschecking files in changesets and manifests
941 941 checking files
942 942 l@1: unpacking 46a2f24864bc: integrity check failed on data/l.i:0
943 943 large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0
944 944 checked 5 changesets with 10 changes to 4 files
945 945 2 integrity errors encountered!
946 946 (first damaged changeset appears to be 0)
947 947 [1]
948 948
949 949 Corrupt local files are not sent upstream. (The alternate dummy remote
950 950 avoids the corrupt lfs object in the original remote.)
951 951
952 952 $ mkdir $TESTTMP/dummy-remote2
953 953 $ hg init dest
954 954 $ hg -R fromcorrupt2 --config lfs.url=file:///$TESTTMP/dummy-remote2 push -v dest
955 955 pushing to dest
956 956 searching for changes
957 957 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
958 958 abort: detected corrupt lfs object: 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
959 959 (run hg verify)
960 960 [255]
961 961
962 962 $ hg -R fromcorrupt2 --config lfs.url=file:///$TESTTMP/dummy-remote2 verify -v
963 963 repository uses revlog format 1
964 964 checking changesets
965 965 checking manifests
966 966 crosschecking files in changesets and manifests
967 967 checking files
968 968 l@1: unpacking 46a2f24864bc: integrity check failed on data/l.i:0
969 969 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
970 970 large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0
971 971 lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store
972 972 lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store
973 973 checked 5 changesets with 10 changes to 4 files
974 974 2 integrity errors encountered!
975 975 (first damaged changeset appears to be 0)
976 976 [1]
977 977
978 978 $ cat $TESTTMP/dummy-remote2/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b | $TESTDIR/f --sha256
979 979 sha256=22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b
980 980 $ cat fromcorrupt2/.hg/store/lfs/objects/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b | $TESTDIR/f --sha256
981 981 sha256=22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b
982 982 $ test -f $TESTTMP/dummy-remote2/66/100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
983 983 [1]
984 984
985 985 Accessing a corrupt file will complain
986 986
987 987 $ hg --cwd fromcorrupt2 cat -r 0 large
988 988 abort: integrity check failed on data/large.i:0!
989 989 [255]
990 990
991 991 lfs -> normal -> lfs round trip conversions are possible. The 'none()'
992 992 predicate on the command line will override whatever is configured globally and
993 993 locally, and ensures everything converts to a regular file. For lfs -> normal,
994 994 there's no 'lfs' destination repo requirement. For normal -> lfs, there is.
995 995
996 996 $ hg --config extensions.convert= --config 'lfs.track=none()' \
997 997 > convert repo8 convert_normal
998 998 initializing destination convert_normal repository
999 999 scanning source...
1000 1000 sorting...
1001 1001 converting...
1002 1002 2 a
1003 1003 1 b
1004 1004 0 meta
1005 1005 $ grep 'lfs' convert_normal/.hg/requires
1006 1006 [1]
1007 1007 $ hg --cwd convert_normal cat a1 -r 0 -T '{rawdata}'
1008 1008 THIS-IS-LFS-BECAUSE-10-BYTES
1009 1009
1010 1010 $ hg --config extensions.convert= --config lfs.threshold=10B \
1011 1011 > convert convert_normal convert_lfs
1012 1012 initializing destination convert_lfs repository
1013 1013 scanning source...
1014 1014 sorting...
1015 1015 converting...
1016 1016 2 a
1017 1017 1 b
1018 1018 0 meta
1019 1019
1020 1020 $ hg --cwd convert_lfs cat -r 0 a1 -T '{rawdata}'
1021 1021 version https://git-lfs.github.com/spec/v1
1022 1022 oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
1023 1023 size 29
1024 1024 x-is-binary 0
1025 1025 $ hg --cwd convert_lfs debugdata a1 0
1026 1026 version https://git-lfs.github.com/spec/v1
1027 1027 oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
1028 1028 size 29
1029 1029 x-is-binary 0
1030 1030 $ hg --cwd convert_lfs log -r 0 -T "{lfs_files % '{lfspointer % '{key}={value}\n'}'}"
1031 1031 version=https://git-lfs.github.com/spec/v1
1032 1032 oid=sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
1033 1033 size=29
1034 1034 x-is-binary=0
1035 1035 $ hg --cwd convert_lfs log -r 0 \
1036 1036 > -T '{lfs_files % "{get(lfspointer, "oid")}\n"}{lfs_files % "{lfspointer.oid}\n"}'
1037 1037 sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
1038 1038 sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
1039 1039 $ hg --cwd convert_lfs log -r 0 -T '{lfs_files % "{lfspointer}\n"}'
1040 1040 version=https://git-lfs.github.com/spec/v1 oid=sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024 size=29 x-is-binary=0
1041 1041 $ hg --cwd convert_lfs \
1042 1042 > log -r 'all()' -T '{rev}: {lfs_files % "{file}: {lfsoid}\n"}'
1043 1043 0: a1: 5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
1044 1044 1: a2: 5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
1045 1045 2: a2: 876dadc86a8542f9798048f2c47f51dbf8e4359aed883e8ec80c5db825f0d943
1046 1046
1047 1047 $ grep 'lfs' convert_lfs/.hg/requires
1048 1048 lfs
1049 1049
1050 1050 The hashes in all stages of the conversion are unchanged.
1051 1051
1052 1052 $ hg -R repo8 log -T '{node|short}\n'
1053 1053 0fae949de7fa
1054 1054 9cd6bdffdac0
1055 1055 7f96794915f7
1056 1056 $ hg -R convert_normal log -T '{node|short}\n'
1057 1057 0fae949de7fa
1058 1058 9cd6bdffdac0
1059 1059 7f96794915f7
1060 1060 $ hg -R convert_lfs log -T '{node|short}\n'
1061 1061 0fae949de7fa
1062 1062 9cd6bdffdac0
1063 1063 7f96794915f7
1064 1064
1065 1065 This convert is trickier, because it contains deleted files (via `hg mv`)
1066 1066
1067 1067 $ hg --config extensions.convert= --config lfs.threshold=1000M \
1068 1068 > convert repo3 convert_normal2
1069 1069 initializing destination convert_normal2 repository
1070 1070 scanning source...
1071 1071 sorting...
1072 1072 converting...
1073 1073 4 commit with lfs content
1074 1074 3 renames
1075 1075 2 large to small, small to large
1076 1076 1 random modifications
1077 1077 0 switch large and small again
1078 1078 $ grep 'lfs' convert_normal2/.hg/requires
1079 1079 [1]
1080 1080 $ hg --cwd convert_normal2 debugdata large 0
1081 1081 LONGER-THAN-TEN-BYTES-WILL-TRIGGER-LFS
1082 1082
1083 1083 $ hg --config extensions.convert= --config lfs.threshold=10B \
1084 1084 > convert convert_normal2 convert_lfs2
1085 1085 initializing destination convert_lfs2 repository
1086 1086 scanning source...
1087 1087 sorting...
1088 1088 converting...
1089 1089 4 commit with lfs content
1090 1090 3 renames
1091 1091 2 large to small, small to large
1092 1092 1 random modifications
1093 1093 0 switch large and small again
1094 1094 $ grep 'lfs' convert_lfs2/.hg/requires
1095 1095 lfs
1096 1096 $ hg --cwd convert_lfs2 debugdata large 0
1097 1097 version https://git-lfs.github.com/spec/v1
1098 1098 oid sha256:66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
1099 1099 size 39
1100 1100 x-is-binary 0
1101 1101
1102 1102 Committing deleted files works:
1103 1103
1104 1104 $ hg init $TESTTMP/repo-del
1105 1105 $ cd $TESTTMP/repo-del
1106 1106 $ echo 1 > A
1107 1107 $ hg commit -m 'add A' -A A
1108 1108 $ hg rm A
1109 1109 $ hg commit -m 'rm A'
1110 1110
1111 1111 Bad .hglfs files will block the commit with a useful message
1112 1112
1113 1113 $ cat > .hglfs << EOF
1114 1114 > [track]
1115 1115 > **.test = size(">5B")
1116 1116 > bad file ... no commit
1117 1117 > EOF
1118 1118
1119 1119 $ echo x > file.txt
1120 1120 $ hg ci -Aqm 'should fail'
1121 hg: parse error at .hglfs:3: bad file ... no commit
1122 [255]
1121 config error at .hglfs:3: bad file ... no commit
1122 [30]
1123 1123
1124 1124 $ cat > .hglfs << EOF
1125 1125 > [track]
1126 1126 > **.test = size(">5B")
1127 1127 > ** = nonexistent()
1128 1128 > EOF
1129 1129
1130 1130 $ hg ci -Aqm 'should fail'
1131 1131 abort: parse error in .hglfs: unknown identifier: nonexistent
1132 1132 [255]
1133 1133
1134 1134 '**' works out to mean all files.
1135 1135
1136 1136 $ cat > .hglfs << EOF
1137 1137 > [track]
1138 1138 > path:.hglfs = none()
1139 1139 > **.test = size(">5B")
1140 1140 > **.exclude = none()
1141 1141 > ** = size(">10B")
1142 1142 > EOF
1143 1143
1144 1144 The LFS policy takes effect without tracking the .hglfs file
1145 1145
1146 1146 $ echo 'largefile' > lfs.test
1147 1147 $ echo '012345678901234567890' > nolfs.exclude
1148 1148 $ echo '01234567890123456' > lfs.catchall
1149 1149 $ hg add *
1150 1150 $ hg ci -qm 'before add .hglfs'
1151 1151 $ hg log -r . -T '{rev}: {lfs_files % "{file}: {lfsoid}\n"}\n'
1152 1152 2: lfs.catchall: d4ec46c2869ba22eceb42a729377432052d9dd75d82fc40390ebaadecee87ee9
1153 1153 lfs.test: 5489e6ced8c36a7b267292bde9fd5242a5f80a7482e8f23fa0477393dfaa4d6c
1154 1154
1155 1155 The .hglfs file works when tracked
1156 1156
1157 1157 $ echo 'largefile2' > lfs.test
1158 1158 $ echo '012345678901234567890a' > nolfs.exclude
1159 1159 $ echo '01234567890123456a' > lfs.catchall
1160 1160 $ hg ci -Aqm 'after adding .hglfs'
1161 1161 $ hg log -r . -T '{rev}: {lfs_files % "{file}: {lfsoid}\n"}\n'
1162 1162 3: lfs.catchall: 31f43b9c62b540126b0ad5884dc013d21a61c9329b77de1fceeae2fc58511573
1163 1163 lfs.test: 8acd23467967bc7b8cc5a280056589b0ba0b17ff21dbd88a7b6474d6290378a6
1164 1164
1165 1165 The LFS policy stops when the .hglfs is gone
1166 1166
1167 1167 $ mv .hglfs .hglfs_
1168 1168 $ echo 'largefile3' > lfs.test
1169 1169 $ echo '012345678901234567890abc' > nolfs.exclude
1170 1170 $ echo '01234567890123456abc' > lfs.catchall
1171 1171 $ hg ci -qm 'file test' -X .hglfs
1172 1172 $ hg log -r . -T '{rev}: {lfs_files % "{file}: {lfsoid}\n"}\n'
1173 1173 4:
1174 1174
1175 1175 $ mv .hglfs_ .hglfs
1176 1176 $ echo '012345678901234567890abc' > lfs.test
1177 1177 $ hg ci -m 'back to lfs'
1178 1178 $ hg rm lfs.test
1179 1179 $ hg ci -qm 'remove lfs'
1180 1180
1181 1181 {lfs_files} will list deleted files too
1182 1182
1183 1183 $ hg log -T "{lfs_files % '{rev} {file}: {lfspointer.oid}\n'}"
1184 1184 6 lfs.test:
1185 1185 5 lfs.test: sha256:43f8f41171b6f62a6b61ba4ce98a8a6c1649240a47ebafd43120aa215ac9e7f6
1186 1186 3 lfs.catchall: sha256:31f43b9c62b540126b0ad5884dc013d21a61c9329b77de1fceeae2fc58511573
1187 1187 3 lfs.test: sha256:8acd23467967bc7b8cc5a280056589b0ba0b17ff21dbd88a7b6474d6290378a6
1188 1188 2 lfs.catchall: sha256:d4ec46c2869ba22eceb42a729377432052d9dd75d82fc40390ebaadecee87ee9
1189 1189 2 lfs.test: sha256:5489e6ced8c36a7b267292bde9fd5242a5f80a7482e8f23fa0477393dfaa4d6c
1190 1190
1191 1191 $ hg log -r 'file("set:lfs()")' -T '{rev} {join(lfs_files, ", ")}\n'
1192 1192 2 lfs.catchall, lfs.test
1193 1193 3 lfs.catchall, lfs.test
1194 1194 5 lfs.test
1195 1195 6 lfs.test
1196 1196
1197 1197 $ cd ..
1198 1198
1199 1199 Unbundling adds a requirement to a non-lfs repo, if necessary.
1200 1200
1201 1201 $ hg bundle -R $TESTTMP/repo-del -qr 0 --base null nolfs.hg
1202 1202 $ hg bundle -R convert_lfs2 -qr tip --base null lfs.hg
1203 1203 $ hg init unbundle
1204 1204 $ hg pull -R unbundle -q nolfs.hg
1205 1205 $ grep lfs unbundle/.hg/requires
1206 1206 [1]
1207 1207 $ hg pull -R unbundle -q lfs.hg
1208 1208 $ grep lfs unbundle/.hg/requires
1209 1209 lfs
1210 1210
1211 1211 $ hg init no_lfs
1212 1212 $ cat >> no_lfs/.hg/hgrc <<EOF
1213 1213 > [experimental]
1214 1214 > changegroup3 = True
1215 1215 > [extensions]
1216 1216 > lfs=!
1217 1217 > EOF
1218 1218 $ cp -R no_lfs no_lfs2
1219 1219
1220 1220 Pushing from a local lfs repo to a local repo without an lfs requirement and
1221 1221 with lfs disabled, fails.
1222 1222
1223 1223 $ hg push -R convert_lfs2 no_lfs
1224 1224 pushing to no_lfs
1225 1225 abort: required features are not supported in the destination: lfs
1226 1226 [255]
1227 1227 $ grep lfs no_lfs/.hg/requires
1228 1228 [1]
1229 1229
1230 1230 Pulling from a local lfs repo to a local repo without an lfs requirement and
1231 1231 with lfs disabled, fails.
1232 1232
1233 1233 $ hg pull -R no_lfs2 convert_lfs2
1234 1234 pulling from convert_lfs2
1235 1235 abort: required features are not supported in the destination: lfs
1236 1236 [255]
1237 1237 $ grep lfs no_lfs2/.hg/requires
1238 1238 [1]
@@ -1,439 +1,439 b''
1 1 $ cat <<EOF > merge
2 2 > from __future__ import print_function
3 3 > import sys, os
4 4 >
5 5 > try:
6 6 > import msvcrt
7 7 > msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
8 8 > msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
9 9 > except ImportError:
10 10 > pass
11 11 >
12 12 > print("merging for", os.path.basename(sys.argv[1]))
13 13 > EOF
14 14 $ HGMERGE="\"$PYTHON\" ../merge"; export HGMERGE
15 15
16 16 $ hg init t
17 17 $ cd t
18 18 $ echo This is file a1 > a
19 19 $ hg add a
20 20 $ hg commit -m "commit #0"
21 21 $ echo This is file b1 > b
22 22 $ hg add b
23 23 $ hg commit -m "commit #1"
24 24
25 25 $ hg update 0
26 26 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
27 27
28 28 Test interrupted updates by having a non-empty dir with the same name as one
29 29 of the files in a commit we're updating to
30 30
31 31 $ mkdir b && touch b/nonempty
32 32 $ hg up
33 33 abort: Unlinking directory not permitted: *$TESTTMP/t/b* (glob) (windows !)
34 34 abort: Directory not empty: '?\$TESTTMP/t/b'? (re) (no-windows !)
35 35 [255]
36 36 $ hg ci
37 37 abort: last update was interrupted
38 38 (use 'hg update' to get a consistent checkout)
39 39 [20]
40 40 $ hg sum
41 41 parent: 0:538afb845929
42 42 commit #0
43 43 branch: default
44 44 commit: 1 unknown (interrupted update)
45 45 update: 1 new changesets (update)
46 46 phases: 2 draft
47 47 Detect interrupted update by hg status --verbose
48 48 $ hg status -v
49 49 ? b/nonempty
50 50 # The repository is in an unfinished *update* state.
51 51
52 52 # To continue: hg update .
53 53
54 54
55 55 $ rm b/nonempty
56 56
57 57 $ hg up
58 58 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
59 59 $ hg sum
60 60 parent: 1:b8bb4a988f25 tip
61 61 commit #1
62 62 branch: default
63 63 commit: (clean)
64 64 update: (current)
65 65 phases: 2 draft
66 66
67 67 Prepare a basic merge
68 68
69 69 $ hg up 0
70 70 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
71 71 $ echo This is file c1 > c
72 72 $ hg add c
73 73 $ hg commit -m "commit #2"
74 74 created new head
75 75 $ echo This is file b1 > b
76 76 no merges expected
77 77 $ hg merge -P 1
78 78 changeset: 1:b8bb4a988f25
79 79 user: test
80 80 date: Thu Jan 01 00:00:00 1970 +0000
81 81 summary: commit #1
82 82
83 83 $ hg merge 1
84 84 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
85 85 (branch merge, don't forget to commit)
86 86 $ hg diff --nodates
87 87 diff -r 49035e18a8e6 b
88 88 --- /dev/null
89 89 +++ b/b
90 90 @@ -0,0 +1,1 @@
91 91 +This is file b1
92 92 $ hg status
93 93 M b
94 94 $ cd ..; rm -r t
95 95
96 96 $ hg init t
97 97 $ cd t
98 98 $ echo This is file a1 > a
99 99 $ hg add a
100 100 $ hg commit -m "commit #0"
101 101 $ echo This is file b1 > b
102 102 $ hg add b
103 103 $ hg commit -m "commit #1"
104 104
105 105 $ hg update 0
106 106 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
107 107 $ echo This is file c1 > c
108 108 $ hg add c
109 109 $ hg commit -m "commit #2"
110 110 created new head
111 111 $ echo This is file b2 > b
112 112 merge should fail
113 113 $ hg merge 1
114 114 b: untracked file differs
115 115 abort: untracked files in working directory differ from files in requested revision
116 116 [255]
117 117
118 118 #if symlink
119 119 symlinks to directories should be treated as regular files (issue5027)
120 120 $ rm b
121 121 $ ln -s 'This is file b2' b
122 122 $ hg merge 1
123 123 b: untracked file differs
124 124 abort: untracked files in working directory differ from files in requested revision
125 125 [255]
126 126 symlinks shouldn't be followed
127 127 $ rm b
128 128 $ echo This is file b1 > .hg/b
129 129 $ ln -s .hg/b b
130 130 $ hg merge 1
131 131 b: untracked file differs
132 132 abort: untracked files in working directory differ from files in requested revision
133 133 [255]
134 134
135 135 $ rm b
136 136 $ echo This is file b2 > b
137 137 #endif
138 138
139 139 bad config
140 140 $ hg merge 1 --config merge.checkunknown=x
141 abort: merge.checkunknown not valid ('x' is none of 'abort', 'ignore', 'warn')
141 config error: merge.checkunknown not valid ('x' is none of 'abort', 'ignore', 'warn')
142 142 [30]
143 143 this merge should fail
144 144 $ hg merge 1 --config merge.checkunknown=abort
145 145 b: untracked file differs
146 146 abort: untracked files in working directory differ from files in requested revision
147 147 [255]
148 148
149 149 this merge should warn
150 150 $ hg merge 1 --config merge.checkunknown=warn
151 151 b: replacing untracked file
152 152 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
153 153 (branch merge, don't forget to commit)
154 154 $ cat b.orig
155 155 This is file b2
156 156 $ hg up --clean 2
157 157 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
158 158 $ mv b.orig b
159 159
160 160 this merge should silently ignore
161 161 $ cat b
162 162 This is file b2
163 163 $ hg merge 1 --config merge.checkunknown=ignore
164 164 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
165 165 (branch merge, don't forget to commit)
166 166
167 167 merge.checkignored
168 168 $ hg up --clean 1
169 169 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
170 170 $ cat >> .hgignore << EOF
171 171 > remoteignored
172 172 > EOF
173 173 $ echo This is file localignored3 > localignored
174 174 $ echo This is file remoteignored3 > remoteignored
175 175 $ hg add .hgignore localignored remoteignored
176 176 $ hg commit -m "commit #3"
177 177
178 178 $ hg up 2
179 179 1 files updated, 0 files merged, 4 files removed, 0 files unresolved
180 180 $ cat >> .hgignore << EOF
181 181 > localignored
182 182 > EOF
183 183 $ hg add .hgignore
184 184 $ hg commit -m "commit #4"
185 185
186 186 remote .hgignore shouldn't be used for determining whether a file is ignored
187 187 $ echo This is file remoteignored4 > remoteignored
188 188 $ hg merge 3 --config merge.checkignored=ignore --config merge.checkunknown=abort
189 189 remoteignored: untracked file differs
190 190 abort: untracked files in working directory differ from files in requested revision
191 191 [255]
192 192 $ hg merge 3 --config merge.checkignored=abort --config merge.checkunknown=ignore
193 193 merging .hgignore
194 194 merging for .hgignore
195 195 3 files updated, 1 files merged, 0 files removed, 0 files unresolved
196 196 (branch merge, don't forget to commit)
197 197 $ cat remoteignored
198 198 This is file remoteignored3
199 199 $ cat remoteignored.orig
200 200 This is file remoteignored4
201 201 $ rm remoteignored.orig
202 202
203 203 local .hgignore should be used for that
204 204 $ hg up --clean 4
205 205 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
206 206 $ echo This is file localignored4 > localignored
207 207 also test other conflicting files to see we output the full set of warnings
208 208 $ echo This is file b2 > b
209 209 $ hg merge 3 --config merge.checkignored=abort --config merge.checkunknown=abort
210 210 b: untracked file differs
211 211 localignored: untracked file differs
212 212 abort: untracked files in working directory differ from files in requested revision
213 213 [255]
214 214 $ hg merge 3 --config merge.checkignored=abort --config merge.checkunknown=ignore
215 215 localignored: untracked file differs
216 216 abort: untracked files in working directory differ from files in requested revision
217 217 [255]
218 218 $ hg merge 3 --config merge.checkignored=warn --config merge.checkunknown=abort
219 219 b: untracked file differs
220 220 abort: untracked files in working directory differ from files in requested revision
221 221 [255]
222 222 $ hg merge 3 --config merge.checkignored=warn --config merge.checkunknown=warn
223 223 b: replacing untracked file
224 224 localignored: replacing untracked file
225 225 merging .hgignore
226 226 merging for .hgignore
227 227 3 files updated, 1 files merged, 0 files removed, 0 files unresolved
228 228 (branch merge, don't forget to commit)
229 229 $ cat localignored
230 230 This is file localignored3
231 231 $ cat localignored.orig
232 232 This is file localignored4
233 233 $ rm localignored.orig
234 234
235 235 $ cat b.orig
236 236 This is file b2
237 237 $ hg up --clean 2
238 238 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
239 239 $ mv b.orig b
240 240
241 241 this merge of b should work
242 242 $ cat b
243 243 This is file b2
244 244 $ hg merge -f 1
245 245 merging b
246 246 merging for b
247 247 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
248 248 (branch merge, don't forget to commit)
249 249 $ hg diff --nodates
250 250 diff -r 49035e18a8e6 b
251 251 --- /dev/null
252 252 +++ b/b
253 253 @@ -0,0 +1,1 @@
254 254 +This is file b2
255 255 $ hg status
256 256 M b
257 257 $ cd ..; rm -r t
258 258
259 259 $ hg init t
260 260 $ cd t
261 261 $ echo This is file a1 > a
262 262 $ hg add a
263 263 $ hg commit -m "commit #0"
264 264 $ echo This is file b1 > b
265 265 $ hg add b
266 266 $ hg commit -m "commit #1"
267 267 $ echo This is file b22 > b
268 268 $ hg commit -m "commit #2"
269 269 $ hg update 1
270 270 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
271 271 $ echo This is file c1 > c
272 272 $ hg add c
273 273 $ hg commit -m "commit #3"
274 274 created new head
275 275
276 276 Contents of b should be "this is file b1"
277 277 $ cat b
278 278 This is file b1
279 279
280 280 $ echo This is file b22 > b
281 281 merge fails
282 282 $ hg merge 2
283 283 abort: uncommitted changes
284 284 (use 'hg status' to list changes)
285 285 [255]
286 286 merge expected!
287 287 $ hg merge -f 2
288 288 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
289 289 (branch merge, don't forget to commit)
290 290 $ hg diff --nodates
291 291 diff -r 85de557015a8 b
292 292 --- a/b
293 293 +++ b/b
294 294 @@ -1,1 +1,1 @@
295 295 -This is file b1
296 296 +This is file b22
297 297 $ hg status
298 298 M b
299 299 $ cd ..; rm -r t
300 300
301 301 $ hg init t
302 302 $ cd t
303 303 $ echo This is file a1 > a
304 304 $ hg add a
305 305 $ hg commit -m "commit #0"
306 306 $ echo This is file b1 > b
307 307 $ hg add b
308 308 $ hg commit -m "commit #1"
309 309 $ echo This is file b22 > b
310 310 $ hg commit -m "commit #2"
311 311 $ hg update 1
312 312 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
313 313 $ echo This is file c1 > c
314 314 $ hg add c
315 315 $ hg commit -m "commit #3"
316 316 created new head
317 317 $ echo This is file b33 > b
318 318 merge of b should fail
319 319 $ hg merge 2
320 320 abort: uncommitted changes
321 321 (use 'hg status' to list changes)
322 322 [255]
323 323 merge of b expected
324 324 $ hg merge -f 2
325 325 merging b
326 326 merging for b
327 327 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
328 328 (branch merge, don't forget to commit)
329 329 $ hg diff --nodates
330 330 diff -r 85de557015a8 b
331 331 --- a/b
332 332 +++ b/b
333 333 @@ -1,1 +1,1 @@
334 334 -This is file b1
335 335 +This is file b33
336 336 $ hg status
337 337 M b
338 338
339 339 Test for issue2364
340 340
341 341 $ hg up -qC .
342 342 $ hg rm b
343 343 $ hg ci -md
344 344 $ hg revert -r -2 b
345 345 $ hg up -q -- -2
346 346
347 347 Test that updated files are treated as "modified", when
348 348 'merge.update()' is aborted before 'merge.recordupdates()' (= parents
349 349 aren't changed), even if none of mode, size and timestamp of them
350 350 isn't changed on the filesystem (see also issue4583).
351 351
352 352 $ cat > $TESTTMP/abort.py <<EOF
353 353 > from __future__ import absolute_import
354 354 > # emulate aborting before "recordupdates()". in this case, files
355 355 > # are changed without updating dirstate
356 356 > from mercurial import (
357 357 > error,
358 358 > extensions,
359 359 > merge,
360 360 > )
361 361 > def applyupdates(orig, *args, **kwargs):
362 362 > orig(*args, **kwargs)
363 363 > raise error.Abort(b'intentional aborting')
364 364 > def extsetup(ui):
365 365 > extensions.wrapfunction(merge, "applyupdates", applyupdates)
366 366 > EOF
367 367
368 368 $ cat >> .hg/hgrc <<EOF
369 369 > [fakedirstatewritetime]
370 370 > # emulate invoking dirstate.write() via repo.status()
371 371 > # at 2000-01-01 00:00
372 372 > fakenow = 200001010000
373 373 > EOF
374 374
375 375 (file gotten from other revision)
376 376
377 377 $ hg update -q -C 2
378 378 $ echo 'THIS IS FILE B5' > b
379 379 $ hg commit -m 'commit #5'
380 380
381 381 $ hg update -q -C 3
382 382 $ cat b
383 383 This is file b1
384 384 $ touch -t 200001010000 b
385 385 $ hg debugrebuildstate
386 386
387 387 $ cat >> .hg/hgrc <<EOF
388 388 > [extensions]
389 389 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
390 390 > abort = $TESTTMP/abort.py
391 391 > EOF
392 392 $ hg merge 5
393 393 abort: intentional aborting
394 394 [255]
395 395 $ cat >> .hg/hgrc <<EOF
396 396 > [extensions]
397 397 > fakedirstatewritetime = !
398 398 > abort = !
399 399 > EOF
400 400
401 401 $ cat b
402 402 THIS IS FILE B5
403 403 $ touch -t 200001010000 b
404 404 $ hg status -A b
405 405 M b
406 406
407 407 (file merged from other revision)
408 408
409 409 $ hg update -q -C 3
410 410 $ echo 'this is file b6' > b
411 411 $ hg commit -m 'commit #6'
412 412 created new head
413 413
414 414 $ cat b
415 415 this is file b6
416 416 $ touch -t 200001010000 b
417 417 $ hg debugrebuildstate
418 418
419 419 $ cat >> .hg/hgrc <<EOF
420 420 > [extensions]
421 421 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
422 422 > abort = $TESTTMP/abort.py
423 423 > EOF
424 424 $ hg merge --tool internal:other 5
425 425 abort: intentional aborting
426 426 [255]
427 427 $ cat >> .hg/hgrc <<EOF
428 428 > [extensions]
429 429 > fakedirstatewritetime = !
430 430 > abort = !
431 431 > EOF
432 432
433 433 $ cat b
434 434 THIS IS FILE B5
435 435 $ touch -t 200001010000 b
436 436 $ hg status -A b
437 437 M b
438 438
439 439 $ cd ..
@@ -1,1054 +1,1054 b''
1 1 $ cat > $TESTTMP/hook.sh << 'EOF'
2 2 > echo "test-hook-close-phase: $HG_NODE: $HG_OLDPHASE -> $HG_PHASE"
3 3 > EOF
4 4
5 5 $ cat >> $HGRCPATH << EOF
6 6 > [extensions]
7 7 > phasereport=$TESTDIR/testlib/ext-phase-report.py
8 8 > [hooks]
9 9 > txnclose-phase.test = sh $TESTTMP/hook.sh
10 10 > EOF
11 11
12 12 $ hglog() { hg log --template "{rev} {phaseidx} {desc}\n" $*; }
13 13 $ mkcommit() {
14 14 > echo "$1" > "$1"
15 15 > hg add "$1"
16 16 > message="$1"
17 17 > shift
18 18 > hg ci -m "$message" $*
19 19 > }
20 20
21 21 $ hg init initialrepo
22 22 $ cd initialrepo
23 23
24 24 Cannot change null revision phase
25 25
26 26 $ hg phase --force --secret null
27 27 abort: cannot change null revision phase
28 28 [255]
29 29 $ hg phase null
30 30 -1: public
31 31
32 32 $ mkcommit A
33 33 test-debug-phase: new rev 0: x -> 1
34 34 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> draft
35 35
36 36 New commit are draft by default
37 37
38 38 $ hglog
39 39 0 1 A
40 40
41 41 Following commit are draft too
42 42
43 43 $ mkcommit B
44 44 test-debug-phase: new rev 1: x -> 1
45 45 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> draft
46 46
47 47 $ hglog
48 48 1 1 B
49 49 0 1 A
50 50
51 51 Working directory phase is secret when its parent is secret.
52 52
53 53 $ hg phase --force --secret .
54 54 test-debug-phase: move rev 0: 1 -> 2
55 55 test-debug-phase: move rev 1: 1 -> 2
56 56 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: draft -> secret
57 57 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: draft -> secret
58 58 $ hg log -r 'wdir()' -T '{phase}\n'
59 59 secret
60 60 $ hg log -r 'wdir() and public()' -T '{phase}\n'
61 61 $ hg log -r 'wdir() and draft()' -T '{phase}\n'
62 62 $ hg log -r 'wdir() and secret()' -T '{phase}\n'
63 63 secret
64 64
65 65 Working directory phase is draft when its parent is draft.
66 66
67 67 $ hg phase --draft .
68 68 test-debug-phase: move rev 1: 2 -> 1
69 69 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: secret -> draft
70 70 $ hg log -r 'wdir()' -T '{phase}\n'
71 71 draft
72 72 $ hg log -r 'wdir() and public()' -T '{phase}\n'
73 73 $ hg log -r 'wdir() and draft()' -T '{phase}\n'
74 74 draft
75 75 $ hg log -r 'wdir() and secret()' -T '{phase}\n'
76 76
77 77 Working directory phase is secret when a new commit will be created as secret,
78 78 even if the parent is draft.
79 79
80 80 $ hg log -r 'wdir() and secret()' -T '{phase}\n' \
81 81 > --config phases.new-commit='secret'
82 82 secret
83 83
84 84 Working directory phase is draft when its parent is public.
85 85
86 86 $ hg phase --public .
87 87 test-debug-phase: move rev 0: 1 -> 0
88 88 test-debug-phase: move rev 1: 1 -> 0
89 89 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: draft -> public
90 90 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: draft -> public
91 91 $ hg log -r 'wdir()' -T '{phase}\n'
92 92 draft
93 93 $ hg log -r 'wdir() and public()' -T '{phase}\n'
94 94 $ hg log -r 'wdir() and draft()' -T '{phase}\n'
95 95 draft
96 96 $ hg log -r 'wdir() and secret()' -T '{phase}\n'
97 97 $ hg log -r 'wdir() and secret()' -T '{phase}\n' \
98 98 > --config phases.new-commit='secret'
99 99 secret
100 100
101 101 Draft commit are properly created over public one:
102 102
103 103 $ hg phase
104 104 1: public
105 105 $ hglog
106 106 1 0 B
107 107 0 0 A
108 108
109 109 $ mkcommit C
110 110 test-debug-phase: new rev 2: x -> 1
111 111 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> draft
112 112 $ mkcommit D
113 113 test-debug-phase: new rev 3: x -> 1
114 114 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> draft
115 115
116 116 $ hglog
117 117 3 1 D
118 118 2 1 C
119 119 1 0 B
120 120 0 0 A
121 121
122 122 Test creating changeset as secret
123 123
124 124 $ mkcommit E --config phases.new-commit='secret'
125 125 test-debug-phase: new rev 4: x -> 2
126 126 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: -> secret
127 127 $ hglog
128 128 4 2 E
129 129 3 1 D
130 130 2 1 C
131 131 1 0 B
132 132 0 0 A
133 133
134 134 Test the secret property is inherited
135 135
136 136 $ mkcommit H
137 137 test-debug-phase: new rev 5: x -> 2
138 138 test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8: -> secret
139 139 $ hglog
140 140 5 2 H
141 141 4 2 E
142 142 3 1 D
143 143 2 1 C
144 144 1 0 B
145 145 0 0 A
146 146
147 147 Even on merge
148 148
149 149 $ hg up -q 1
150 150 $ mkcommit "B'"
151 151 test-debug-phase: new rev 6: x -> 1
152 152 created new head
153 153 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> draft
154 154 $ hglog
155 155 6 1 B'
156 156 5 2 H
157 157 4 2 E
158 158 3 1 D
159 159 2 1 C
160 160 1 0 B
161 161 0 0 A
162 162 $ hg merge 4 # E
163 163 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
164 164 (branch merge, don't forget to commit)
165 165 $ hg phase
166 166 6: draft
167 167 4: secret
168 168 $ hg ci -m "merge B' and E"
169 169 test-debug-phase: new rev 7: x -> 2
170 170 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: -> secret
171 171
172 172 $ hglog
173 173 7 2 merge B' and E
174 174 6 1 B'
175 175 5 2 H
176 176 4 2 E
177 177 3 1 D
178 178 2 1 C
179 179 1 0 B
180 180 0 0 A
181 181
182 182 Test secret changeset are not pushed
183 183
184 184 $ hg init ../push-dest
185 185 $ cat > ../push-dest/.hg/hgrc << EOF
186 186 > [phases]
187 187 > publish=False
188 188 > EOF
189 189 $ hg outgoing ../push-dest --template='{rev} {phase} {desc|firstline}\n'
190 190 comparing with ../push-dest
191 191 searching for changes
192 192 0 public A
193 193 1 public B
194 194 2 draft C
195 195 3 draft D
196 196 6 draft B'
197 197 $ hg outgoing -r 'branch(default)' ../push-dest --template='{rev} {phase} {desc|firstline}\n'
198 198 comparing with ../push-dest
199 199 searching for changes
200 200 0 public A
201 201 1 public B
202 202 2 draft C
203 203 3 draft D
204 204 6 draft B'
205 205
206 206 $ hg push ../push-dest -f # force because we push multiple heads
207 207 pushing to ../push-dest
208 208 searching for changes
209 209 adding changesets
210 210 adding manifests
211 211 adding file changes
212 212 added 5 changesets with 5 changes to 5 files (+1 heads)
213 213 test-debug-phase: new rev 0: x -> 0
214 214 test-debug-phase: new rev 1: x -> 0
215 215 test-debug-phase: new rev 2: x -> 1
216 216 test-debug-phase: new rev 3: x -> 1
217 217 test-debug-phase: new rev 4: x -> 1
218 218 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> public
219 219 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> public
220 220 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> draft
221 221 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> draft
222 222 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> draft
223 223 $ hglog
224 224 7 2 merge B' and E
225 225 6 1 B'
226 226 5 2 H
227 227 4 2 E
228 228 3 1 D
229 229 2 1 C
230 230 1 0 B
231 231 0 0 A
232 232 $ cd ../push-dest
233 233 $ hglog
234 234 4 1 B'
235 235 3 1 D
236 236 2 1 C
237 237 1 0 B
238 238 0 0 A
239 239
240 240 (Issue3303)
241 241 Check that remote secret changeset are ignore when checking creation of remote heads
242 242
243 243 We add a secret head into the push destination. This secret head shadows a
244 244 visible shared between the initial repo and the push destination.
245 245
246 246 $ hg up -q 4 # B'
247 247 $ mkcommit Z --config phases.new-commit=secret
248 248 test-debug-phase: new rev 5: x -> 2
249 249 test-hook-close-phase: 2713879da13d6eea1ff22b442a5a87cb31a7ce6a: -> secret
250 250 $ hg phase .
251 251 5: secret
252 252
253 253 We now try to push a new public changeset that descend from the common public
254 254 head shadowed by the remote secret head.
255 255
256 256 $ cd ../initialrepo
257 257 $ hg up -q 6 #B'
258 258 $ mkcommit I
259 259 test-debug-phase: new rev 8: x -> 1
260 260 created new head
261 261 test-hook-close-phase: 6d6770faffce199f1fddd1cf87f6f026138cf061: -> draft
262 262 $ hg push ../push-dest
263 263 pushing to ../push-dest
264 264 searching for changes
265 265 adding changesets
266 266 adding manifests
267 267 adding file changes
268 268 added 1 changesets with 1 changes to 1 files (+1 heads)
269 269 test-debug-phase: new rev 6: x -> 1
270 270 test-hook-close-phase: 6d6770faffce199f1fddd1cf87f6f026138cf061: -> draft
271 271
272 272 :note: The "(+1 heads)" is wrong as we do not had any visible head
273 273
274 274 check that branch cache with "served" filter are properly computed and stored
275 275
276 276 $ ls ../push-dest/.hg/cache/branch2*
277 277 ../push-dest/.hg/cache/branch2-base
278 278 ../push-dest/.hg/cache/branch2-served
279 279 $ cat ../push-dest/.hg/cache/branch2-served
280 280 6d6770faffce199f1fddd1cf87f6f026138cf061 6 465891ffab3c47a3c23792f7dc84156e19a90722
281 281 b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
282 282 6d6770faffce199f1fddd1cf87f6f026138cf061 o default
283 283 $ hg heads -R ../push-dest --template '{rev}:{node} {phase}\n' #update visible cache too
284 284 6:6d6770faffce199f1fddd1cf87f6f026138cf061 draft
285 285 5:2713879da13d6eea1ff22b442a5a87cb31a7ce6a secret
286 286 3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e draft
287 287 $ ls ../push-dest/.hg/cache/branch2*
288 288 ../push-dest/.hg/cache/branch2-base
289 289 ../push-dest/.hg/cache/branch2-served
290 290 ../push-dest/.hg/cache/branch2-visible
291 291 $ cat ../push-dest/.hg/cache/branch2-served
292 292 6d6770faffce199f1fddd1cf87f6f026138cf061 6 465891ffab3c47a3c23792f7dc84156e19a90722
293 293 b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
294 294 6d6770faffce199f1fddd1cf87f6f026138cf061 o default
295 295 $ cat ../push-dest/.hg/cache/branch2-visible
296 296 6d6770faffce199f1fddd1cf87f6f026138cf061 6
297 297 b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
298 298 2713879da13d6eea1ff22b442a5a87cb31a7ce6a o default
299 299 6d6770faffce199f1fddd1cf87f6f026138cf061 o default
300 300
301 301
302 302 Restore condition prior extra insertion.
303 303 $ hg -q --config extensions.mq= strip .
304 304 $ hg up -q 7
305 305 $ cd ..
306 306
307 307 Test secret changeset are not pull
308 308
309 309 $ hg init pull-dest
310 310 $ cd pull-dest
311 311 $ hg pull ../initialrepo
312 312 pulling from ../initialrepo
313 313 requesting all changes
314 314 adding changesets
315 315 adding manifests
316 316 adding file changes
317 317 added 5 changesets with 5 changes to 5 files (+1 heads)
318 318 new changesets 4a2df7238c3b:cf9fe039dfd6
319 319 test-debug-phase: new rev 0: x -> 0
320 320 test-debug-phase: new rev 1: x -> 0
321 321 test-debug-phase: new rev 2: x -> 0
322 322 test-debug-phase: new rev 3: x -> 0
323 323 test-debug-phase: new rev 4: x -> 0
324 324 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> public
325 325 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> public
326 326 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> public
327 327 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> public
328 328 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> public
329 329 (run 'hg heads' to see heads, 'hg merge' to merge)
330 330 $ hglog
331 331 4 0 B'
332 332 3 0 D
333 333 2 0 C
334 334 1 0 B
335 335 0 0 A
336 336 $ cd ..
337 337
338 338 But secret can still be bundled explicitly
339 339
340 340 $ cd initialrepo
341 341 $ hg bundle --base '4^' -r 'children(4)' ../secret-bundle.hg
342 342 4 changesets found
343 343 $ cd ..
344 344
345 345 Test secret changeset are not cloned
346 346 (during local clone)
347 347
348 348 $ hg clone -qU initialrepo clone-dest
349 349 test-debug-phase: new rev 0: x -> 0
350 350 test-debug-phase: new rev 1: x -> 0
351 351 test-debug-phase: new rev 2: x -> 0
352 352 test-debug-phase: new rev 3: x -> 0
353 353 test-debug-phase: new rev 4: x -> 0
354 354 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> public
355 355 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> public
356 356 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> public
357 357 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> public
358 358 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> public
359 359 $ hglog -R clone-dest
360 360 4 0 B'
361 361 3 0 D
362 362 2 0 C
363 363 1 0 B
364 364 0 0 A
365 365
366 366 Test summary
367 367
368 368 $ hg summary -R clone-dest --verbose
369 369 parent: -1:000000000000 (no revision checked out)
370 370 branch: default
371 371 commit: (clean)
372 372 update: 5 new changesets (update)
373 373 $ hg summary -R initialrepo
374 374 parent: 7:17a481b3bccb tip
375 375 merge B' and E
376 376 branch: default
377 377 commit: (clean) (secret)
378 378 update: 1 new changesets, 2 branch heads (merge)
379 379 phases: 3 draft, 3 secret
380 380 $ hg summary -R initialrepo --quiet
381 381 parent: 7:17a481b3bccb tip
382 382 update: 1 new changesets, 2 branch heads (merge)
383 383
384 384 Test revset
385 385
386 386 $ cd initialrepo
387 387 $ hglog -r 'public()'
388 388 0 0 A
389 389 1 0 B
390 390 $ hglog -r 'draft()'
391 391 2 1 C
392 392 3 1 D
393 393 6 1 B'
394 394 $ hglog -r 'secret()'
395 395 4 2 E
396 396 5 2 H
397 397 7 2 merge B' and E
398 398
399 399 test that phase are displayed in log at debug level
400 400
401 401 $ hg log --debug
402 402 changeset: 7:17a481b3bccb796c0521ae97903d81c52bfee4af
403 403 tag: tip
404 404 phase: secret
405 405 parent: 6:cf9fe039dfd67e829edf6522a45de057b5c86519
406 406 parent: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
407 407 manifest: 7:5e724ffacba267b2ab726c91fc8b650710deaaa8
408 408 user: test
409 409 date: Thu Jan 01 00:00:00 1970 +0000
410 410 files+: C D E
411 411 extra: branch=default
412 412 description:
413 413 merge B' and E
414 414
415 415
416 416 changeset: 6:cf9fe039dfd67e829edf6522a45de057b5c86519
417 417 phase: draft
418 418 parent: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
419 419 parent: -1:0000000000000000000000000000000000000000
420 420 manifest: 6:ab8bfef2392903058bf4ebb9e7746e8d7026b27a
421 421 user: test
422 422 date: Thu Jan 01 00:00:00 1970 +0000
423 423 files+: B'
424 424 extra: branch=default
425 425 description:
426 426 B'
427 427
428 428
429 429 changeset: 5:a030c6be5127abc010fcbff1851536552e6951a8
430 430 phase: secret
431 431 parent: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
432 432 parent: -1:0000000000000000000000000000000000000000
433 433 manifest: 5:5c710aa854874fe3d5fa7192e77bdb314cc08b5a
434 434 user: test
435 435 date: Thu Jan 01 00:00:00 1970 +0000
436 436 files+: H
437 437 extra: branch=default
438 438 description:
439 439 H
440 440
441 441
442 442 changeset: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
443 443 phase: secret
444 444 parent: 3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e
445 445 parent: -1:0000000000000000000000000000000000000000
446 446 manifest: 4:7173fd1c27119750b959e3a0f47ed78abe75d6dc
447 447 user: test
448 448 date: Thu Jan 01 00:00:00 1970 +0000
449 449 files+: E
450 450 extra: branch=default
451 451 description:
452 452 E
453 453
454 454
455 455 changeset: 3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e
456 456 phase: draft
457 457 parent: 2:f838bfaca5c7226600ebcfd84f3c3c13a28d3757
458 458 parent: -1:0000000000000000000000000000000000000000
459 459 manifest: 3:6e1f4c47ecb533ffd0c8e52cdc88afb6cd39e20c
460 460 user: test
461 461 date: Thu Jan 01 00:00:00 1970 +0000
462 462 files+: D
463 463 extra: branch=default
464 464 description:
465 465 D
466 466
467 467
468 468 changeset: 2:f838bfaca5c7226600ebcfd84f3c3c13a28d3757
469 469 phase: draft
470 470 parent: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
471 471 parent: -1:0000000000000000000000000000000000000000
472 472 manifest: 2:66a5a01817fdf5239c273802b5b7618d051c89e4
473 473 user: test
474 474 date: Thu Jan 01 00:00:00 1970 +0000
475 475 files+: C
476 476 extra: branch=default
477 477 description:
478 478 C
479 479
480 480
481 481 changeset: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
482 482 phase: public
483 483 parent: 0:4a2df7238c3b48766b5e22fafbb8a2f506ec8256
484 484 parent: -1:0000000000000000000000000000000000000000
485 485 manifest: 1:cb5cbbc1bfbf24cc34b9e8c16914e9caa2d2a7fd
486 486 user: test
487 487 date: Thu Jan 01 00:00:00 1970 +0000
488 488 files+: B
489 489 extra: branch=default
490 490 description:
491 491 B
492 492
493 493
494 494 changeset: 0:4a2df7238c3b48766b5e22fafbb8a2f506ec8256
495 495 phase: public
496 496 parent: -1:0000000000000000000000000000000000000000
497 497 parent: -1:0000000000000000000000000000000000000000
498 498 manifest: 0:007d8c9d88841325f5c6b06371b35b4e8a2b1a83
499 499 user: test
500 500 date: Thu Jan 01 00:00:00 1970 +0000
501 501 files+: A
502 502 extra: branch=default
503 503 description:
504 504 A
505 505
506 506
507 507
508 508
509 509 (Issue3707)
510 510 test invalid phase name
511 511
512 512 $ mkcommit I --config phases.new-commit='babar'
513 513 transaction abort!
514 514 rollback completed
515 abort: phases.new-commit: not a valid phase name ('babar')
515 config error: phases.new-commit: not a valid phase name ('babar')
516 516 [30]
517 517 Test phase command
518 518 ===================
519 519
520 520 initial picture
521 521
522 522 $ hg log -G --template "{rev} {phase} {desc}\n"
523 523 @ 7 secret merge B' and E
524 524 |\
525 525 | o 6 draft B'
526 526 | |
527 527 +---o 5 secret H
528 528 | |
529 529 o | 4 secret E
530 530 | |
531 531 o | 3 draft D
532 532 | |
533 533 o | 2 draft C
534 534 |/
535 535 o 1 public B
536 536 |
537 537 o 0 public A
538 538
539 539
540 540 display changesets phase
541 541
542 542 (mixing -r and plain rev specification)
543 543
544 544 $ hg phase 1::4 -r 7
545 545 1: public
546 546 2: draft
547 547 3: draft
548 548 4: secret
549 549 7: secret
550 550
551 551
552 552 move changeset forward
553 553
554 554 (with -r option)
555 555
556 556 $ hg phase --public -r 2
557 557 test-debug-phase: move rev 2: 1 -> 0
558 558 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: draft -> public
559 559 $ hg log -G --template "{rev} {phase} {desc}\n"
560 560 @ 7 secret merge B' and E
561 561 |\
562 562 | o 6 draft B'
563 563 | |
564 564 +---o 5 secret H
565 565 | |
566 566 o | 4 secret E
567 567 | |
568 568 o | 3 draft D
569 569 | |
570 570 o | 2 public C
571 571 |/
572 572 o 1 public B
573 573 |
574 574 o 0 public A
575 575
576 576
577 577 move changeset backward
578 578
579 579 (without -r option)
580 580
581 581 $ hg phase --draft --force 2
582 582 test-debug-phase: move rev 2: 0 -> 1
583 583 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: public -> draft
584 584 $ hg log -G --template "{rev} {phase} {desc}\n"
585 585 @ 7 secret merge B' and E
586 586 |\
587 587 | o 6 draft B'
588 588 | |
589 589 +---o 5 secret H
590 590 | |
591 591 o | 4 secret E
592 592 | |
593 593 o | 3 draft D
594 594 | |
595 595 o | 2 draft C
596 596 |/
597 597 o 1 public B
598 598 |
599 599 o 0 public A
600 600
601 601
602 602 move changeset forward and backward
603 603
604 604 $ hg phase --draft --force 1::4
605 605 test-debug-phase: move rev 1: 0 -> 1
606 606 test-debug-phase: move rev 4: 2 -> 1
607 607 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: public -> draft
608 608 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: secret -> draft
609 609 $ hg log -G --template "{rev} {phase} {desc}\n"
610 610 @ 7 secret merge B' and E
611 611 |\
612 612 | o 6 draft B'
613 613 | |
614 614 +---o 5 secret H
615 615 | |
616 616 o | 4 draft E
617 617 | |
618 618 o | 3 draft D
619 619 | |
620 620 o | 2 draft C
621 621 |/
622 622 o 1 draft B
623 623 |
624 624 o 0 public A
625 625
626 626 test partial failure
627 627
628 628 $ hg phase --public 7
629 629 test-debug-phase: move rev 1: 1 -> 0
630 630 test-debug-phase: move rev 2: 1 -> 0
631 631 test-debug-phase: move rev 3: 1 -> 0
632 632 test-debug-phase: move rev 4: 1 -> 0
633 633 test-debug-phase: move rev 6: 1 -> 0
634 634 test-debug-phase: move rev 7: 2 -> 0
635 635 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: draft -> public
636 636 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: draft -> public
637 637 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: draft -> public
638 638 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: draft -> public
639 639 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: draft -> public
640 640 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: secret -> public
641 641 $ hg phase --draft '5 or 7'
642 642 test-debug-phase: move rev 5: 2 -> 1
643 643 test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8: secret -> draft
644 644 cannot move 1 changesets to a higher phase, use --force
645 645 phase changed for 1 changesets
646 646 [1]
647 647 $ hg log -G --template "{rev} {phase} {desc}\n"
648 648 @ 7 public merge B' and E
649 649 |\
650 650 | o 6 public B'
651 651 | |
652 652 +---o 5 draft H
653 653 | |
654 654 o | 4 public E
655 655 | |
656 656 o | 3 public D
657 657 | |
658 658 o | 2 public C
659 659 |/
660 660 o 1 public B
661 661 |
662 662 o 0 public A
663 663
664 664
665 665 test complete failure
666 666
667 667 $ hg phase --draft 7
668 668 cannot move 1 changesets to a higher phase, use --force
669 669 no phases changed
670 670 [1]
671 671
672 672 $ cd ..
673 673
674 674 test hidden changeset are not cloned as public (issue3935)
675 675
676 676 $ cd initialrepo
677 677
678 678 (enabling evolution)
679 679 $ cat >> $HGRCPATH << EOF
680 680 > [experimental]
681 681 > evolution.createmarkers=True
682 682 > EOF
683 683
684 684 (making a changeset hidden; H in that case)
685 685 $ hg debugobsolete `hg id --debug -r 5`
686 686 1 new obsolescence markers
687 687 obsoleted 1 changesets
688 688
689 689 $ cd ..
690 690 $ hg clone initialrepo clonewithobs
691 691 requesting all changes
692 692 adding changesets
693 693 adding manifests
694 694 adding file changes
695 695 added 7 changesets with 6 changes to 6 files
696 696 new changesets 4a2df7238c3b:17a481b3bccb
697 697 test-debug-phase: new rev 0: x -> 0
698 698 test-debug-phase: new rev 1: x -> 0
699 699 test-debug-phase: new rev 2: x -> 0
700 700 test-debug-phase: new rev 3: x -> 0
701 701 test-debug-phase: new rev 4: x -> 0
702 702 test-debug-phase: new rev 5: x -> 0
703 703 test-debug-phase: new rev 6: x -> 0
704 704 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> public
705 705 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> public
706 706 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> public
707 707 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> public
708 708 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: -> public
709 709 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> public
710 710 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: -> public
711 711 updating to branch default
712 712 6 files updated, 0 files merged, 0 files removed, 0 files unresolved
713 713 $ cd clonewithobs
714 714 $ hg log -G --template "{rev} {phase} {desc}\n"
715 715 @ 6 public merge B' and E
716 716 |\
717 717 | o 5 public B'
718 718 | |
719 719 o | 4 public E
720 720 | |
721 721 o | 3 public D
722 722 | |
723 723 o | 2 public C
724 724 |/
725 725 o 1 public B
726 726 |
727 727 o 0 public A
728 728
729 729
730 730 test verify repo containing hidden changesets, which should not abort just
731 731 because repo.cancopy() is False
732 732
733 733 $ cd ../initialrepo
734 734 $ hg verify
735 735 checking changesets
736 736 checking manifests
737 737 crosschecking files in changesets and manifests
738 738 checking files
739 739 checked 8 changesets with 7 changes to 7 files
740 740
741 741 $ cd ..
742 742
743 743 check whether HG_PENDING makes pending changes only in related
744 744 repositories visible to an external hook.
745 745
746 746 (emulate a transaction running concurrently by copied
747 747 .hg/phaseroots.pending in subsequent test)
748 748
749 749 $ cat > $TESTTMP/savepending.sh <<EOF
750 750 > cp .hg/store/phaseroots.pending .hg/store/phaseroots.pending.saved
751 751 > exit 1 # to avoid changing phase for subsequent tests
752 752 > EOF
753 753 $ cd push-dest
754 754 $ hg phase 6
755 755 6: draft
756 756 $ hg --config hooks.pretxnclose="sh $TESTTMP/savepending.sh" phase -f -s 6
757 757 transaction abort!
758 758 rollback completed
759 759 abort: pretxnclose hook exited with status 1
760 760 [255]
761 761 $ cp .hg/store/phaseroots.pending.saved .hg/store/phaseroots.pending
762 762
763 763 (check (in)visibility of phaseroot while transaction running in repo)
764 764
765 765 $ cat > $TESTTMP/checkpending.sh <<EOF
766 766 > echo '@initialrepo'
767 767 > hg -R "$TESTTMP/initialrepo" phase 7
768 768 > echo '@push-dest'
769 769 > hg -R "$TESTTMP/push-dest" phase 6
770 770 > exit 1 # to avoid changing phase for subsequent tests
771 771 > EOF
772 772 $ cd ../initialrepo
773 773 $ hg phase 7
774 774 7: public
775 775 $ hg --config hooks.pretxnclose="sh $TESTTMP/checkpending.sh" phase -f -s 7
776 776 @initialrepo
777 777 7: secret
778 778 @push-dest
779 779 6: draft
780 780 transaction abort!
781 781 rollback completed
782 782 abort: pretxnclose hook exited with status 1
783 783 [255]
784 784
785 785 Check that pretxnclose-phase hook can control phase movement
786 786
787 787 $ hg phase --force b3325c91a4d9 --secret
788 788 test-debug-phase: move rev 3: 0 -> 2
789 789 test-debug-phase: move rev 4: 0 -> 2
790 790 test-debug-phase: move rev 5: 1 -> 2
791 791 test-debug-phase: move rev 7: 0 -> 2
792 792 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: public -> secret
793 793 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: public -> secret
794 794 test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8: draft -> secret
795 795 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: public -> secret
796 796 $ hg log -G -T phases
797 797 @ changeset: 7:17a481b3bccb
798 798 |\ tag: tip
799 799 | | phase: secret
800 800 | | parent: 6:cf9fe039dfd6
801 801 | | parent: 4:a603bfb5a83e
802 802 | | user: test
803 803 | | date: Thu Jan 01 00:00:00 1970 +0000
804 804 | | summary: merge B' and E
805 805 | |
806 806 | o changeset: 6:cf9fe039dfd6
807 807 | | phase: public
808 808 | | parent: 1:27547f69f254
809 809 | | user: test
810 810 | | date: Thu Jan 01 00:00:00 1970 +0000
811 811 | | summary: B'
812 812 | |
813 813 o | changeset: 4:a603bfb5a83e
814 814 | | phase: secret
815 815 | | user: test
816 816 | | date: Thu Jan 01 00:00:00 1970 +0000
817 817 | | summary: E
818 818 | |
819 819 o | changeset: 3:b3325c91a4d9
820 820 | | phase: secret
821 821 | | user: test
822 822 | | date: Thu Jan 01 00:00:00 1970 +0000
823 823 | | summary: D
824 824 | |
825 825 o | changeset: 2:f838bfaca5c7
826 826 |/ phase: public
827 827 | user: test
828 828 | date: Thu Jan 01 00:00:00 1970 +0000
829 829 | summary: C
830 830 |
831 831 o changeset: 1:27547f69f254
832 832 | phase: public
833 833 | user: test
834 834 | date: Thu Jan 01 00:00:00 1970 +0000
835 835 | summary: B
836 836 |
837 837 o changeset: 0:4a2df7238c3b
838 838 phase: public
839 839 user: test
840 840 date: Thu Jan 01 00:00:00 1970 +0000
841 841 summary: A
842 842
843 843
844 844 Install a hook that prevent b3325c91a4d9 to become public
845 845
846 846 $ cat >> .hg/hgrc << EOF
847 847 > [hooks]
848 848 > pretxnclose-phase.nopublish_D = sh -c "(echo \$HG_NODE| grep -v b3325c91a4d9>/dev/null) || [ 'public' != \$HG_PHASE ]"
849 849 > EOF
850 850
851 851 Try various actions. only the draft move should succeed
852 852
853 853 $ hg phase --public b3325c91a4d9
854 854 transaction abort!
855 855 rollback completed
856 856 abort: pretxnclose-phase.nopublish_D hook exited with status 1
857 857 [255]
858 858 $ hg phase --public a603bfb5a83e
859 859 transaction abort!
860 860 rollback completed
861 861 abort: pretxnclose-phase.nopublish_D hook exited with status 1
862 862 [255]
863 863 $ hg phase --draft 17a481b3bccb
864 864 test-debug-phase: move rev 3: 2 -> 1
865 865 test-debug-phase: move rev 4: 2 -> 1
866 866 test-debug-phase: move rev 7: 2 -> 1
867 867 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: secret -> draft
868 868 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: secret -> draft
869 869 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: secret -> draft
870 870 $ hg phase --public 17a481b3bccb
871 871 transaction abort!
872 872 rollback completed
873 873 abort: pretxnclose-phase.nopublish_D hook exited with status 1
874 874 [255]
875 875
876 876 $ cd ..
877 877
878 878 Test for the "internal" phase
879 879 =============================
880 880
881 881 Check we deny its usage on older repository
882 882
883 883 $ hg init no-internal-phase --config format.internal-phase=no
884 884 $ cd no-internal-phase
885 885 $ cat .hg/requires
886 886 dotencode
887 887 fncache
888 888 generaldelta
889 889 revlogv1
890 890 sparserevlog
891 891 store
892 892 $ echo X > X
893 893 $ hg add X
894 894 $ hg status
895 895 A X
896 896 $ hg --config "phases.new-commit=internal" commit -m "my test internal commit" 2>&1 | grep ProgrammingError
897 897 ** ProgrammingError: this repository does not support the internal phase
898 898 raise error.ProgrammingError(msg)
899 899 *ProgrammingError: this repository does not support the internal phase (glob)
900 900 $ hg --config "phases.new-commit=archived" commit -m "my test archived commit" 2>&1 | grep ProgrammingError
901 901 ** ProgrammingError: this repository does not support the archived phase
902 902 raise error.ProgrammingError(msg)
903 903 *ProgrammingError: this repository does not support the archived phase (glob)
904 904
905 905 $ cd ..
906 906
907 907 Check it works fine with repository that supports it.
908 908
909 909 $ hg init internal-phase --config format.internal-phase=yes
910 910 $ cd internal-phase
911 911 $ cat .hg/requires
912 912 dotencode
913 913 fncache
914 914 generaldelta
915 915 internal-phase
916 916 revlogv1
917 917 sparserevlog
918 918 store
919 919 $ mkcommit A
920 920 test-debug-phase: new rev 0: x -> 1
921 921 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> draft
922 922
923 923 Commit an internal changesets
924 924
925 925 $ echo B > B
926 926 $ hg add B
927 927 $ hg status
928 928 A B
929 929 $ hg --config "phases.new-commit=internal" commit -m "my test internal commit"
930 930 test-debug-phase: new rev 1: x -> 96
931 931 test-hook-close-phase: c01c42dffc7f81223397e99652a0703f83e1c5ea: -> internal
932 932
933 933 The changeset is a working parent descendant.
934 934 Per the usual visibility rules, it is made visible.
935 935
936 936 $ hg log -G -l 3
937 937 @ changeset: 1:c01c42dffc7f
938 938 | tag: tip
939 939 | user: test
940 940 | date: Thu Jan 01 00:00:00 1970 +0000
941 941 | summary: my test internal commit
942 942 |
943 943 o changeset: 0:4a2df7238c3b
944 944 user: test
945 945 date: Thu Jan 01 00:00:00 1970 +0000
946 946 summary: A
947 947
948 948
949 949 Commit is hidden as expected
950 950
951 951 $ hg up 0
952 952 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
953 953 $ hg log -G
954 954 @ changeset: 0:4a2df7238c3b
955 955 tag: tip
956 956 user: test
957 957 date: Thu Jan 01 00:00:00 1970 +0000
958 958 summary: A
959 959
960 960
961 961 Test for archived phase
962 962 -----------------------
963 963
964 964 Commit an archived changesets
965 965
966 966 $ echo B > B
967 967 $ hg add B
968 968 $ hg status
969 969 A B
970 970 $ hg --config "phases.new-commit=archived" commit -m "my test archived commit"
971 971 test-debug-phase: new rev 2: x -> 32
972 972 test-hook-close-phase: 8df5997c3361518f733d1ae67cd3adb9b0eaf125: -> archived
973 973
974 974 The changeset is a working parent descendant.
975 975 Per the usual visibility rules, it is made visible.
976 976
977 977 $ hg log -G -l 3
978 978 @ changeset: 2:8df5997c3361
979 979 | tag: tip
980 980 | parent: 0:4a2df7238c3b
981 981 | user: test
982 982 | date: Thu Jan 01 00:00:00 1970 +0000
983 983 | summary: my test archived commit
984 984 |
985 985 o changeset: 0:4a2df7238c3b
986 986 user: test
987 987 date: Thu Jan 01 00:00:00 1970 +0000
988 988 summary: A
989 989
990 990
991 991 Commit is hidden as expected
992 992
993 993 $ hg up 0
994 994 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
995 995 $ hg log -G
996 996 @ changeset: 0:4a2df7238c3b
997 997 tag: tip
998 998 user: test
999 999 date: Thu Jan 01 00:00:00 1970 +0000
1000 1000 summary: A
1001 1001
1002 1002 $ cd ..
1003 1003
1004 1004 Recommitting an exact match of a public commit shouldn't change it to
1005 1005 draft:
1006 1006
1007 1007 $ cd initialrepo
1008 1008 $ hg phase -r 2
1009 1009 2: public
1010 1010 $ hg up -C 1
1011 1011 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
1012 1012 $ mkcommit C
1013 1013 warning: commit already existed in the repository!
1014 1014 $ hg phase -r 2
1015 1015 2: public
1016 1016
1017 1017 Same, but for secret:
1018 1018
1019 1019 $ hg up 7
1020 1020 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
1021 1021 $ mkcommit F -s
1022 1022 test-debug-phase: new rev 8: x -> 2
1023 1023 test-hook-close-phase: de414268ec5ce2330c590b942fbb5ff0b0ca1a0a: -> secret
1024 1024 $ hg up 7
1025 1025 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1026 1026 $ hg phase
1027 1027 7: draft
1028 1028 $ mkcommit F
1029 1029 test-debug-phase: new rev 8: x -> 2
1030 1030 warning: commit already existed in the repository!
1031 1031 test-hook-close-phase: de414268ec5ce2330c590b942fbb5ff0b0ca1a0a: -> secret
1032 1032 $ hg phase -r tip
1033 1033 8: secret
1034 1034
1035 1035 But what about obsoleted changesets?
1036 1036
1037 1037 $ hg up 4
1038 1038 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
1039 1039 $ mkcommit H
1040 1040 test-debug-phase: new rev 5: x -> 2
1041 1041 warning: commit already existed in the repository!
1042 1042 test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8: -> secret
1043 1043 $ hg phase -r 5
1044 1044 5: secret
1045 1045 $ hg par
1046 1046 changeset: 5:a030c6be5127
1047 1047 user: test
1048 1048 date: Thu Jan 01 00:00:00 1970 +0000
1049 1049 obsolete: pruned
1050 1050 summary: H
1051 1051
1052 1052 $ hg up tip
1053 1053 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
1054 1054 $ cd ..
@@ -1,204 +1,204 b''
1 1 A new repository uses zlib storage, which doesn't need a requirement
2 2
3 3 $ hg init default
4 4 $ cd default
5 5 $ cat .hg/requires
6 6 dotencode
7 7 fncache
8 8 generaldelta
9 9 revlogv1
10 10 sparserevlog
11 11 store
12 12 testonly-simplestore (reposimplestore !)
13 13
14 14 $ touch foo
15 15 $ hg -q commit -A -m 'initial commit with a lot of repeated repeated repeated text to trigger compression'
16 16 $ hg debugrevlog -c | grep 0x78
17 17 0x78 (x) : 1 (100.00%)
18 18 0x78 (x) : 110 (100.00%)
19 19
20 20 $ cd ..
21 21
22 22 Unknown compression engine to format.compression aborts
23 23
24 24 $ hg --config format.revlog-compression=unknown init unknown
25 25 abort: compression engines "unknown" defined by format.revlog-compression not available
26 26 (run "hg debuginstall" to list available compression engines)
27 27 [255]
28 28
29 29 unknown compression engine in a list with known one works fine
30 30
31 31 $ hg --config format.revlog-compression=zlib,unknown init zlib-before-unknow
32 32 $ hg --config format.revlog-compression=unknown,zlib init unknown-before-zlib
33 33
34 34 A requirement specifying an unknown compression engine results in bail
35 35
36 36 $ hg init unknownrequirement
37 37 $ cd unknownrequirement
38 38 $ echo exp-compression-unknown >> .hg/requires
39 39 $ hg log
40 40 abort: repository requires features unknown to this Mercurial: exp-compression-unknown!
41 41 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
42 42 [255]
43 43
44 44 $ cd ..
45 45
46 46 #if zstd
47 47
48 48 $ hg --config format.revlog-compression=zstd init zstd
49 49 $ cd zstd
50 50 $ cat .hg/requires
51 51 dotencode
52 52 fncache
53 53 generaldelta
54 54 revlog-compression-zstd
55 55 revlogv1
56 56 sparserevlog
57 57 store
58 58 testonly-simplestore (reposimplestore !)
59 59
60 60 $ touch foo
61 61 $ hg -q commit -A -m 'initial commit with a lot of repeated repeated repeated text'
62 62
63 63 $ hg debugrevlog -c | grep 0x28
64 64 0x28 : 1 (100.00%)
65 65 0x28 : 98 (100.00%)
66 66
67 67 $ cd ..
68 68
69 69 Specifying a new format.compression on an existing repo won't introduce data
70 70 with that engine or a requirement
71 71
72 72 $ cd default
73 73 $ touch bar
74 74 $ hg --config format.revlog-compression=zstd -q commit -A -m 'add bar with a lot of repeated repeated repeated text'
75 75
76 76 $ cat .hg/requires
77 77 dotencode
78 78 fncache
79 79 generaldelta
80 80 revlogv1
81 81 sparserevlog
82 82 store
83 83 testonly-simplestore (reposimplestore !)
84 84
85 85 $ hg debugrevlog -c | grep 0x78
86 86 0x78 (x) : 2 (100.00%)
87 87 0x78 (x) : 199 (100.00%)
88 88
89 89 #endif
90 90
91 91 checking zlib options
92 92 =====================
93 93
94 94 $ hg init zlib-level-default
95 95 $ hg init zlib-level-1
96 96 $ cat << EOF >> zlib-level-1/.hg/hgrc
97 97 > [storage]
98 98 > revlog.zlib.level=1
99 99 > EOF
100 100 $ hg init zlib-level-9
101 101 $ cat << EOF >> zlib-level-9/.hg/hgrc
102 102 > [storage]
103 103 > revlog.zlib.level=9
104 104 > EOF
105 105
106 106
107 107 $ commitone() {
108 108 > repo=$1
109 109 > cp $RUNTESTDIR/bundles/issue4438-r1.hg $repo/a
110 110 > hg -R $repo add $repo/a
111 111 > hg -R $repo commit -m some-commit
112 112 > }
113 113
114 114 $ for repo in zlib-level-default zlib-level-1 zlib-level-9; do
115 115 > commitone $repo
116 116 > done
117 117
118 118 $ $RUNTESTDIR/f -s */.hg/store/data/*
119 119 default/.hg/store/data/foo.i: size=64 (pure !)
120 120 zlib-level-1/.hg/store/data/a.i: size=4146
121 121 zlib-level-9/.hg/store/data/a.i: size=4138
122 122 zlib-level-default/.hg/store/data/a.i: size=4138
123 123
124 124 Test error cases
125 125
126 126 $ hg init zlib-level-invalid
127 127 $ cat << EOF >> zlib-level-invalid/.hg/hgrc
128 128 > [storage]
129 129 > revlog.zlib.level=foobar
130 130 > EOF
131 131 $ commitone zlib-level-invalid
132 abort: storage.revlog.zlib.level is not a valid integer ('foobar')
133 abort: storage.revlog.zlib.level is not a valid integer ('foobar')
132 config error: storage.revlog.zlib.level is not a valid integer ('foobar')
133 config error: storage.revlog.zlib.level is not a valid integer ('foobar')
134 134 [30]
135 135
136 136 $ hg init zlib-level-out-of-range
137 137 $ cat << EOF >> zlib-level-out-of-range/.hg/hgrc
138 138 > [storage]
139 139 > revlog.zlib.level=42
140 140 > EOF
141 141
142 142 $ commitone zlib-level-out-of-range
143 143 abort: invalid value for `storage.revlog.zlib.level` config: 42
144 144 abort: invalid value for `storage.revlog.zlib.level` config: 42
145 145 [255]
146 146
147 147 #if zstd
148 148
149 149 checking zstd options
150 150 =====================
151 151
152 152 $ hg init zstd-level-default --config format.revlog-compression=zstd
153 153 $ hg init zstd-level-1 --config format.revlog-compression=zstd
154 154 $ cat << EOF >> zstd-level-1/.hg/hgrc
155 155 > [storage]
156 156 > revlog.zstd.level=1
157 157 > EOF
158 158 $ hg init zstd-level-22 --config format.revlog-compression=zstd
159 159 $ cat << EOF >> zstd-level-22/.hg/hgrc
160 160 > [storage]
161 161 > revlog.zstd.level=22
162 162 > EOF
163 163
164 164
165 165 $ commitone() {
166 166 > repo=$1
167 167 > cp $RUNTESTDIR/bundles/issue4438-r1.hg $repo/a
168 168 > hg -R $repo add $repo/a
169 169 > hg -R $repo commit -m some-commit
170 170 > }
171 171
172 172 $ for repo in zstd-level-default zstd-level-1 zstd-level-22; do
173 173 > commitone $repo
174 174 > done
175 175
176 176 $ $RUNTESTDIR/f -s zstd-*/.hg/store/data/*
177 177 zstd-level-1/.hg/store/data/a.i: size=4114
178 178 zstd-level-22/.hg/store/data/a.i: size=4091
179 179 zstd-level-default/\.hg/store/data/a\.i: size=(4094|4102) (re)
180 180
181 181 Test error cases
182 182
183 183 $ hg init zstd-level-invalid --config format.revlog-compression=zstd
184 184 $ cat << EOF >> zstd-level-invalid/.hg/hgrc
185 185 > [storage]
186 186 > revlog.zstd.level=foobar
187 187 > EOF
188 188 $ commitone zstd-level-invalid
189 abort: storage.revlog.zstd.level is not a valid integer ('foobar')
190 abort: storage.revlog.zstd.level is not a valid integer ('foobar')
189 config error: storage.revlog.zstd.level is not a valid integer ('foobar')
190 config error: storage.revlog.zstd.level is not a valid integer ('foobar')
191 191 [30]
192 192
193 193 $ hg init zstd-level-out-of-range --config format.revlog-compression=zstd
194 194 $ cat << EOF >> zstd-level-out-of-range/.hg/hgrc
195 195 > [storage]
196 196 > revlog.zstd.level=42
197 197 > EOF
198 198
199 199 $ commitone zstd-level-out-of-range
200 200 abort: invalid value for `storage.revlog.zstd.level` config: 42
201 201 abort: invalid value for `storage.revlog.zstd.level` config: 42
202 202 [255]
203 203
204 204 #endif
@@ -1,320 +1,320 b''
1 1 # Since it's not easy to write a test that portably deals
2 2 # with files from different users/groups, we cheat a bit by
3 3 # monkey-patching some functions in the util module
4 4
5 5 from __future__ import absolute_import, print_function
6 6
7 7 import os
8 8 import sys
9 9
10 10 from mercurial import (
11 11 error,
12 12 pycompat,
13 13 ui as uimod,
14 14 util,
15 15 )
16 16 from mercurial.utils import stringutil
17 17
18 18 hgrc = os.environ['HGRCPATH']
19 19 f = open(hgrc, 'rb')
20 20 basehgrc = f.read()
21 21 f.close()
22 22
23 23
24 24 def _maybesysstr(v):
25 25 if isinstance(v, bytes):
26 26 return pycompat.sysstr(v)
27 27 return pycompat.sysstr(stringutil.pprint(v))
28 28
29 29
30 30 def bprint(*args, **kwargs):
31 31 print(
32 32 *[_maybesysstr(a) for a in args],
33 33 **{k: _maybesysstr(v) for k, v in kwargs.items()}
34 34 )
35 35 # avoid awkward interleaving with ui object's output
36 36 sys.stdout.flush()
37 37
38 38
39 39 def testui(
40 40 user=b'foo',
41 41 group=b'bar',
42 42 tusers=(),
43 43 tgroups=(),
44 44 cuser=b'foo',
45 45 cgroup=b'bar',
46 46 debug=False,
47 47 silent=False,
48 48 report=True,
49 49 ):
50 50 # user, group => owners of the file
51 51 # tusers, tgroups => trusted users/groups
52 52 # cuser, cgroup => user/group of the current process
53 53
54 54 # write a global hgrc with the list of trusted users/groups and
55 55 # some setting so that we can be sure it was read
56 56 f = open(hgrc, 'wb')
57 57 f.write(basehgrc)
58 58 f.write(b'\n[paths]\n')
59 59 f.write(b'global = /some/path\n\n')
60 60
61 61 if tusers or tgroups:
62 62 f.write(b'[trusted]\n')
63 63 if tusers:
64 64 f.write(b'users = %s\n' % b', '.join(tusers))
65 65 if tgroups:
66 66 f.write(b'groups = %s\n' % b', '.join(tgroups))
67 67 f.close()
68 68
69 69 # override the functions that give names to uids and gids
70 70 def username(uid=None):
71 71 if uid is None:
72 72 return cuser
73 73 return user
74 74
75 75 util.username = username
76 76
77 77 def groupname(gid=None):
78 78 if gid is None:
79 79 return b'bar'
80 80 return group
81 81
82 82 util.groupname = groupname
83 83
84 84 def isowner(st):
85 85 return user == cuser
86 86
87 87 util.isowner = isowner
88 88
89 89 # try to read everything
90 90 # print '# File belongs to user %s, group %s' % (user, group)
91 91 # print '# trusted users = %s; trusted groups = %s' % (tusers, tgroups)
92 92 kind = (b'different', b'same')
93 93 who = (b'', b'user', b'group', b'user and the group')
94 94 trusted = who[(user in tusers) + 2 * (group in tgroups)]
95 95 if trusted:
96 96 trusted = b', but we trust the ' + trusted
97 97 bprint(
98 98 b'# %s user, %s group%s'
99 99 % (kind[user == cuser], kind[group == cgroup], trusted)
100 100 )
101 101
102 102 u = uimod.ui.load()
103 103 # disable the configuration registration warning
104 104 #
105 105 # the purpose of this test is to check the old behavior, not to validate the
106 106 # behavior from registered item. so we silent warning related to unregisted
107 107 # config.
108 108 u.setconfig(b'devel', b'warn-config-unknown', False, b'test')
109 109 u.setconfig(b'devel', b'all-warnings', False, b'test')
110 110 u.setconfig(b'ui', b'debug', pycompat.bytestr(bool(debug)))
111 111 u.setconfig(b'ui', b'report_untrusted', pycompat.bytestr(bool(report)))
112 112 u.readconfig(b'.hg/hgrc')
113 113 if silent:
114 114 return u
115 115 bprint(b'trusted')
116 116 for name, path in u.configitems(b'paths'):
117 117 bprint(b' ', name, b'=', util.pconvert(path))
118 118 bprint(b'untrusted')
119 119 for name, path in u.configitems(b'paths', untrusted=True):
120 120 bprint(b'.', end=b' ')
121 121 u.config(b'paths', name) # warning with debug=True
122 122 bprint(b'.', end=b' ')
123 123 u.config(b'paths', name, untrusted=True) # no warnings
124 124 bprint(name, b'=', util.pconvert(path))
125 125 print()
126 126
127 127 return u
128 128
129 129
130 130 os.mkdir(b'repo')
131 131 os.chdir(b'repo')
132 132 os.mkdir(b'.hg')
133 133 f = open(b'.hg/hgrc', 'wb')
134 134 f.write(b'[paths]\n')
135 135 f.write(b'local = /another/path\n\n')
136 136 f.close()
137 137
138 138 # print '# Everything is run by user foo, group bar\n'
139 139
140 140 # same user, same group
141 141 testui()
142 142 # same user, different group
143 143 testui(group=b'def')
144 144 # different user, same group
145 145 testui(user=b'abc')
146 146 # ... but we trust the group
147 147 testui(user=b'abc', tgroups=[b'bar'])
148 148 # different user, different group
149 149 testui(user=b'abc', group=b'def')
150 150 # ... but we trust the user
151 151 testui(user=b'abc', group=b'def', tusers=[b'abc'])
152 152 # ... but we trust the group
153 153 testui(user=b'abc', group=b'def', tgroups=[b'def'])
154 154 # ... but we trust the user and the group
155 155 testui(user=b'abc', group=b'def', tusers=[b'abc'], tgroups=[b'def'])
156 156 # ... but we trust all users
157 157 bprint(b'# we trust all users')
158 158 testui(user=b'abc', group=b'def', tusers=[b'*'])
159 159 # ... but we trust all groups
160 160 bprint(b'# we trust all groups')
161 161 testui(user=b'abc', group=b'def', tgroups=[b'*'])
162 162 # ... but we trust the whole universe
163 163 bprint(b'# we trust all users and groups')
164 164 testui(user=b'abc', group=b'def', tusers=[b'*'], tgroups=[b'*'])
165 165 # ... check that users and groups are in different namespaces
166 166 bprint(b"# we don't get confused by users and groups with the same name")
167 167 testui(user=b'abc', group=b'def', tusers=[b'def'], tgroups=[b'abc'])
168 168 # ... lists of user names work
169 169 bprint(b"# list of user names")
170 170 testui(
171 171 user=b'abc',
172 172 group=b'def',
173 173 tusers=[b'foo', b'xyz', b'abc', b'bleh'],
174 174 tgroups=[b'bar', b'baz', b'qux'],
175 175 )
176 176 # ... lists of group names work
177 177 bprint(b"# list of group names")
178 178 testui(
179 179 user=b'abc',
180 180 group=b'def',
181 181 tusers=[b'foo', b'xyz', b'bleh'],
182 182 tgroups=[b'bar', b'def', b'baz', b'qux'],
183 183 )
184 184
185 185 bprint(b"# Can't figure out the name of the user running this process")
186 186 testui(user=b'abc', group=b'def', cuser=None)
187 187
188 188 bprint(b"# prints debug warnings")
189 189 u = testui(user=b'abc', group=b'def', cuser=b'foo', debug=True)
190 190
191 191 bprint(b"# report_untrusted enabled without debug hides warnings")
192 192 u = testui(user=b'abc', group=b'def', cuser=b'foo', report=False)
193 193
194 194 bprint(b"# report_untrusted enabled with debug shows warnings")
195 195 u = testui(user=b'abc', group=b'def', cuser=b'foo', debug=True, report=False)
196 196
197 197 bprint(b"# ui.readconfig sections")
198 198 filename = b'foobar'
199 199 f = open(filename, 'wb')
200 200 f.write(b'[foobar]\n')
201 201 f.write(b'baz = quux\n')
202 202 f.close()
203 203 u.readconfig(filename, sections=[b'foobar'])
204 204 bprint(u.config(b'foobar', b'baz'))
205 205
206 206 print()
207 207 bprint(b"# read trusted, untrusted, new ui, trusted")
208 208 u = uimod.ui.load()
209 209 # disable the configuration registration warning
210 210 #
211 211 # the purpose of this test is to check the old behavior, not to validate the
212 212 # behavior from registered item. so we silent warning related to unregisted
213 213 # config.
214 214 u.setconfig(b'devel', b'warn-config-unknown', False, b'test')
215 215 u.setconfig(b'devel', b'all-warnings', False, b'test')
216 216 u.setconfig(b'ui', b'debug', b'on')
217 217 u.readconfig(filename)
218 218 u2 = u.copy()
219 219
220 220
221 221 def username(uid=None):
222 222 return b'foo'
223 223
224 224
225 225 util.username = username
226 226 u2.readconfig(b'.hg/hgrc')
227 227 bprint(b'trusted:')
228 228 bprint(u2.config(b'foobar', b'baz'))
229 229 bprint(b'untrusted:')
230 230 bprint(u2.config(b'foobar', b'baz', untrusted=True))
231 231
232 232 print()
233 233 bprint(b"# error handling")
234 234
235 235
236 236 def assertraises(f, exc=error.Abort):
237 237 try:
238 238 f()
239 239 except exc as inst:
240 240 bprint(b'raised', inst.__class__.__name__)
241 241 else:
242 242 bprint(b'no exception?!')
243 243
244 244
245 245 bprint(b"# file doesn't exist")
246 246 os.unlink(b'.hg/hgrc')
247 247 assert not os.path.exists(b'.hg/hgrc')
248 248 testui(debug=True, silent=True)
249 249 testui(user=b'abc', group=b'def', debug=True, silent=True)
250 250
251 251 print()
252 252 bprint(b"# parse error")
253 253 f = open(b'.hg/hgrc', 'wb')
254 254 f.write(b'foo')
255 255 f.close()
256 256
257 257 try:
258 258 testui(user=b'abc', group=b'def', silent=True)
259 except error.ParseError as inst:
259 except error.ConfigError as inst:
260 260 bprint(inst.format())
261 261
262 262 try:
263 263 testui(debug=True, silent=True)
264 except error.ParseError as inst:
264 except error.ConfigError as inst:
265 265 bprint(inst.format())
266 266
267 267 print()
268 268 bprint(b'# access typed information')
269 269 with open(b'.hg/hgrc', 'wb') as f:
270 270 f.write(
271 271 b'''\
272 272 [foo]
273 273 sub=main
274 274 sub:one=one
275 275 sub:two=two
276 276 path=monty/python
277 277 bool=true
278 278 int=42
279 279 bytes=81mb
280 280 list=spam,ham,eggs
281 281 '''
282 282 )
283 283 u = testui(user=b'abc', group=b'def', cuser=b'foo', silent=True)
284 284
285 285
286 286 def configpath(section, name, default=None, untrusted=False):
287 287 path = u.configpath(section, name, default, untrusted)
288 288 if path is None:
289 289 return None
290 290 return util.pconvert(path)
291 291
292 292
293 293 bprint(b'# suboptions, trusted and untrusted')
294 294 trusted = u.configsuboptions(b'foo', b'sub')
295 295 untrusted = u.configsuboptions(b'foo', b'sub', untrusted=True)
296 296 bprint(
297 297 (trusted[0], sorted(trusted[1].items())),
298 298 (untrusted[0], sorted(untrusted[1].items())),
299 299 )
300 300 bprint(b'# path, trusted and untrusted')
301 301 bprint(configpath(b'foo', b'path'), configpath(b'foo', b'path', untrusted=True))
302 302 bprint(b'# bool, trusted and untrusted')
303 303 bprint(
304 304 u.configbool(b'foo', b'bool'), u.configbool(b'foo', b'bool', untrusted=True)
305 305 )
306 306 bprint(b'# int, trusted and untrusted')
307 307 bprint(
308 308 u.configint(b'foo', b'int', 0),
309 309 u.configint(b'foo', b'int', 0, untrusted=True),
310 310 )
311 311 bprint(b'# bytes, trusted and untrusted')
312 312 bprint(
313 313 u.configbytes(b'foo', b'bytes', 0),
314 314 u.configbytes(b'foo', b'bytes', 0, untrusted=True),
315 315 )
316 316 bprint(b'# list, trusted and untrusted')
317 317 bprint(
318 318 u.configlist(b'foo', b'list', []),
319 319 u.configlist(b'foo', b'list', [], untrusted=True),
320 320 )
@@ -1,196 +1,196 b''
1 1 # same user, same group
2 2 trusted
3 3 global = /some/path
4 4 local = /another/path
5 5 untrusted
6 6 . . global = /some/path
7 7 . . local = /another/path
8 8
9 9 # same user, different group
10 10 trusted
11 11 global = /some/path
12 12 local = /another/path
13 13 untrusted
14 14 . . global = /some/path
15 15 . . local = /another/path
16 16
17 17 # different user, same group
18 18 not trusting file .hg/hgrc from untrusted user abc, group bar
19 19 trusted
20 20 global = /some/path
21 21 untrusted
22 22 . . global = /some/path
23 23 . . local = /another/path
24 24
25 25 # different user, same group, but we trust the group
26 26 trusted
27 27 global = /some/path
28 28 local = /another/path
29 29 untrusted
30 30 . . global = /some/path
31 31 . . local = /another/path
32 32
33 33 # different user, different group
34 34 not trusting file .hg/hgrc from untrusted user abc, group def
35 35 trusted
36 36 global = /some/path
37 37 untrusted
38 38 . . global = /some/path
39 39 . . local = /another/path
40 40
41 41 # different user, different group, but we trust the user
42 42 trusted
43 43 global = /some/path
44 44 local = /another/path
45 45 untrusted
46 46 . . global = /some/path
47 47 . . local = /another/path
48 48
49 49 # different user, different group, but we trust the group
50 50 trusted
51 51 global = /some/path
52 52 local = /another/path
53 53 untrusted
54 54 . . global = /some/path
55 55 . . local = /another/path
56 56
57 57 # different user, different group, but we trust the user and the group
58 58 trusted
59 59 global = /some/path
60 60 local = /another/path
61 61 untrusted
62 62 . . global = /some/path
63 63 . . local = /another/path
64 64
65 65 # we trust all users
66 66 # different user, different group
67 67 trusted
68 68 global = /some/path
69 69 local = /another/path
70 70 untrusted
71 71 . . global = /some/path
72 72 . . local = /another/path
73 73
74 74 # we trust all groups
75 75 # different user, different group
76 76 trusted
77 77 global = /some/path
78 78 local = /another/path
79 79 untrusted
80 80 . . global = /some/path
81 81 . . local = /another/path
82 82
83 83 # we trust all users and groups
84 84 # different user, different group
85 85 trusted
86 86 global = /some/path
87 87 local = /another/path
88 88 untrusted
89 89 . . global = /some/path
90 90 . . local = /another/path
91 91
92 92 # we don't get confused by users and groups with the same name
93 93 # different user, different group
94 94 not trusting file .hg/hgrc from untrusted user abc, group def
95 95 trusted
96 96 global = /some/path
97 97 untrusted
98 98 . . global = /some/path
99 99 . . local = /another/path
100 100
101 101 # list of user names
102 102 # different user, different group, but we trust the user
103 103 trusted
104 104 global = /some/path
105 105 local = /another/path
106 106 untrusted
107 107 . . global = /some/path
108 108 . . local = /another/path
109 109
110 110 # list of group names
111 111 # different user, different group, but we trust the group
112 112 trusted
113 113 global = /some/path
114 114 local = /another/path
115 115 untrusted
116 116 . . global = /some/path
117 117 . . local = /another/path
118 118
119 119 # Can't figure out the name of the user running this process
120 120 # different user, different group
121 121 not trusting file .hg/hgrc from untrusted user abc, group def
122 122 trusted
123 123 global = /some/path
124 124 untrusted
125 125 . . global = /some/path
126 126 . . local = /another/path
127 127
128 128 # prints debug warnings
129 129 # different user, different group
130 130 not trusting file .hg/hgrc from untrusted user abc, group def
131 131 trusted
132 132 ignoring untrusted configuration option paths.local = /another/path
133 133 global = /some/path
134 134 untrusted
135 135 . . global = /some/path
136 136 . ignoring untrusted configuration option paths.local = /another/path
137 137 . local = /another/path
138 138
139 139 # report_untrusted enabled without debug hides warnings
140 140 # different user, different group
141 141 trusted
142 142 global = /some/path
143 143 untrusted
144 144 . . global = /some/path
145 145 . . local = /another/path
146 146
147 147 # report_untrusted enabled with debug shows warnings
148 148 # different user, different group
149 149 not trusting file .hg/hgrc from untrusted user abc, group def
150 150 trusted
151 151 ignoring untrusted configuration option paths.local = /another/path
152 152 global = /some/path
153 153 untrusted
154 154 . . global = /some/path
155 155 . ignoring untrusted configuration option paths.local = /another/path
156 156 . local = /another/path
157 157
158 158 # ui.readconfig sections
159 159 quux
160 160
161 161 # read trusted, untrusted, new ui, trusted
162 162 not trusting file foobar from untrusted user abc, group def
163 163 trusted:
164 164 ignoring untrusted configuration option foobar.baz = quux
165 165 None
166 166 untrusted:
167 167 quux
168 168
169 169 # error handling
170 170 # file doesn't exist
171 171 # same user, same group
172 172 # different user, different group
173 173
174 174 # parse error
175 175 # different user, different group
176 176 not trusting file .hg/hgrc from untrusted user abc, group def
177 177 ignored .hg/hgrc:1: foo
178 178 # same user, same group
179 hg: parse error at .hg/hgrc:1: foo
179 config error at .hg/hgrc:1: foo
180 180
181 181
182 182 # access typed information
183 183 # different user, different group
184 184 not trusting file .hg/hgrc from untrusted user abc, group def
185 185 # suboptions, trusted and untrusted
186 186 (None, []) ('main', [('one', 'one'), ('two', 'two')])
187 187 # path, trusted and untrusted
188 188 None .hg/monty/python
189 189 # bool, trusted and untrusted
190 190 False True
191 191 # int, trusted and untrusted
192 192 0 42
193 193 # bytes, trusted and untrusted
194 194 0 84934656
195 195 # list, trusted and untrusted
196 196 [] ['spam', 'ham', 'eggs']
General Comments 0
You need to be logged in to leave comments. Login now