##// END OF EJS Templates
progress: move all logic altering the ui object logic in mercurial.ui...
Pierre-Yves David -
r25499:0fa964d6 default
parent child Browse files
Show More
@@ -1,77 +1,43 b''
1 1 # progress.py show progress bars for some actions
2 2 #
3 3 # Copyright (C) 2010 Augie Fackler <durin42@gmail.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 """show progress bars for some actions
9 9
10 10 This extension uses the progress information logged by hg commands
11 11 to draw progress bars that are as informative as possible. Some progress
12 12 bars only offer indeterminate information, while others have a definite
13 13 end point.
14 14
15 15 The following settings are available::
16 16
17 17 [progress]
18 18 delay = 3 # number of seconds (float) before showing the progress bar
19 19 changedelay = 1 # changedelay: minimum delay before showing a new topic.
20 20 # If set to less than 3 * refresh, that value will
21 21 # be used instead.
22 22 refresh = 0.1 # time in seconds between refreshes of the progress bar
23 23 format = topic bar number estimate # format of the progress bar
24 24 width = <none> # if set, the maximum width of the progress information
25 25 # (that is, min(width, term width) will be used)
26 26 clear-complete = True # clear the progress bar after it's done
27 27 disable = False # if true, don't show a progress bar
28 28 assume-tty = False # if true, ALWAYS show a progress bar, unless
29 29 # disable is given
30 30
31 31 Valid entries for the format field are topic, bar, number, unit,
32 32 estimate, speed, and item. item defaults to the last 20 characters of
33 33 the item, but this can be changed by adding either ``-<num>`` which
34 34 would take the last num characters, or ``+<num>`` for the first num
35 35 characters.
36 36 """
37 37
38 from mercurial import progress
39 from mercurial import ui as uimod
40
41 38 def uisetup(ui):
42 class progressui(ui.__class__):
43 _progbar = None
44
45 def _quiet(self):
46 return self.debugflag or self.quiet
47
48 def progress(self, *args, **opts):
49 if not self._quiet():
50 self._progbar.progress(*args, **opts)
51 return super(progressui, self).progress(*args, **opts)
52
53 def write(self, *args, **opts):
54 if not self._quiet() and self._progbar.printed:
55 self._progbar.clear()
56 return super(progressui, self).write(*args, **opts)
57
58 def write_err(self, *args, **opts):
59 if not self._quiet() and self._progbar.printed:
60 self._progbar.clear()
61 return super(progressui, self).write_err(*args, **opts)
62
63 # Apps that derive a class from ui.ui() can use
64 # setconfig('progress', 'disable', 'True') to disable this extension
65 if ui.configbool('progress', 'disable'):
66 return
67 if progress.shouldprint(ui) and not ui.debugflag and not ui.quiet:
68 dval = object()
69 if getattr(ui, '_progbar', dval) is dval:
70 ui.__class__ = progressui
71 # we instantiate one globally-shared progress bar to avoid
72 # competing progress bars when multiple UI objects get created
73 if not progressui._progbar:
74 progressui._progbar = uimod.getprogbar(ui)
39 if ui.config('progress', 'disable', None) is None:
40 ui.setconfig('progress', 'disable', 'False', 'hgext-progress')
75 41
76 42 def reposetup(ui, repo):
77 43 uisetup(repo.ui)
@@ -1,997 +1,1017 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 i18n import _
9 9 import errno, getpass, os, socket, sys, tempfile, traceback
10 10 import config, scmutil, util, error, formatter, progress
11 11 from node import hex
12 12
13 13 samplehgrcs = {
14 14 'user':
15 15 """# example user config (see "hg help config" for more info)
16 16 [ui]
17 17 # name and email, e.g.
18 18 # username = Jane Doe <jdoe@example.com>
19 19 username =
20 20
21 21 [extensions]
22 22 # uncomment these lines to enable some popular extensions
23 23 # (see "hg help extensions" for more info)
24 24 #
25 25 # pager =
26 26 # progress =
27 27 # color =""",
28 28
29 29 'cloned':
30 30 """# example repository config (see "hg help config" for more info)
31 31 [paths]
32 32 default = %s
33 33
34 34 # path aliases to other clones of this repo in URLs or filesystem paths
35 35 # (see "hg help config.paths" for more info)
36 36 #
37 37 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
38 38 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
39 39 # my-clone = /home/jdoe/jdoes-clone
40 40
41 41 [ui]
42 42 # name and email (local to this repository, optional), e.g.
43 43 # username = Jane Doe <jdoe@example.com>
44 44 """,
45 45
46 46 'local':
47 47 """# example repository config (see "hg help config" for more info)
48 48 [paths]
49 49 # path aliases to other clones of this repo in URLs or filesystem paths
50 50 # (see "hg help config.paths" for more info)
51 51 #
52 52 # default = http://example.com/hg/example-repo
53 53 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
54 54 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
55 55 # my-clone = /home/jdoe/jdoes-clone
56 56
57 57 [ui]
58 58 # name and email (local to this repository, optional), e.g.
59 59 # username = Jane Doe <jdoe@example.com>
60 60 """,
61 61
62 62 'global':
63 63 """# example system-wide hg config (see "hg help config" for more info)
64 64
65 65 [extensions]
66 66 # uncomment these lines to enable some popular extensions
67 67 # (see "hg help extensions" for more info)
68 68 #
69 69 # blackbox =
70 70 # progress =
71 71 # color =
72 72 # pager =""",
73 73 }
74 74
75 75 class ui(object):
76 76 def __init__(self, src=None):
77 77 # _buffers: used for temporary capture of output
78 78 self._buffers = []
79 79 # _bufferstates:
80 80 # should the temporary capture include stderr and subprocess output
81 81 self._bufferstates = []
82 82 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
83 83 self._reportuntrusted = True
84 84 self._ocfg = config.config() # overlay
85 85 self._tcfg = config.config() # trusted
86 86 self._ucfg = config.config() # untrusted
87 87 self._trustusers = set()
88 88 self._trustgroups = set()
89 89 self.callhooks = True
90 90
91 91 if src:
92 92 self.fout = src.fout
93 93 self.ferr = src.ferr
94 94 self.fin = src.fin
95 95
96 96 self._tcfg = src._tcfg.copy()
97 97 self._ucfg = src._ucfg.copy()
98 98 self._ocfg = src._ocfg.copy()
99 99 self._trustusers = src._trustusers.copy()
100 100 self._trustgroups = src._trustgroups.copy()
101 101 self.environ = src.environ
102 102 self.callhooks = src.callhooks
103 103 self.fixconfig()
104 104 else:
105 105 self.fout = sys.stdout
106 106 self.ferr = sys.stderr
107 107 self.fin = sys.stdin
108 108
109 109 # shared read-only environment
110 110 self.environ = os.environ
111 111 # we always trust global config files
112 112 for f in scmutil.rcpath():
113 113 self.readconfig(f, trust=True)
114 114
115 115 def copy(self):
116 116 return self.__class__(self)
117 117
118 118 def formatter(self, topic, opts):
119 119 return formatter.formatter(self, topic, opts)
120 120
121 121 def _trusted(self, fp, f):
122 122 st = util.fstat(fp)
123 123 if util.isowner(st):
124 124 return True
125 125
126 126 tusers, tgroups = self._trustusers, self._trustgroups
127 127 if '*' in tusers or '*' in tgroups:
128 128 return True
129 129
130 130 user = util.username(st.st_uid)
131 131 group = util.groupname(st.st_gid)
132 132 if user in tusers or group in tgroups or user == util.username():
133 133 return True
134 134
135 135 if self._reportuntrusted:
136 136 self.warn(_('not trusting file %s from untrusted '
137 137 'user %s, group %s\n') % (f, user, group))
138 138 return False
139 139
140 140 def readconfig(self, filename, root=None, trust=False,
141 141 sections=None, remap=None):
142 142 try:
143 143 fp = open(filename)
144 144 except IOError:
145 145 if not sections: # ignore unless we were looking for something
146 146 return
147 147 raise
148 148
149 149 cfg = config.config()
150 150 trusted = sections or trust or self._trusted(fp, filename)
151 151
152 152 try:
153 153 cfg.read(filename, fp, sections=sections, remap=remap)
154 154 fp.close()
155 155 except error.ConfigError, inst:
156 156 if trusted:
157 157 raise
158 158 self.warn(_("ignored: %s\n") % str(inst))
159 159
160 160 if self.plain():
161 161 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
162 162 'logtemplate', 'statuscopies', 'style',
163 163 'traceback', 'verbose'):
164 164 if k in cfg['ui']:
165 165 del cfg['ui'][k]
166 166 for k, v in cfg.items('defaults'):
167 167 del cfg['defaults'][k]
168 168 # Don't remove aliases from the configuration if in the exceptionlist
169 169 if self.plain('alias'):
170 170 for k, v in cfg.items('alias'):
171 171 del cfg['alias'][k]
172 172 if self.plain('revsetalias'):
173 173 for k, v in cfg.items('revsetalias'):
174 174 del cfg['revsetalias'][k]
175 175
176 176 if trusted:
177 177 self._tcfg.update(cfg)
178 178 self._tcfg.update(self._ocfg)
179 179 self._ucfg.update(cfg)
180 180 self._ucfg.update(self._ocfg)
181 181
182 182 if root is None:
183 183 root = os.path.expanduser('~')
184 184 self.fixconfig(root=root)
185 185
186 186 def fixconfig(self, root=None, section=None):
187 187 if section in (None, 'paths'):
188 188 # expand vars and ~
189 189 # translate paths relative to root (or home) into absolute paths
190 190 root = root or os.getcwd()
191 191 for c in self._tcfg, self._ucfg, self._ocfg:
192 192 for n, p in c.items('paths'):
193 193 if not p:
194 194 continue
195 195 if '%%' in p:
196 196 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
197 197 % (n, p, self.configsource('paths', n)))
198 198 p = p.replace('%%', '%')
199 199 p = util.expandpath(p)
200 200 if not util.hasscheme(p) and not os.path.isabs(p):
201 201 p = os.path.normpath(os.path.join(root, p))
202 202 c.set("paths", n, p)
203 203
204 204 if section in (None, 'ui'):
205 205 # update ui options
206 206 self.debugflag = self.configbool('ui', 'debug')
207 207 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
208 208 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
209 209 if self.verbose and self.quiet:
210 210 self.quiet = self.verbose = False
211 211 self._reportuntrusted = self.debugflag or self.configbool("ui",
212 212 "report_untrusted", True)
213 213 self.tracebackflag = self.configbool('ui', 'traceback', False)
214 214
215 215 if section in (None, 'trusted'):
216 216 # update trust information
217 217 self._trustusers.update(self.configlist('trusted', 'users'))
218 218 self._trustgroups.update(self.configlist('trusted', 'groups'))
219 219
220 220 def backupconfig(self, section, item):
221 221 return (self._ocfg.backup(section, item),
222 222 self._tcfg.backup(section, item),
223 223 self._ucfg.backup(section, item),)
224 224 def restoreconfig(self, data):
225 225 self._ocfg.restore(data[0])
226 226 self._tcfg.restore(data[1])
227 227 self._ucfg.restore(data[2])
228 228
229 229 def setconfig(self, section, name, value, source=''):
230 230 for cfg in (self._ocfg, self._tcfg, self._ucfg):
231 231 cfg.set(section, name, value, source)
232 232 self.fixconfig(section=section)
233 233
234 234 def _data(self, untrusted):
235 235 return untrusted and self._ucfg or self._tcfg
236 236
237 237 def configsource(self, section, name, untrusted=False):
238 238 return self._data(untrusted).source(section, name) or 'none'
239 239
240 240 def config(self, section, name, default=None, untrusted=False):
241 241 if isinstance(name, list):
242 242 alternates = name
243 243 else:
244 244 alternates = [name]
245 245
246 246 for n in alternates:
247 247 value = self._data(untrusted).get(section, n, None)
248 248 if value is not None:
249 249 name = n
250 250 break
251 251 else:
252 252 value = default
253 253
254 254 if self.debugflag and not untrusted and self._reportuntrusted:
255 255 for n in alternates:
256 256 uvalue = self._ucfg.get(section, n)
257 257 if uvalue is not None and uvalue != value:
258 258 self.debug("ignoring untrusted configuration option "
259 259 "%s.%s = %s\n" % (section, n, uvalue))
260 260 return value
261 261
262 262 def configpath(self, section, name, default=None, untrusted=False):
263 263 'get a path config item, expanded relative to repo root or config file'
264 264 v = self.config(section, name, default, untrusted)
265 265 if v is None:
266 266 return None
267 267 if not os.path.isabs(v) or "://" not in v:
268 268 src = self.configsource(section, name, untrusted)
269 269 if ':' in src:
270 270 base = os.path.dirname(src.rsplit(':')[0])
271 271 v = os.path.join(base, os.path.expanduser(v))
272 272 return v
273 273
274 274 def configbool(self, section, name, default=False, untrusted=False):
275 275 """parse a configuration element as a boolean
276 276
277 277 >>> u = ui(); s = 'foo'
278 278 >>> u.setconfig(s, 'true', 'yes')
279 279 >>> u.configbool(s, 'true')
280 280 True
281 281 >>> u.setconfig(s, 'false', 'no')
282 282 >>> u.configbool(s, 'false')
283 283 False
284 284 >>> u.configbool(s, 'unknown')
285 285 False
286 286 >>> u.configbool(s, 'unknown', True)
287 287 True
288 288 >>> u.setconfig(s, 'invalid', 'somevalue')
289 289 >>> u.configbool(s, 'invalid')
290 290 Traceback (most recent call last):
291 291 ...
292 292 ConfigError: foo.invalid is not a boolean ('somevalue')
293 293 """
294 294
295 295 v = self.config(section, name, None, untrusted)
296 296 if v is None:
297 297 return default
298 298 if isinstance(v, bool):
299 299 return v
300 300 b = util.parsebool(v)
301 301 if b is None:
302 302 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
303 303 % (section, name, v))
304 304 return b
305 305
306 306 def configint(self, section, name, default=None, untrusted=False):
307 307 """parse a configuration element as an integer
308 308
309 309 >>> u = ui(); s = 'foo'
310 310 >>> u.setconfig(s, 'int1', '42')
311 311 >>> u.configint(s, 'int1')
312 312 42
313 313 >>> u.setconfig(s, 'int2', '-42')
314 314 >>> u.configint(s, 'int2')
315 315 -42
316 316 >>> u.configint(s, 'unknown', 7)
317 317 7
318 318 >>> u.setconfig(s, 'invalid', 'somevalue')
319 319 >>> u.configint(s, 'invalid')
320 320 Traceback (most recent call last):
321 321 ...
322 322 ConfigError: foo.invalid is not an integer ('somevalue')
323 323 """
324 324
325 325 v = self.config(section, name, None, untrusted)
326 326 if v is None:
327 327 return default
328 328 try:
329 329 return int(v)
330 330 except ValueError:
331 331 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
332 332 % (section, name, v))
333 333
334 334 def configbytes(self, section, name, default=0, untrusted=False):
335 335 """parse a configuration element as a quantity in bytes
336 336
337 337 Units can be specified as b (bytes), k or kb (kilobytes), m or
338 338 mb (megabytes), g or gb (gigabytes).
339 339
340 340 >>> u = ui(); s = 'foo'
341 341 >>> u.setconfig(s, 'val1', '42')
342 342 >>> u.configbytes(s, 'val1')
343 343 42
344 344 >>> u.setconfig(s, 'val2', '42.5 kb')
345 345 >>> u.configbytes(s, 'val2')
346 346 43520
347 347 >>> u.configbytes(s, 'unknown', '7 MB')
348 348 7340032
349 349 >>> u.setconfig(s, 'invalid', 'somevalue')
350 350 >>> u.configbytes(s, 'invalid')
351 351 Traceback (most recent call last):
352 352 ...
353 353 ConfigError: foo.invalid is not a byte quantity ('somevalue')
354 354 """
355 355
356 356 value = self.config(section, name)
357 357 if value is None:
358 358 if not isinstance(default, str):
359 359 return default
360 360 value = default
361 361 try:
362 362 return util.sizetoint(value)
363 363 except error.ParseError:
364 364 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
365 365 % (section, name, value))
366 366
367 367 def configlist(self, section, name, default=None, untrusted=False):
368 368 """parse a configuration element as a list of comma/space separated
369 369 strings
370 370
371 371 >>> u = ui(); s = 'foo'
372 372 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
373 373 >>> u.configlist(s, 'list1')
374 374 ['this', 'is', 'a small', 'test']
375 375 """
376 376
377 377 def _parse_plain(parts, s, offset):
378 378 whitespace = False
379 379 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
380 380 whitespace = True
381 381 offset += 1
382 382 if offset >= len(s):
383 383 return None, parts, offset
384 384 if whitespace:
385 385 parts.append('')
386 386 if s[offset] == '"' and not parts[-1]:
387 387 return _parse_quote, parts, offset + 1
388 388 elif s[offset] == '"' and parts[-1][-1] == '\\':
389 389 parts[-1] = parts[-1][:-1] + s[offset]
390 390 return _parse_plain, parts, offset + 1
391 391 parts[-1] += s[offset]
392 392 return _parse_plain, parts, offset + 1
393 393
394 394 def _parse_quote(parts, s, offset):
395 395 if offset < len(s) and s[offset] == '"': # ""
396 396 parts.append('')
397 397 offset += 1
398 398 while offset < len(s) and (s[offset].isspace() or
399 399 s[offset] == ','):
400 400 offset += 1
401 401 return _parse_plain, parts, offset
402 402
403 403 while offset < len(s) and s[offset] != '"':
404 404 if (s[offset] == '\\' and offset + 1 < len(s)
405 405 and s[offset + 1] == '"'):
406 406 offset += 1
407 407 parts[-1] += '"'
408 408 else:
409 409 parts[-1] += s[offset]
410 410 offset += 1
411 411
412 412 if offset >= len(s):
413 413 real_parts = _configlist(parts[-1])
414 414 if not real_parts:
415 415 parts[-1] = '"'
416 416 else:
417 417 real_parts[0] = '"' + real_parts[0]
418 418 parts = parts[:-1]
419 419 parts.extend(real_parts)
420 420 return None, parts, offset
421 421
422 422 offset += 1
423 423 while offset < len(s) and s[offset] in [' ', ',']:
424 424 offset += 1
425 425
426 426 if offset < len(s):
427 427 if offset + 1 == len(s) and s[offset] == '"':
428 428 parts[-1] += '"'
429 429 offset += 1
430 430 else:
431 431 parts.append('')
432 432 else:
433 433 return None, parts, offset
434 434
435 435 return _parse_plain, parts, offset
436 436
437 437 def _configlist(s):
438 438 s = s.rstrip(' ,')
439 439 if not s:
440 440 return []
441 441 parser, parts, offset = _parse_plain, [''], 0
442 442 while parser:
443 443 parser, parts, offset = parser(parts, s, offset)
444 444 return parts
445 445
446 446 result = self.config(section, name, untrusted=untrusted)
447 447 if result is None:
448 448 result = default or []
449 449 if isinstance(result, basestring):
450 450 result = _configlist(result.lstrip(' ,\n'))
451 451 if result is None:
452 452 result = default or []
453 453 return result
454 454
455 455 def has_section(self, section, untrusted=False):
456 456 '''tell whether section exists in config.'''
457 457 return section in self._data(untrusted)
458 458
459 459 def configitems(self, section, untrusted=False):
460 460 items = self._data(untrusted).items(section)
461 461 if self.debugflag and not untrusted and self._reportuntrusted:
462 462 for k, v in self._ucfg.items(section):
463 463 if self._tcfg.get(section, k) != v:
464 464 self.debug("ignoring untrusted configuration option "
465 465 "%s.%s = %s\n" % (section, k, v))
466 466 return items
467 467
468 468 def walkconfig(self, untrusted=False):
469 469 cfg = self._data(untrusted)
470 470 for section in cfg.sections():
471 471 for name, value in self.configitems(section, untrusted):
472 472 yield section, name, value
473 473
474 474 def plain(self, feature=None):
475 475 '''is plain mode active?
476 476
477 477 Plain mode means that all configuration variables which affect
478 478 the behavior and output of Mercurial should be
479 479 ignored. Additionally, the output should be stable,
480 480 reproducible and suitable for use in scripts or applications.
481 481
482 482 The only way to trigger plain mode is by setting either the
483 483 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
484 484
485 485 The return value can either be
486 486 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
487 487 - True otherwise
488 488 '''
489 489 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
490 490 return False
491 491 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
492 492 if feature and exceptions:
493 493 return feature not in exceptions
494 494 return True
495 495
496 496 def username(self):
497 497 """Return default username to be used in commits.
498 498
499 499 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
500 500 and stop searching if one of these is set.
501 501 If not found and ui.askusername is True, ask the user, else use
502 502 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
503 503 """
504 504 user = os.environ.get("HGUSER")
505 505 if user is None:
506 506 user = self.config("ui", ["username", "user"])
507 507 if user is not None:
508 508 user = os.path.expandvars(user)
509 509 if user is None:
510 510 user = os.environ.get("EMAIL")
511 511 if user is None and self.configbool("ui", "askusername"):
512 512 user = self.prompt(_("enter a commit username:"), default=None)
513 513 if user is None and not self.interactive():
514 514 try:
515 515 user = '%s@%s' % (util.getuser(), socket.getfqdn())
516 516 self.warn(_("no username found, using '%s' instead\n") % user)
517 517 except KeyError:
518 518 pass
519 519 if not user:
520 520 raise util.Abort(_('no username supplied'),
521 521 hint=_('use "hg config --edit" '
522 522 'to set your username'))
523 523 if "\n" in user:
524 524 raise util.Abort(_("username %s contains a newline\n") % repr(user))
525 525 return user
526 526
527 527 def shortuser(self, user):
528 528 """Return a short representation of a user name or email address."""
529 529 if not self.verbose:
530 530 user = util.shortuser(user)
531 531 return user
532 532
533 533 def expandpath(self, loc, default=None):
534 534 """Return repository location relative to cwd or from [paths]"""
535 535 if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
536 536 return loc
537 537
538 538 p = self.paths.getpath(loc, default=default)
539 539 if p:
540 540 return p.loc
541 541 return loc
542 542
543 543 @util.propertycache
544 544 def paths(self):
545 545 return paths(self)
546 546
547 547 def pushbuffer(self, error=False, subproc=False):
548 548 """install a buffer to capture standard output of the ui object
549 549
550 550 If error is True, the error output will be captured too.
551 551
552 552 If subproc is True, output from subprocesses (typically hooks) will be
553 553 captured too."""
554 554 self._buffers.append([])
555 555 self._bufferstates.append((error, subproc))
556 556
557 557 def popbuffer(self, labeled=False):
558 558 '''pop the last buffer and return the buffered output
559 559
560 560 If labeled is True, any labels associated with buffered
561 561 output will be handled. By default, this has no effect
562 562 on the output returned, but extensions and GUI tools may
563 563 handle this argument and returned styled output. If output
564 564 is being buffered so it can be captured and parsed or
565 565 processed, labeled should not be set to True.
566 566 '''
567 567 self._bufferstates.pop()
568 568 return "".join(self._buffers.pop())
569 569
570 570 def write(self, *args, **opts):
571 571 '''write args to output
572 572
573 573 By default, this method simply writes to the buffer or stdout,
574 574 but extensions or GUI tools may override this method,
575 575 write_err(), popbuffer(), and label() to style output from
576 576 various parts of hg.
577 577
578 578 An optional keyword argument, "label", can be passed in.
579 579 This should be a string containing label names separated by
580 580 space. Label names take the form of "topic.type". For example,
581 581 ui.debug() issues a label of "ui.debug".
582 582
583 583 When labeling output for a specific command, a label of
584 584 "cmdname.type" is recommended. For example, status issues
585 585 a label of "status.modified" for modified files.
586 586 '''
587 self._progclear()
587 588 if self._buffers:
588 589 self._buffers[-1].extend([str(a) for a in args])
589 590 else:
590 591 for a in args:
591 592 self.fout.write(str(a))
592 593
593 594 def write_err(self, *args, **opts):
595 self._progclear()
594 596 try:
595 597 if self._bufferstates and self._bufferstates[-1][0]:
596 598 return self.write(*args, **opts)
597 599 if not getattr(self.fout, 'closed', False):
598 600 self.fout.flush()
599 601 for a in args:
600 602 self.ferr.write(str(a))
601 603 # stderr may be buffered under win32 when redirected to files,
602 604 # including stdout.
603 605 if not getattr(self.ferr, 'closed', False):
604 606 self.ferr.flush()
605 607 except IOError, inst:
606 608 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
607 609 raise
608 610
609 611 def flush(self):
610 612 try: self.fout.flush()
611 613 except (IOError, ValueError): pass
612 614 try: self.ferr.flush()
613 615 except (IOError, ValueError): pass
614 616
615 617 def _isatty(self, fh):
616 618 if self.configbool('ui', 'nontty', False):
617 619 return False
618 620 return util.isatty(fh)
619 621
620 622 def interactive(self):
621 623 '''is interactive input allowed?
622 624
623 625 An interactive session is a session where input can be reasonably read
624 626 from `sys.stdin'. If this function returns false, any attempt to read
625 627 from stdin should fail with an error, unless a sensible default has been
626 628 specified.
627 629
628 630 Interactiveness is triggered by the value of the `ui.interactive'
629 631 configuration variable or - if it is unset - when `sys.stdin' points
630 632 to a terminal device.
631 633
632 634 This function refers to input only; for output, see `ui.formatted()'.
633 635 '''
634 636 i = self.configbool("ui", "interactive", None)
635 637 if i is None:
636 638 # some environments replace stdin without implementing isatty
637 639 # usually those are non-interactive
638 640 return self._isatty(self.fin)
639 641
640 642 return i
641 643
642 644 def termwidth(self):
643 645 '''how wide is the terminal in columns?
644 646 '''
645 647 if 'COLUMNS' in os.environ:
646 648 try:
647 649 return int(os.environ['COLUMNS'])
648 650 except ValueError:
649 651 pass
650 652 return util.termwidth()
651 653
652 654 def formatted(self):
653 655 '''should formatted output be used?
654 656
655 657 It is often desirable to format the output to suite the output medium.
656 658 Examples of this are truncating long lines or colorizing messages.
657 659 However, this is not often not desirable when piping output into other
658 660 utilities, e.g. `grep'.
659 661
660 662 Formatted output is triggered by the value of the `ui.formatted'
661 663 configuration variable or - if it is unset - when `sys.stdout' points
662 664 to a terminal device. Please note that `ui.formatted' should be
663 665 considered an implementation detail; it is not intended for use outside
664 666 Mercurial or its extensions.
665 667
666 668 This function refers to output only; for input, see `ui.interactive()'.
667 669 This function always returns false when in plain mode, see `ui.plain()'.
668 670 '''
669 671 if self.plain():
670 672 return False
671 673
672 674 i = self.configbool("ui", "formatted", None)
673 675 if i is None:
674 676 # some environments replace stdout without implementing isatty
675 677 # usually those are non-interactive
676 678 return self._isatty(self.fout)
677 679
678 680 return i
679 681
680 682 def _readline(self, prompt=''):
681 683 if self._isatty(self.fin):
682 684 try:
683 685 # magically add command line editing support, where
684 686 # available
685 687 import readline
686 688 # force demandimport to really load the module
687 689 readline.read_history_file
688 690 # windows sometimes raises something other than ImportError
689 691 except Exception:
690 692 pass
691 693
692 694 # call write() so output goes through subclassed implementation
693 695 # e.g. color extension on Windows
694 696 self.write(prompt)
695 697
696 698 # instead of trying to emulate raw_input, swap (self.fin,
697 699 # self.fout) with (sys.stdin, sys.stdout)
698 700 oldin = sys.stdin
699 701 oldout = sys.stdout
700 702 sys.stdin = self.fin
701 703 sys.stdout = self.fout
702 704 # prompt ' ' must exist; otherwise readline may delete entire line
703 705 # - http://bugs.python.org/issue12833
704 706 line = raw_input(' ')
705 707 sys.stdin = oldin
706 708 sys.stdout = oldout
707 709
708 710 # When stdin is in binary mode on Windows, it can cause
709 711 # raw_input() to emit an extra trailing carriage return
710 712 if os.linesep == '\r\n' and line and line[-1] == '\r':
711 713 line = line[:-1]
712 714 return line
713 715
714 716 def prompt(self, msg, default="y"):
715 717 """Prompt user with msg, read response.
716 718 If ui is not interactive, the default is returned.
717 719 """
718 720 if not self.interactive():
719 721 self.write(msg, ' ', default, "\n")
720 722 return default
721 723 try:
722 724 r = self._readline(self.label(msg, 'ui.prompt'))
723 725 if not r:
724 726 r = default
725 727 if self.configbool('ui', 'promptecho'):
726 728 self.write(r, "\n")
727 729 return r
728 730 except EOFError:
729 731 raise util.Abort(_('response expected'))
730 732
731 733 @staticmethod
732 734 def extractchoices(prompt):
733 735 """Extract prompt message and list of choices from specified prompt.
734 736
735 737 This returns tuple "(message, choices)", and "choices" is the
736 738 list of tuple "(response character, text without &)".
737 739 """
738 740 parts = prompt.split('$$')
739 741 msg = parts[0].rstrip(' ')
740 742 choices = [p.strip(' ') for p in parts[1:]]
741 743 return (msg,
742 744 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
743 745 for s in choices])
744 746
745 747 def promptchoice(self, prompt, default=0):
746 748 """Prompt user with a message, read response, and ensure it matches
747 749 one of the provided choices. The prompt is formatted as follows:
748 750
749 751 "would you like fries with that (Yn)? $$ &Yes $$ &No"
750 752
751 753 The index of the choice is returned. Responses are case
752 754 insensitive. If ui is not interactive, the default is
753 755 returned.
754 756 """
755 757
756 758 msg, choices = self.extractchoices(prompt)
757 759 resps = [r for r, t in choices]
758 760 while True:
759 761 r = self.prompt(msg, resps[default])
760 762 if r.lower() in resps:
761 763 return resps.index(r.lower())
762 764 self.write(_("unrecognized response\n"))
763 765
764 766 def getpass(self, prompt=None, default=None):
765 767 if not self.interactive():
766 768 return default
767 769 try:
768 770 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
769 771 # disable getpass() only if explicitly specified. it's still valid
770 772 # to interact with tty even if fin is not a tty.
771 773 if self.configbool('ui', 'nontty'):
772 774 return self.fin.readline().rstrip('\n')
773 775 else:
774 776 return getpass.getpass('')
775 777 except EOFError:
776 778 raise util.Abort(_('response expected'))
777 779 def status(self, *msg, **opts):
778 780 '''write status message to output (if ui.quiet is False)
779 781
780 782 This adds an output label of "ui.status".
781 783 '''
782 784 if not self.quiet:
783 785 opts['label'] = opts.get('label', '') + ' ui.status'
784 786 self.write(*msg, **opts)
785 787 def warn(self, *msg, **opts):
786 788 '''write warning message to output (stderr)
787 789
788 790 This adds an output label of "ui.warning".
789 791 '''
790 792 opts['label'] = opts.get('label', '') + ' ui.warning'
791 793 self.write_err(*msg, **opts)
792 794 def note(self, *msg, **opts):
793 795 '''write note to output (if ui.verbose is True)
794 796
795 797 This adds an output label of "ui.note".
796 798 '''
797 799 if self.verbose:
798 800 opts['label'] = opts.get('label', '') + ' ui.note'
799 801 self.write(*msg, **opts)
800 802 def debug(self, *msg, **opts):
801 803 '''write debug message to output (if ui.debugflag is True)
802 804
803 805 This adds an output label of "ui.debug".
804 806 '''
805 807 if self.debugflag:
806 808 opts['label'] = opts.get('label', '') + ' ui.debug'
807 809 self.write(*msg, **opts)
808 810 def edit(self, text, user, extra={}, editform=None):
809 811 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
810 812 text=True)
811 813 try:
812 814 f = os.fdopen(fd, "w")
813 815 f.write(text)
814 816 f.close()
815 817
816 818 environ = {'HGUSER': user}
817 819 if 'transplant_source' in extra:
818 820 environ.update({'HGREVISION': hex(extra['transplant_source'])})
819 821 for label in ('intermediate-source', 'source', 'rebase_source'):
820 822 if label in extra:
821 823 environ.update({'HGREVISION': extra[label]})
822 824 break
823 825 if editform:
824 826 environ.update({'HGEDITFORM': editform})
825 827
826 828 editor = self.geteditor()
827 829
828 830 self.system("%s \"%s\"" % (editor, name),
829 831 environ=environ,
830 832 onerr=util.Abort, errprefix=_("edit failed"))
831 833
832 834 f = open(name)
833 835 t = f.read()
834 836 f.close()
835 837 finally:
836 838 os.unlink(name)
837 839
838 840 return t
839 841
840 842 def system(self, cmd, environ={}, cwd=None, onerr=None, errprefix=None):
841 843 '''execute shell command with appropriate output stream. command
842 844 output will be redirected if fout is not stdout.
843 845 '''
844 846 out = self.fout
845 847 if any(s[1] for s in self._bufferstates):
846 848 out = self
847 849 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
848 850 errprefix=errprefix, out=out)
849 851
850 852 def traceback(self, exc=None, force=False):
851 853 '''print exception traceback if traceback printing enabled or forced.
852 854 only to call in exception handler. returns true if traceback
853 855 printed.'''
854 856 if self.tracebackflag or force:
855 857 if exc is None:
856 858 exc = sys.exc_info()
857 859 cause = getattr(exc[1], 'cause', None)
858 860
859 861 if cause is not None:
860 862 causetb = traceback.format_tb(cause[2])
861 863 exctb = traceback.format_tb(exc[2])
862 864 exconly = traceback.format_exception_only(cause[0], cause[1])
863 865
864 866 # exclude frame where 'exc' was chained and rethrown from exctb
865 867 self.write_err('Traceback (most recent call last):\n',
866 868 ''.join(exctb[:-1]),
867 869 ''.join(causetb),
868 870 ''.join(exconly))
869 871 else:
870 872 self.flush() # flush debug or status message
871 873 traceback.print_exception(exc[0], exc[1], exc[2],
872 874 file=self.ferr)
873 875 return self.tracebackflag or force
874 876
875 877 def geteditor(self):
876 878 '''return editor to use'''
877 879 if sys.platform == 'plan9':
878 880 # vi is the MIPS instruction simulator on Plan 9. We
879 881 # instead default to E to plumb commit messages to
880 882 # avoid confusion.
881 883 editor = 'E'
882 884 else:
883 885 editor = 'vi'
884 886 return (os.environ.get("HGEDITOR") or
885 887 self.config("ui", "editor") or
886 888 os.environ.get("VISUAL") or
887 889 os.environ.get("EDITOR", editor))
888 890
891 @util.propertycache
892 def _progbar(self):
893 """setup the progbar singleton to the ui object"""
894 if (self.quiet or self.debugflag
895 or self.configbool('progress', 'disable', True)
896 or not progress.shouldprint(self)):
897 return None
898 return getprogbar(self)
899
900 def _progclear(self):
901 """clear progress bar output if any. use it before any output"""
902 if '_progbar' not in vars(self): # nothing loadef yet
903 return
904 if self._progbar is not None and self._progbar.printed:
905 self._progbar.clear()
906
889 907 def progress(self, topic, pos, item="", unit="", total=None):
890 908 '''show a progress message
891 909
892 910 With stock hg, this is simply a debug message that is hidden
893 911 by default, but with extensions or GUI tools it may be
894 912 visible. 'topic' is the current operation, 'item' is a
895 913 non-numeric marker of the current position (i.e. the currently
896 914 in-process file), 'pos' is the current numeric position (i.e.
897 915 revision, bytes, etc.), unit is a corresponding unit label,
898 916 and total is the highest expected pos.
899 917
900 918 Multiple nested topics may be active at a time.
901 919
902 920 All topics should be marked closed by setting pos to None at
903 921 termination.
904 922 '''
905
923 if self._progbar is not None:
924 self._progbar.progress(topic, pos, item=item, unit=unit,
925 total=total)
906 926 if pos is None or not self.configbool('progress', 'debug'):
907 927 return
908 928
909 929 if unit:
910 930 unit = ' ' + unit
911 931 if item:
912 932 item = ' ' + item
913 933
914 934 if total:
915 935 pct = 100.0 * pos / total
916 936 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
917 937 % (topic, item, pos, total, unit, pct))
918 938 else:
919 939 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
920 940
921 941 def log(self, service, *msg, **opts):
922 942 '''hook for logging facility extensions
923 943
924 944 service should be a readily-identifiable subsystem, which will
925 945 allow filtering.
926 946 message should be a newline-terminated string to log.
927 947 '''
928 948 pass
929 949
930 950 def label(self, msg, label):
931 951 '''style msg based on supplied label
932 952
933 953 Like ui.write(), this just returns msg unchanged, but extensions
934 954 and GUI tools can override it to allow styling output without
935 955 writing it.
936 956
937 957 ui.write(s, 'label') is equivalent to
938 958 ui.write(ui.label(s, 'label')).
939 959 '''
940 960 return msg
941 961
942 962 class paths(dict):
943 963 """Represents a collection of paths and their configs.
944 964
945 965 Data is initially derived from ui instances and the config files they have
946 966 loaded.
947 967 """
948 968 def __init__(self, ui):
949 969 dict.__init__(self)
950 970
951 971 for name, loc in ui.configitems('paths'):
952 972 # No location is the same as not existing.
953 973 if not loc:
954 974 continue
955 975 self[name] = path(name, rawloc=loc)
956 976
957 977 def getpath(self, name, default=None):
958 978 """Return a ``path`` for the specified name, falling back to a default.
959 979
960 980 Returns the first of ``name`` or ``default`` that is present, or None
961 981 if neither is present.
962 982 """
963 983 try:
964 984 return self[name]
965 985 except KeyError:
966 986 if default is not None:
967 987 try:
968 988 return self[default]
969 989 except KeyError:
970 990 pass
971 991
972 992 return None
973 993
974 994 class path(object):
975 995 """Represents an individual path and its configuration."""
976 996
977 997 def __init__(self, name, rawloc=None):
978 998 """Construct a path from its config options.
979 999
980 1000 ``name`` is the symbolic name of the path.
981 1001 ``rawloc`` is the raw location, as defined in the config.
982 1002 """
983 1003 self.name = name
984 1004 # We'll do more intelligent things with rawloc in the future.
985 1005 self.loc = rawloc
986 1006
987 1007 # we instantiate one globally shared progress bar to avoid
988 1008 # competing progress bars when multiple UI objects get created
989 1009 _progresssingleton = None
990 1010
991 1011 def getprogbar(ui):
992 1012 global _progresssingleton
993 1013 if _progresssingleton is None:
994 1014 # passing 'ui' object to the singleton is fishy,
995 1015 # this is how the extension used to work but feel free to rework it.
996 1016 _progresssingleton = progress.progbar(ui)
997 1017 return _progresssingleton
@@ -1,572 +1,573 b''
1 1 Create test repository:
2 2
3 3 $ hg init repo
4 4 $ cd repo
5 5 $ echo x1 > x.txt
6 6
7 7 $ hg init foo
8 8 $ cd foo
9 9 $ echo y1 > y.txt
10 10
11 11 $ hg init bar
12 12 $ cd bar
13 13 $ echo z1 > z.txt
14 14
15 15 $ cd ..
16 16 $ echo 'bar = bar' > .hgsub
17 17
18 18 $ cd ..
19 19 $ echo 'foo = foo' > .hgsub
20 20
21 21 Add files --- .hgsub files must go first to trigger subrepos:
22 22
23 23 $ hg add -S .hgsub
24 24 $ hg add -S foo/.hgsub
25 25 $ hg add -S foo/bar
26 26 adding foo/bar/z.txt (glob)
27 27 $ hg add -S
28 28 adding x.txt
29 29 adding foo/y.txt (glob)
30 30
31 31 Test recursive status without committing anything:
32 32
33 33 $ hg status -S
34 34 A .hgsub
35 35 A foo/.hgsub
36 36 A foo/bar/z.txt
37 37 A foo/y.txt
38 38 A x.txt
39 39
40 40 Test recursive diff without committing anything:
41 41
42 42 $ hg diff --nodates -S foo
43 43 diff -r 000000000000 foo/.hgsub
44 44 --- /dev/null
45 45 +++ b/foo/.hgsub
46 46 @@ -0,0 +1,1 @@
47 47 +bar = bar
48 48 diff -r 000000000000 foo/y.txt
49 49 --- /dev/null
50 50 +++ b/foo/y.txt
51 51 @@ -0,0 +1,1 @@
52 52 +y1
53 53 diff -r 000000000000 foo/bar/z.txt
54 54 --- /dev/null
55 55 +++ b/foo/bar/z.txt
56 56 @@ -0,0 +1,1 @@
57 57 +z1
58 58
59 59 Commits:
60 60
61 61 $ hg commit -m fails
62 62 abort: uncommitted changes in subrepository 'foo'
63 63 (use --subrepos for recursive commit)
64 64 [255]
65 65
66 66 The --subrepos flag overwrite the config setting:
67 67
68 68 $ hg commit -m 0-0-0 --config ui.commitsubrepos=No --subrepos
69 69 committing subrepository foo
70 70 committing subrepository foo/bar (glob)
71 71
72 72 $ cd foo
73 73 $ echo y2 >> y.txt
74 74 $ hg commit -m 0-1-0
75 75
76 76 $ cd bar
77 77 $ echo z2 >> z.txt
78 78 $ hg commit -m 0-1-1
79 79
80 80 $ cd ..
81 81 $ hg commit -m 0-2-1
82 82
83 83 $ cd ..
84 84 $ hg commit -m 1-2-1
85 85
86 86 Change working directory:
87 87
88 88 $ echo y3 >> foo/y.txt
89 89 $ echo z3 >> foo/bar/z.txt
90 90 $ hg status -S
91 91 M foo/bar/z.txt
92 92 M foo/y.txt
93 93 $ hg diff --nodates -S
94 94 diff -r d254738c5f5e foo/y.txt
95 95 --- a/foo/y.txt
96 96 +++ b/foo/y.txt
97 97 @@ -1,2 +1,3 @@
98 98 y1
99 99 y2
100 100 +y3
101 101 diff -r 9647f22de499 foo/bar/z.txt
102 102 --- a/foo/bar/z.txt
103 103 +++ b/foo/bar/z.txt
104 104 @@ -1,2 +1,3 @@
105 105 z1
106 106 z2
107 107 +z3
108 108
109 109 Status call crossing repository boundaries:
110 110
111 111 $ hg status -S foo/bar/z.txt
112 112 M foo/bar/z.txt
113 113 $ hg status -S -I 'foo/?.txt'
114 114 M foo/y.txt
115 115 $ hg status -S -I '**/?.txt'
116 116 M foo/bar/z.txt
117 117 M foo/y.txt
118 118 $ hg diff --nodates -S -I '**/?.txt'
119 119 diff -r d254738c5f5e foo/y.txt
120 120 --- a/foo/y.txt
121 121 +++ b/foo/y.txt
122 122 @@ -1,2 +1,3 @@
123 123 y1
124 124 y2
125 125 +y3
126 126 diff -r 9647f22de499 foo/bar/z.txt
127 127 --- a/foo/bar/z.txt
128 128 +++ b/foo/bar/z.txt
129 129 @@ -1,2 +1,3 @@
130 130 z1
131 131 z2
132 132 +z3
133 133
134 134 Status from within a subdirectory:
135 135
136 136 $ mkdir dir
137 137 $ cd dir
138 138 $ echo a1 > a.txt
139 139 $ hg status -S
140 140 M foo/bar/z.txt
141 141 M foo/y.txt
142 142 ? dir/a.txt
143 143 $ hg diff --nodates -S
144 144 diff -r d254738c5f5e foo/y.txt
145 145 --- a/foo/y.txt
146 146 +++ b/foo/y.txt
147 147 @@ -1,2 +1,3 @@
148 148 y1
149 149 y2
150 150 +y3
151 151 diff -r 9647f22de499 foo/bar/z.txt
152 152 --- a/foo/bar/z.txt
153 153 +++ b/foo/bar/z.txt
154 154 @@ -1,2 +1,3 @@
155 155 z1
156 156 z2
157 157 +z3
158 158
159 159 Status with relative path:
160 160
161 161 $ hg status -S ..
162 162 M ../foo/bar/z.txt
163 163 M ../foo/y.txt
164 164 ? a.txt
165 165
166 166 XXX: filtering lfilesrepo.status() in 3.3-rc causes these files to be listed as
167 167 added instead of modified.
168 168 $ hg status -S .. --config extensions.largefiles=
169 169 M ../foo/bar/z.txt
170 170 M ../foo/y.txt
171 171 ? a.txt
172 172
173 173 $ hg diff --nodates -S ..
174 174 diff -r d254738c5f5e foo/y.txt
175 175 --- a/foo/y.txt
176 176 +++ b/foo/y.txt
177 177 @@ -1,2 +1,3 @@
178 178 y1
179 179 y2
180 180 +y3
181 181 diff -r 9647f22de499 foo/bar/z.txt
182 182 --- a/foo/bar/z.txt
183 183 +++ b/foo/bar/z.txt
184 184 @@ -1,2 +1,3 @@
185 185 z1
186 186 z2
187 187 +z3
188 188 $ cd ..
189 189
190 190 Cleanup and final commit:
191 191
192 192 $ rm -r dir
193 193 $ hg commit --subrepos -m 2-3-2
194 194 committing subrepository foo
195 195 committing subrepository foo/bar (glob)
196 196
197 197 Test explicit path commands within subrepos: add/forget
198 198 $ echo z1 > foo/bar/z2.txt
199 199 $ hg status -S
200 200 ? foo/bar/z2.txt
201 201 $ hg add foo/bar/z2.txt
202 202 $ hg status -S
203 203 A foo/bar/z2.txt
204 204 $ hg forget foo/bar/z2.txt
205 205 $ hg status -S
206 206 ? foo/bar/z2.txt
207 207 $ hg forget foo/bar/z2.txt
208 208 not removing foo/bar/z2.txt: file is already untracked (glob)
209 209 [1]
210 210 $ hg status -S
211 211 ? foo/bar/z2.txt
212 212 $ rm foo/bar/z2.txt
213 213
214 214 Log with the relationships between repo and its subrepo:
215 215
216 216 $ hg log --template '{rev}:{node|short} {desc}\n'
217 217 2:1326fa26d0c0 2-3-2
218 218 1:4b3c9ff4f66b 1-2-1
219 219 0:23376cbba0d8 0-0-0
220 220
221 221 $ hg -R foo log --template '{rev}:{node|short} {desc}\n'
222 222 3:65903cebad86 2-3-2
223 223 2:d254738c5f5e 0-2-1
224 224 1:8629ce7dcc39 0-1-0
225 225 0:af048e97ade2 0-0-0
226 226
227 227 $ hg -R foo/bar log --template '{rev}:{node|short} {desc}\n'
228 228 2:31ecbdafd357 2-3-2
229 229 1:9647f22de499 0-1-1
230 230 0:4904098473f9 0-0-0
231 231
232 232 Status between revisions:
233 233
234 234 $ hg status -S
235 235 $ hg status -S --rev 0:1
236 236 M .hgsubstate
237 237 M foo/.hgsubstate
238 238 M foo/bar/z.txt
239 239 M foo/y.txt
240 240 $ hg diff --nodates -S -I '**/?.txt' --rev 0:1
241 241 diff -r af048e97ade2 -r d254738c5f5e foo/y.txt
242 242 --- a/foo/y.txt
243 243 +++ b/foo/y.txt
244 244 @@ -1,1 +1,2 @@
245 245 y1
246 246 +y2
247 247 diff -r 4904098473f9 -r 9647f22de499 foo/bar/z.txt
248 248 --- a/foo/bar/z.txt
249 249 +++ b/foo/bar/z.txt
250 250 @@ -1,1 +1,2 @@
251 251 z1
252 252 +z2
253 253
254 254 Enable progress extension for archive tests:
255 255
256 256 $ cp $HGRCPATH $HGRCPATH.no-progress
257 257 $ cat >> $HGRCPATH <<EOF
258 258 > [extensions]
259 259 > progress =
260 260 > [progress]
261 > disable=False
261 262 > assume-tty = 1
262 263 > delay = 0
263 264 > # set changedelay really large so we don't see nested topics
264 265 > changedelay = 30000
265 266 > format = topic bar number
266 267 > refresh = 0
267 268 > width = 60
268 269 > EOF
269 270
270 271 Test archiving to a directory tree (the doubled lines in the output
271 272 only show up in the test output, not in real usage):
272 273
273 274 $ hg archive --subrepos ../archive
274 275 \r (no-eol) (esc)
275 276 archiving [ ] 0/3\r (no-eol) (esc)
276 277 archiving [=============> ] 1/3\r (no-eol) (esc)
277 278 archiving [===========================> ] 2/3\r (no-eol) (esc)
278 279 archiving [==========================================>] 3/3\r (no-eol) (esc)
279 280 \r (no-eol) (esc)
280 281 \r (no-eol) (esc)
281 282 archiving (foo) [ ] 0/3\r (no-eol) (esc)
282 283 archiving (foo) [===========> ] 1/3\r (no-eol) (esc)
283 284 archiving (foo) [=======================> ] 2/3\r (no-eol) (esc)
284 285 archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
285 286 \r (no-eol) (esc)
286 287 \r (no-eol) (esc)
287 288 archiving (foo/bar) [ ] 0/1\r (no-eol) (glob) (esc)
288 289 archiving (foo/bar) [================================>] 1/1\r (no-eol) (glob) (esc)
289 290 \r (no-eol) (esc)
290 291 $ find ../archive | sort
291 292 ../archive
292 293 ../archive/.hg_archival.txt
293 294 ../archive/.hgsub
294 295 ../archive/.hgsubstate
295 296 ../archive/foo
296 297 ../archive/foo/.hgsub
297 298 ../archive/foo/.hgsubstate
298 299 ../archive/foo/bar
299 300 ../archive/foo/bar/z.txt
300 301 ../archive/foo/y.txt
301 302 ../archive/x.txt
302 303
303 304 Test archiving to zip file (unzip output is unstable):
304 305
305 306 $ hg archive --subrepos --prefix '.' ../archive.zip
306 307 \r (no-eol) (esc)
307 308 archiving [ ] 0/3\r (no-eol) (esc)
308 309 archiving [=============> ] 1/3\r (no-eol) (esc)
309 310 archiving [===========================> ] 2/3\r (no-eol) (esc)
310 311 archiving [==========================================>] 3/3\r (no-eol) (esc)
311 312 \r (no-eol) (esc)
312 313 \r (no-eol) (esc)
313 314 archiving (foo) [ ] 0/3\r (no-eol) (esc)
314 315 archiving (foo) [===========> ] 1/3\r (no-eol) (esc)
315 316 archiving (foo) [=======================> ] 2/3\r (no-eol) (esc)
316 317 archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
317 318 \r (no-eol) (esc)
318 319 \r (no-eol) (esc)
319 320 archiving (foo/bar) [ ] 0/1\r (no-eol) (glob) (esc)
320 321 archiving (foo/bar) [================================>] 1/1\r (no-eol) (glob) (esc)
321 322 \r (no-eol) (esc)
322 323
323 324 (unzip date formating is unstable, we do not care about it and glob it out)
324 325
325 326 $ unzip -l ../archive.zip
326 327 Archive: ../archive.zip
327 328 Length Date Time Name
328 329 --------- ---------- ----- ----
329 330 172 ?????????? 00:00 .hg_archival.txt (glob)
330 331 10 ?????????? 00:00 .hgsub (glob)
331 332 45 ?????????? 00:00 .hgsubstate (glob)
332 333 3 ?????????? 00:00 x.txt (glob)
333 334 10 ?????????? 00:00 foo/.hgsub (glob)
334 335 45 ?????????? 00:00 foo/.hgsubstate (glob)
335 336 9 ?????????? 00:00 foo/y.txt (glob)
336 337 9 ?????????? 00:00 foo/bar/z.txt (glob)
337 338 --------- -------
338 339 303 8 files
339 340
340 341 Test archiving a revision that references a subrepo that is not yet
341 342 cloned:
342 343
343 344 #if hardlink
344 345 $ hg clone -U . ../empty
345 346 \r (no-eol) (esc)
346 347 linking [ <=> ] 1\r (no-eol) (esc)
347 348 linking [ <=> ] 2\r (no-eol) (esc)
348 349 linking [ <=> ] 3\r (no-eol) (esc)
349 350 linking [ <=> ] 4\r (no-eol) (esc)
350 351 linking [ <=> ] 5\r (no-eol) (esc)
351 352 linking [ <=> ] 6\r (no-eol) (esc)
352 353 linking [ <=> ] 7\r (no-eol) (esc)
353 354 linking [ <=> ] 8\r (no-eol) (esc)
354 355 \r (no-eol) (esc)
355 356 #else
356 357 $ hg clone -U . ../empty
357 358 \r (no-eol) (esc)
358 359 linking [ <=> ] 1 (no-eol)
359 360 #endif
360 361
361 362 $ cd ../empty
362 363 #if hardlink
363 364 $ hg archive --subrepos -r tip --prefix './' ../archive.tar.gz
364 365 \r (no-eol) (esc)
365 366 archiving [ ] 0/3\r (no-eol) (esc)
366 367 archiving [=============> ] 1/3\r (no-eol) (esc)
367 368 archiving [===========================> ] 2/3\r (no-eol) (esc)
368 369 archiving [==========================================>] 3/3\r (no-eol) (esc)
369 370 \r (no-eol) (esc)
370 371 \r (no-eol) (esc)
371 372 linking [ <=> ] 1\r (no-eol) (esc)
372 373 linking [ <=> ] 2\r (no-eol) (esc)
373 374 linking [ <=> ] 3\r (no-eol) (esc)
374 375 linking [ <=> ] 4\r (no-eol) (esc)
375 376 linking [ <=> ] 5\r (no-eol) (esc)
376 377 linking [ <=> ] 6\r (no-eol) (esc)
377 378 linking [ <=> ] 7\r (no-eol) (esc)
378 379 linking [ <=> ] 8\r (no-eol) (esc)
379 380 \r (no-eol) (esc)
380 381 \r (no-eol) (esc)
381 382 archiving (foo) [ ] 0/3\r (no-eol) (esc)
382 383 archiving (foo) [===========> ] 1/3\r (no-eol) (esc)
383 384 archiving (foo) [=======================> ] 2/3\r (no-eol) (esc)
384 385 archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
385 386 \r (no-eol) (esc)
386 387 \r (no-eol) (esc)
387 388 linking [ <=> ] 1\r (no-eol) (esc)
388 389 linking [ <=> ] 2\r (no-eol) (esc)
389 390 linking [ <=> ] 3\r (no-eol) (esc)
390 391 linking [ <=> ] 4\r (no-eol) (esc)
391 392 linking [ <=> ] 5\r (no-eol) (esc)
392 393 linking [ <=> ] 6\r (no-eol) (esc)
393 394 \r (no-eol) (esc)
394 395 \r (no-eol) (esc)
395 396 archiving (foo/bar) [ ] 0/1\r (no-eol) (glob) (esc)
396 397 archiving (foo/bar) [================================>] 1/1\r (no-eol) (glob) (esc)
397 398 \r (no-eol) (esc)
398 399 cloning subrepo foo from $TESTTMP/repo/foo
399 400 cloning subrepo foo/bar from $TESTTMP/repo/foo/bar (glob)
400 401 #else
401 402 Note there's a slight output glitch on non-hardlink systems: the last
402 403 "linking" progress topic never gets closed, leading to slight output corruption on that platform.
403 404 $ hg archive --subrepos -r tip --prefix './' ../archive.tar.gz
404 405 \r (no-eol) (esc)
405 406 archiving [ ] 0/3\r (no-eol) (esc)
406 407 archiving [=============> ] 1/3\r (no-eol) (esc)
407 408 archiving [===========================> ] 2/3\r (no-eol) (esc)
408 409 archiving [==========================================>] 3/3\r (no-eol) (esc)
409 410 \r (no-eol) (esc)
410 411 \r (no-eol) (esc)
411 412 linking [ <=> ] 1\r (no-eol) (esc)
412 413 \r (no-eol) (esc)
413 414 \r (no-eol) (esc)
414 415 linking [ <=> ] 1cloning subrepo foo from $TESTTMP/repo/foo
415 416 cloning subrepo foo/bar from $TESTTMP/repo/foo/bar (glob)
416 417 #endif
417 418
418 419 Archive + subrepos uses '/' for all component separators
419 420
420 421 $ tar -tzf ../archive.tar.gz | sort
421 422 .hg_archival.txt
422 423 .hgsub
423 424 .hgsubstate
424 425 foo/.hgsub
425 426 foo/.hgsubstate
426 427 foo/bar/z.txt
427 428 foo/y.txt
428 429 x.txt
429 430
430 431 The newly cloned subrepos contain no working copy:
431 432
432 433 $ hg -R foo summary
433 434 parent: -1:000000000000 (no revision checked out)
434 435 branch: default
435 436 commit: (clean)
436 437 update: 4 new changesets (update)
437 438
438 439 Disable progress extension and cleanup:
439 440
440 441 $ mv $HGRCPATH.no-progress $HGRCPATH
441 442
442 443 Test archiving when there is a directory in the way for a subrepo
443 444 created by archive:
444 445
445 446 $ hg clone -U . ../almost-empty
446 447 $ cd ../almost-empty
447 448 $ mkdir foo
448 449 $ echo f > foo/f
449 450 $ hg archive --subrepos -r tip archive
450 451 cloning subrepo foo from $TESTTMP/empty/foo
451 452 abort: destination '$TESTTMP/almost-empty/foo' is not empty (in subrepo foo) (glob)
452 453 [255]
453 454
454 455 Clone and test outgoing:
455 456
456 457 $ cd ..
457 458 $ hg clone repo repo2
458 459 updating to branch default
459 460 cloning subrepo foo from $TESTTMP/repo/foo
460 461 cloning subrepo foo/bar from $TESTTMP/repo/foo/bar (glob)
461 462 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
462 463 $ cd repo2
463 464 $ hg outgoing -S
464 465 comparing with $TESTTMP/repo (glob)
465 466 searching for changes
466 467 no changes found
467 468 comparing with $TESTTMP/repo/foo
468 469 searching for changes
469 470 no changes found
470 471 comparing with $TESTTMP/repo/foo/bar
471 472 searching for changes
472 473 no changes found
473 474 [1]
474 475
475 476 Make nested change:
476 477
477 478 $ echo y4 >> foo/y.txt
478 479 $ hg diff --nodates -S
479 480 diff -r 65903cebad86 foo/y.txt
480 481 --- a/foo/y.txt
481 482 +++ b/foo/y.txt
482 483 @@ -1,3 +1,4 @@
483 484 y1
484 485 y2
485 486 y3
486 487 +y4
487 488 $ hg commit --subrepos -m 3-4-2
488 489 committing subrepository foo
489 490 $ hg outgoing -S
490 491 comparing with $TESTTMP/repo (glob)
491 492 searching for changes
492 493 changeset: 3:2655b8ecc4ee
493 494 tag: tip
494 495 user: test
495 496 date: Thu Jan 01 00:00:00 1970 +0000
496 497 summary: 3-4-2
497 498
498 499 comparing with $TESTTMP/repo/foo
499 500 searching for changes
500 501 changeset: 4:e96193d6cb36
501 502 tag: tip
502 503 user: test
503 504 date: Thu Jan 01 00:00:00 1970 +0000
504 505 summary: 3-4-2
505 506
506 507 comparing with $TESTTMP/repo/foo/bar
507 508 searching for changes
508 509 no changes found
509 510
510 511
511 512 Switch to original repo and setup default path:
512 513
513 514 $ cd ../repo
514 515 $ echo '[paths]' >> .hg/hgrc
515 516 $ echo 'default = ../repo2' >> .hg/hgrc
516 517
517 518 Test incoming:
518 519
519 520 $ hg incoming -S
520 521 comparing with $TESTTMP/repo2 (glob)
521 522 searching for changes
522 523 changeset: 3:2655b8ecc4ee
523 524 tag: tip
524 525 user: test
525 526 date: Thu Jan 01 00:00:00 1970 +0000
526 527 summary: 3-4-2
527 528
528 529 comparing with $TESTTMP/repo2/foo
529 530 searching for changes
530 531 changeset: 4:e96193d6cb36
531 532 tag: tip
532 533 user: test
533 534 date: Thu Jan 01 00:00:00 1970 +0000
534 535 summary: 3-4-2
535 536
536 537 comparing with $TESTTMP/repo2/foo/bar
537 538 searching for changes
538 539 no changes found
539 540
540 541 $ hg incoming -S --bundle incoming.hg
541 542 abort: cannot combine --bundle and --subrepos
542 543 [255]
543 544
544 545 Test missing subrepo:
545 546
546 547 $ rm -r foo
547 548 $ hg status -S
548 549 warning: error "unknown revision '65903cebad86f1a84bd4f1134f62fa7dcb7a1c98'" in subrepository "foo"
549 550
550 551 Issue2619: IndexError: list index out of range on hg add with subrepos
551 552 The subrepo must sorts after the explicit filename.
552 553
553 554 $ cd ..
554 555 $ hg init test
555 556 $ cd test
556 557 $ hg init x
557 558 $ echo abc > abc.txt
558 559 $ hg ci -Am "abc"
559 560 adding abc.txt
560 561 $ echo "x = x" >> .hgsub
561 562 $ hg add .hgsub
562 563 $ touch a x/a
563 564 $ hg add a x/a
564 565
565 566 $ hg ci -Sm "added x"
566 567 committing subrepository x
567 568 $ echo abc > x/a
568 569 $ hg revert --rev '.^' "set:subrepo('glob:x*')"
569 570 abort: subrepository 'x' does not exist in 25ac2c9b3180!
570 571 [255]
571 572
572 573 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now