##// END OF EJS Templates
formatter: make debug output prettier...
Yuya Nishihara -
r40313:1159031a default
parent child Browse files
Show More
@@ -1,659 +1,660
1 # formatter.py - generic output formatting for mercurial
1 # formatter.py - generic output formatting for mercurial
2 #
2 #
3 # Copyright 2012 Matt Mackall <mpm@selenic.com>
3 # Copyright 2012 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Generic output formatting for Mercurial
8 """Generic output formatting for Mercurial
9
9
10 The formatter provides API to show data in various ways. The following
10 The formatter provides API to show data in various ways. The following
11 functions should be used in place of ui.write():
11 functions should be used in place of ui.write():
12
12
13 - fm.write() for unconditional output
13 - fm.write() for unconditional output
14 - fm.condwrite() to show some extra data conditionally in plain output
14 - fm.condwrite() to show some extra data conditionally in plain output
15 - fm.context() to provide changectx to template output
15 - fm.context() to provide changectx to template output
16 - fm.data() to provide extra data to JSON or template output
16 - fm.data() to provide extra data to JSON or template output
17 - fm.plain() to show raw text that isn't provided to JSON or template output
17 - fm.plain() to show raw text that isn't provided to JSON or template output
18
18
19 To show structured data (e.g. date tuples, dicts, lists), apply fm.format*()
19 To show structured data (e.g. date tuples, dicts, lists), apply fm.format*()
20 beforehand so the data is converted to the appropriate data type. Use
20 beforehand so the data is converted to the appropriate data type. Use
21 fm.isplain() if you need to convert or format data conditionally which isn't
21 fm.isplain() if you need to convert or format data conditionally which isn't
22 supported by the formatter API.
22 supported by the formatter API.
23
23
24 To build nested structure (i.e. a list of dicts), use fm.nested().
24 To build nested structure (i.e. a list of dicts), use fm.nested().
25
25
26 See also https://www.mercurial-scm.org/wiki/GenericTemplatingPlan
26 See also https://www.mercurial-scm.org/wiki/GenericTemplatingPlan
27
27
28 fm.condwrite() vs 'if cond:':
28 fm.condwrite() vs 'if cond:':
29
29
30 In most cases, use fm.condwrite() so users can selectively show the data
30 In most cases, use fm.condwrite() so users can selectively show the data
31 in template output. If it's costly to build data, use plain 'if cond:' with
31 in template output. If it's costly to build data, use plain 'if cond:' with
32 fm.write().
32 fm.write().
33
33
34 fm.nested() vs fm.formatdict() (or fm.formatlist()):
34 fm.nested() vs fm.formatdict() (or fm.formatlist()):
35
35
36 fm.nested() should be used to form a tree structure (a list of dicts of
36 fm.nested() should be used to form a tree structure (a list of dicts of
37 lists of dicts...) which can be accessed through template keywords, e.g.
37 lists of dicts...) which can be accessed through template keywords, e.g.
38 "{foo % "{bar % {...}} {baz % {...}}"}". On the other hand, fm.formatdict()
38 "{foo % "{bar % {...}} {baz % {...}}"}". On the other hand, fm.formatdict()
39 exports a dict-type object to template, which can be accessed by e.g.
39 exports a dict-type object to template, which can be accessed by e.g.
40 "{get(foo, key)}" function.
40 "{get(foo, key)}" function.
41
41
42 Doctest helper:
42 Doctest helper:
43
43
44 >>> def show(fn, verbose=False, **opts):
44 >>> def show(fn, verbose=False, **opts):
45 ... import sys
45 ... import sys
46 ... from . import ui as uimod
46 ... from . import ui as uimod
47 ... ui = uimod.ui()
47 ... ui = uimod.ui()
48 ... ui.verbose = verbose
48 ... ui.verbose = verbose
49 ... ui.pushbuffer()
49 ... ui.pushbuffer()
50 ... try:
50 ... try:
51 ... return fn(ui, ui.formatter(pycompat.sysbytes(fn.__name__),
51 ... return fn(ui, ui.formatter(pycompat.sysbytes(fn.__name__),
52 ... pycompat.byteskwargs(opts)))
52 ... pycompat.byteskwargs(opts)))
53 ... finally:
53 ... finally:
54 ... print(pycompat.sysstr(ui.popbuffer()), end='')
54 ... print(pycompat.sysstr(ui.popbuffer()), end='')
55
55
56 Basic example:
56 Basic example:
57
57
58 >>> def files(ui, fm):
58 >>> def files(ui, fm):
59 ... files = [(b'foo', 123, (0, 0)), (b'bar', 456, (1, 0))]
59 ... files = [(b'foo', 123, (0, 0)), (b'bar', 456, (1, 0))]
60 ... for f in files:
60 ... for f in files:
61 ... fm.startitem()
61 ... fm.startitem()
62 ... fm.write(b'path', b'%s', f[0])
62 ... fm.write(b'path', b'%s', f[0])
63 ... fm.condwrite(ui.verbose, b'date', b' %s',
63 ... fm.condwrite(ui.verbose, b'date', b' %s',
64 ... fm.formatdate(f[2], b'%Y-%m-%d %H:%M:%S'))
64 ... fm.formatdate(f[2], b'%Y-%m-%d %H:%M:%S'))
65 ... fm.data(size=f[1])
65 ... fm.data(size=f[1])
66 ... fm.plain(b'\\n')
66 ... fm.plain(b'\\n')
67 ... fm.end()
67 ... fm.end()
68 >>> show(files)
68 >>> show(files)
69 foo
69 foo
70 bar
70 bar
71 >>> show(files, verbose=True)
71 >>> show(files, verbose=True)
72 foo 1970-01-01 00:00:00
72 foo 1970-01-01 00:00:00
73 bar 1970-01-01 00:00:01
73 bar 1970-01-01 00:00:01
74 >>> show(files, template=b'json')
74 >>> show(files, template=b'json')
75 [
75 [
76 {
76 {
77 "date": [0, 0],
77 "date": [0, 0],
78 "path": "foo",
78 "path": "foo",
79 "size": 123
79 "size": 123
80 },
80 },
81 {
81 {
82 "date": [1, 0],
82 "date": [1, 0],
83 "path": "bar",
83 "path": "bar",
84 "size": 456
84 "size": 456
85 }
85 }
86 ]
86 ]
87 >>> show(files, template=b'path: {path}\\ndate: {date|rfc3339date}\\n')
87 >>> show(files, template=b'path: {path}\\ndate: {date|rfc3339date}\\n')
88 path: foo
88 path: foo
89 date: 1970-01-01T00:00:00+00:00
89 date: 1970-01-01T00:00:00+00:00
90 path: bar
90 path: bar
91 date: 1970-01-01T00:00:01+00:00
91 date: 1970-01-01T00:00:01+00:00
92
92
93 Nested example:
93 Nested example:
94
94
95 >>> def subrepos(ui, fm):
95 >>> def subrepos(ui, fm):
96 ... fm.startitem()
96 ... fm.startitem()
97 ... fm.write(b'reponame', b'[%s]\\n', b'baz')
97 ... fm.write(b'reponame', b'[%s]\\n', b'baz')
98 ... files(ui, fm.nested(b'files', tmpl=b'{reponame}'))
98 ... files(ui, fm.nested(b'files', tmpl=b'{reponame}'))
99 ... fm.end()
99 ... fm.end()
100 >>> show(subrepos)
100 >>> show(subrepos)
101 [baz]
101 [baz]
102 foo
102 foo
103 bar
103 bar
104 >>> show(subrepos, template=b'{reponame}: {join(files % "{path}", ", ")}\\n')
104 >>> show(subrepos, template=b'{reponame}: {join(files % "{path}", ", ")}\\n')
105 baz: foo, bar
105 baz: foo, bar
106 """
106 """
107
107
108 from __future__ import absolute_import, print_function
108 from __future__ import absolute_import, print_function
109
109
110 import contextlib
110 import contextlib
111 import itertools
111 import itertools
112 import os
112 import os
113
113
114 from .i18n import _
114 from .i18n import _
115 from .node import (
115 from .node import (
116 hex,
116 hex,
117 short,
117 short,
118 )
118 )
119 from .thirdparty import (
119 from .thirdparty import (
120 attr,
120 attr,
121 )
121 )
122
122
123 from . import (
123 from . import (
124 error,
124 error,
125 pycompat,
125 pycompat,
126 templatefilters,
126 templatefilters,
127 templatekw,
127 templatekw,
128 templater,
128 templater,
129 templateutil,
129 templateutil,
130 util,
130 util,
131 )
131 )
132 from .utils import (
132 from .utils import (
133 dateutil,
133 dateutil,
134 stringutil,
134 stringutil,
135 )
135 )
136
136
137 pickle = util.pickle
137 pickle = util.pickle
138
138
139 class _nullconverter(object):
139 class _nullconverter(object):
140 '''convert non-primitive data types to be processed by formatter'''
140 '''convert non-primitive data types to be processed by formatter'''
141
141
142 # set to True if context object should be stored as item
142 # set to True if context object should be stored as item
143 storecontext = False
143 storecontext = False
144
144
145 @staticmethod
145 @staticmethod
146 def wrapnested(data, tmpl, sep):
146 def wrapnested(data, tmpl, sep):
147 '''wrap nested data by appropriate type'''
147 '''wrap nested data by appropriate type'''
148 return data
148 return data
149 @staticmethod
149 @staticmethod
150 def formatdate(date, fmt):
150 def formatdate(date, fmt):
151 '''convert date tuple to appropriate format'''
151 '''convert date tuple to appropriate format'''
152 # timestamp can be float, but the canonical form should be int
152 # timestamp can be float, but the canonical form should be int
153 ts, tz = date
153 ts, tz = date
154 return (int(ts), tz)
154 return (int(ts), tz)
155 @staticmethod
155 @staticmethod
156 def formatdict(data, key, value, fmt, sep):
156 def formatdict(data, key, value, fmt, sep):
157 '''convert dict or key-value pairs to appropriate dict format'''
157 '''convert dict or key-value pairs to appropriate dict format'''
158 # use plain dict instead of util.sortdict so that data can be
158 # use plain dict instead of util.sortdict so that data can be
159 # serialized as a builtin dict in pickle output
159 # serialized as a builtin dict in pickle output
160 return dict(data)
160 return dict(data)
161 @staticmethod
161 @staticmethod
162 def formatlist(data, name, fmt, sep):
162 def formatlist(data, name, fmt, sep):
163 '''convert iterable to appropriate list format'''
163 '''convert iterable to appropriate list format'''
164 return list(data)
164 return list(data)
165
165
166 class baseformatter(object):
166 class baseformatter(object):
167 def __init__(self, ui, topic, opts, converter):
167 def __init__(self, ui, topic, opts, converter):
168 self._ui = ui
168 self._ui = ui
169 self._topic = topic
169 self._topic = topic
170 self._opts = opts
170 self._opts = opts
171 self._converter = converter
171 self._converter = converter
172 self._item = None
172 self._item = None
173 # function to convert node to string suitable for this output
173 # function to convert node to string suitable for this output
174 self.hexfunc = hex
174 self.hexfunc = hex
175 def __enter__(self):
175 def __enter__(self):
176 return self
176 return self
177 def __exit__(self, exctype, excvalue, traceback):
177 def __exit__(self, exctype, excvalue, traceback):
178 if exctype is None:
178 if exctype is None:
179 self.end()
179 self.end()
180 def _showitem(self):
180 def _showitem(self):
181 '''show a formatted item once all data is collected'''
181 '''show a formatted item once all data is collected'''
182 def startitem(self):
182 def startitem(self):
183 '''begin an item in the format list'''
183 '''begin an item in the format list'''
184 if self._item is not None:
184 if self._item is not None:
185 self._showitem()
185 self._showitem()
186 self._item = {}
186 self._item = {}
187 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
187 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
188 '''convert date tuple to appropriate format'''
188 '''convert date tuple to appropriate format'''
189 return self._converter.formatdate(date, fmt)
189 return self._converter.formatdate(date, fmt)
190 def formatdict(self, data, key='key', value='value', fmt=None, sep=' '):
190 def formatdict(self, data, key='key', value='value', fmt=None, sep=' '):
191 '''convert dict or key-value pairs to appropriate dict format'''
191 '''convert dict or key-value pairs to appropriate dict format'''
192 return self._converter.formatdict(data, key, value, fmt, sep)
192 return self._converter.formatdict(data, key, value, fmt, sep)
193 def formatlist(self, data, name, fmt=None, sep=' '):
193 def formatlist(self, data, name, fmt=None, sep=' '):
194 '''convert iterable to appropriate list format'''
194 '''convert iterable to appropriate list format'''
195 # name is mandatory argument for now, but it could be optional if
195 # name is mandatory argument for now, but it could be optional if
196 # we have default template keyword, e.g. {item}
196 # we have default template keyword, e.g. {item}
197 return self._converter.formatlist(data, name, fmt, sep)
197 return self._converter.formatlist(data, name, fmt, sep)
198 def context(self, **ctxs):
198 def context(self, **ctxs):
199 '''insert context objects to be used to render template keywords'''
199 '''insert context objects to be used to render template keywords'''
200 ctxs = pycompat.byteskwargs(ctxs)
200 ctxs = pycompat.byteskwargs(ctxs)
201 assert all(k in {'repo', 'ctx', 'fctx'} for k in ctxs)
201 assert all(k in {'repo', 'ctx', 'fctx'} for k in ctxs)
202 if self._converter.storecontext:
202 if self._converter.storecontext:
203 # populate missing resources in fctx -> ctx -> repo order
203 # populate missing resources in fctx -> ctx -> repo order
204 if 'fctx' in ctxs and 'ctx' not in ctxs:
204 if 'fctx' in ctxs and 'ctx' not in ctxs:
205 ctxs['ctx'] = ctxs['fctx'].changectx()
205 ctxs['ctx'] = ctxs['fctx'].changectx()
206 if 'ctx' in ctxs and 'repo' not in ctxs:
206 if 'ctx' in ctxs and 'repo' not in ctxs:
207 ctxs['repo'] = ctxs['ctx'].repo()
207 ctxs['repo'] = ctxs['ctx'].repo()
208 self._item.update(ctxs)
208 self._item.update(ctxs)
209 def datahint(self):
209 def datahint(self):
210 '''set of field names to be referenced'''
210 '''set of field names to be referenced'''
211 return set()
211 return set()
212 def data(self, **data):
212 def data(self, **data):
213 '''insert data into item that's not shown in default output'''
213 '''insert data into item that's not shown in default output'''
214 data = pycompat.byteskwargs(data)
214 data = pycompat.byteskwargs(data)
215 self._item.update(data)
215 self._item.update(data)
216 def write(self, fields, deftext, *fielddata, **opts):
216 def write(self, fields, deftext, *fielddata, **opts):
217 '''do default text output while assigning data to item'''
217 '''do default text output while assigning data to item'''
218 fieldkeys = fields.split()
218 fieldkeys = fields.split()
219 assert len(fieldkeys) == len(fielddata), (fieldkeys, fielddata)
219 assert len(fieldkeys) == len(fielddata), (fieldkeys, fielddata)
220 self._item.update(zip(fieldkeys, fielddata))
220 self._item.update(zip(fieldkeys, fielddata))
221 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
221 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
222 '''do conditional write (primarily for plain formatter)'''
222 '''do conditional write (primarily for plain formatter)'''
223 fieldkeys = fields.split()
223 fieldkeys = fields.split()
224 assert len(fieldkeys) == len(fielddata)
224 assert len(fieldkeys) == len(fielddata)
225 self._item.update(zip(fieldkeys, fielddata))
225 self._item.update(zip(fieldkeys, fielddata))
226 def plain(self, text, **opts):
226 def plain(self, text, **opts):
227 '''show raw text for non-templated mode'''
227 '''show raw text for non-templated mode'''
228 def isplain(self):
228 def isplain(self):
229 '''check for plain formatter usage'''
229 '''check for plain formatter usage'''
230 return False
230 return False
231 def nested(self, field, tmpl=None, sep=''):
231 def nested(self, field, tmpl=None, sep=''):
232 '''sub formatter to store nested data in the specified field'''
232 '''sub formatter to store nested data in the specified field'''
233 data = []
233 data = []
234 self._item[field] = self._converter.wrapnested(data, tmpl, sep)
234 self._item[field] = self._converter.wrapnested(data, tmpl, sep)
235 return _nestedformatter(self._ui, self._converter, data)
235 return _nestedformatter(self._ui, self._converter, data)
236 def end(self):
236 def end(self):
237 '''end output for the formatter'''
237 '''end output for the formatter'''
238 if self._item is not None:
238 if self._item is not None:
239 self._showitem()
239 self._showitem()
240
240
241 def nullformatter(ui, topic, opts):
241 def nullformatter(ui, topic, opts):
242 '''formatter that prints nothing'''
242 '''formatter that prints nothing'''
243 return baseformatter(ui, topic, opts, converter=_nullconverter)
243 return baseformatter(ui, topic, opts, converter=_nullconverter)
244
244
245 class _nestedformatter(baseformatter):
245 class _nestedformatter(baseformatter):
246 '''build sub items and store them in the parent formatter'''
246 '''build sub items and store them in the parent formatter'''
247 def __init__(self, ui, converter, data):
247 def __init__(self, ui, converter, data):
248 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
248 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
249 self._data = data
249 self._data = data
250 def _showitem(self):
250 def _showitem(self):
251 self._data.append(self._item)
251 self._data.append(self._item)
252
252
253 def _iteritems(data):
253 def _iteritems(data):
254 '''iterate key-value pairs in stable order'''
254 '''iterate key-value pairs in stable order'''
255 if isinstance(data, dict):
255 if isinstance(data, dict):
256 return sorted(data.iteritems())
256 return sorted(data.iteritems())
257 return data
257 return data
258
258
259 class _plainconverter(object):
259 class _plainconverter(object):
260 '''convert non-primitive data types to text'''
260 '''convert non-primitive data types to text'''
261
261
262 storecontext = False
262 storecontext = False
263
263
264 @staticmethod
264 @staticmethod
265 def wrapnested(data, tmpl, sep):
265 def wrapnested(data, tmpl, sep):
266 raise error.ProgrammingError('plainformatter should never be nested')
266 raise error.ProgrammingError('plainformatter should never be nested')
267 @staticmethod
267 @staticmethod
268 def formatdate(date, fmt):
268 def formatdate(date, fmt):
269 '''stringify date tuple in the given format'''
269 '''stringify date tuple in the given format'''
270 return dateutil.datestr(date, fmt)
270 return dateutil.datestr(date, fmt)
271 @staticmethod
271 @staticmethod
272 def formatdict(data, key, value, fmt, sep):
272 def formatdict(data, key, value, fmt, sep):
273 '''stringify key-value pairs separated by sep'''
273 '''stringify key-value pairs separated by sep'''
274 prefmt = pycompat.identity
274 prefmt = pycompat.identity
275 if fmt is None:
275 if fmt is None:
276 fmt = '%s=%s'
276 fmt = '%s=%s'
277 prefmt = pycompat.bytestr
277 prefmt = pycompat.bytestr
278 return sep.join(fmt % (prefmt(k), prefmt(v))
278 return sep.join(fmt % (prefmt(k), prefmt(v))
279 for k, v in _iteritems(data))
279 for k, v in _iteritems(data))
280 @staticmethod
280 @staticmethod
281 def formatlist(data, name, fmt, sep):
281 def formatlist(data, name, fmt, sep):
282 '''stringify iterable separated by sep'''
282 '''stringify iterable separated by sep'''
283 prefmt = pycompat.identity
283 prefmt = pycompat.identity
284 if fmt is None:
284 if fmt is None:
285 fmt = '%s'
285 fmt = '%s'
286 prefmt = pycompat.bytestr
286 prefmt = pycompat.bytestr
287 return sep.join(fmt % prefmt(e) for e in data)
287 return sep.join(fmt % prefmt(e) for e in data)
288
288
289 class plainformatter(baseformatter):
289 class plainformatter(baseformatter):
290 '''the default text output scheme'''
290 '''the default text output scheme'''
291 def __init__(self, ui, out, topic, opts):
291 def __init__(self, ui, out, topic, opts):
292 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
292 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
293 if ui.debugflag:
293 if ui.debugflag:
294 self.hexfunc = hex
294 self.hexfunc = hex
295 else:
295 else:
296 self.hexfunc = short
296 self.hexfunc = short
297 if ui is out:
297 if ui is out:
298 self._write = ui.write
298 self._write = ui.write
299 else:
299 else:
300 self._write = lambda s, **opts: out.write(s)
300 self._write = lambda s, **opts: out.write(s)
301 def startitem(self):
301 def startitem(self):
302 pass
302 pass
303 def data(self, **data):
303 def data(self, **data):
304 pass
304 pass
305 def write(self, fields, deftext, *fielddata, **opts):
305 def write(self, fields, deftext, *fielddata, **opts):
306 self._write(deftext % fielddata, **opts)
306 self._write(deftext % fielddata, **opts)
307 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
307 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
308 '''do conditional write'''
308 '''do conditional write'''
309 if cond:
309 if cond:
310 self._write(deftext % fielddata, **opts)
310 self._write(deftext % fielddata, **opts)
311 def plain(self, text, **opts):
311 def plain(self, text, **opts):
312 self._write(text, **opts)
312 self._write(text, **opts)
313 def isplain(self):
313 def isplain(self):
314 return True
314 return True
315 def nested(self, field, tmpl=None, sep=''):
315 def nested(self, field, tmpl=None, sep=''):
316 # nested data will be directly written to ui
316 # nested data will be directly written to ui
317 return self
317 return self
318 def end(self):
318 def end(self):
319 pass
319 pass
320
320
321 class debugformatter(baseformatter):
321 class debugformatter(baseformatter):
322 def __init__(self, ui, out, topic, opts):
322 def __init__(self, ui, out, topic, opts):
323 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
323 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
324 self._out = out
324 self._out = out
325 self._out.write("%s = [\n" % self._topic)
325 self._out.write("%s = [\n" % self._topic)
326 def _showitem(self):
326 def _showitem(self):
327 self._out.write(' %s,\n' % stringutil.pprint(self._item))
327 self._out.write(' %s,\n'
328 % stringutil.pprint(self._item, indent=4, level=1))
328 def end(self):
329 def end(self):
329 baseformatter.end(self)
330 baseformatter.end(self)
330 self._out.write("]\n")
331 self._out.write("]\n")
331
332
332 class pickleformatter(baseformatter):
333 class pickleformatter(baseformatter):
333 def __init__(self, ui, out, topic, opts):
334 def __init__(self, ui, out, topic, opts):
334 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
335 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
335 self._out = out
336 self._out = out
336 self._data = []
337 self._data = []
337 def _showitem(self):
338 def _showitem(self):
338 self._data.append(self._item)
339 self._data.append(self._item)
339 def end(self):
340 def end(self):
340 baseformatter.end(self)
341 baseformatter.end(self)
341 self._out.write(pickle.dumps(self._data))
342 self._out.write(pickle.dumps(self._data))
342
343
343 class jsonformatter(baseformatter):
344 class jsonformatter(baseformatter):
344 def __init__(self, ui, out, topic, opts):
345 def __init__(self, ui, out, topic, opts):
345 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
346 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
346 self._out = out
347 self._out = out
347 self._out.write("[")
348 self._out.write("[")
348 self._first = True
349 self._first = True
349 def _showitem(self):
350 def _showitem(self):
350 if self._first:
351 if self._first:
351 self._first = False
352 self._first = False
352 else:
353 else:
353 self._out.write(",")
354 self._out.write(",")
354
355
355 self._out.write("\n {\n")
356 self._out.write("\n {\n")
356 first = True
357 first = True
357 for k, v in sorted(self._item.items()):
358 for k, v in sorted(self._item.items()):
358 if first:
359 if first:
359 first = False
360 first = False
360 else:
361 else:
361 self._out.write(",\n")
362 self._out.write(",\n")
362 u = templatefilters.json(v, paranoid=False)
363 u = templatefilters.json(v, paranoid=False)
363 self._out.write(' "%s": %s' % (k, u))
364 self._out.write(' "%s": %s' % (k, u))
364 self._out.write("\n }")
365 self._out.write("\n }")
365 def end(self):
366 def end(self):
366 baseformatter.end(self)
367 baseformatter.end(self)
367 self._out.write("\n]\n")
368 self._out.write("\n]\n")
368
369
369 class _templateconverter(object):
370 class _templateconverter(object):
370 '''convert non-primitive data types to be processed by templater'''
371 '''convert non-primitive data types to be processed by templater'''
371
372
372 storecontext = True
373 storecontext = True
373
374
374 @staticmethod
375 @staticmethod
375 def wrapnested(data, tmpl, sep):
376 def wrapnested(data, tmpl, sep):
376 '''wrap nested data by templatable type'''
377 '''wrap nested data by templatable type'''
377 return templateutil.mappinglist(data, tmpl=tmpl, sep=sep)
378 return templateutil.mappinglist(data, tmpl=tmpl, sep=sep)
378 @staticmethod
379 @staticmethod
379 def formatdate(date, fmt):
380 def formatdate(date, fmt):
380 '''return date tuple'''
381 '''return date tuple'''
381 return templateutil.date(date)
382 return templateutil.date(date)
382 @staticmethod
383 @staticmethod
383 def formatdict(data, key, value, fmt, sep):
384 def formatdict(data, key, value, fmt, sep):
384 '''build object that can be evaluated as either plain string or dict'''
385 '''build object that can be evaluated as either plain string or dict'''
385 data = util.sortdict(_iteritems(data))
386 data = util.sortdict(_iteritems(data))
386 def f():
387 def f():
387 yield _plainconverter.formatdict(data, key, value, fmt, sep)
388 yield _plainconverter.formatdict(data, key, value, fmt, sep)
388 return templateutil.hybriddict(data, key=key, value=value, fmt=fmt,
389 return templateutil.hybriddict(data, key=key, value=value, fmt=fmt,
389 gen=f)
390 gen=f)
390 @staticmethod
391 @staticmethod
391 def formatlist(data, name, fmt, sep):
392 def formatlist(data, name, fmt, sep):
392 '''build object that can be evaluated as either plain string or list'''
393 '''build object that can be evaluated as either plain string or list'''
393 data = list(data)
394 data = list(data)
394 def f():
395 def f():
395 yield _plainconverter.formatlist(data, name, fmt, sep)
396 yield _plainconverter.formatlist(data, name, fmt, sep)
396 return templateutil.hybridlist(data, name=name, fmt=fmt, gen=f)
397 return templateutil.hybridlist(data, name=name, fmt=fmt, gen=f)
397
398
398 class templateformatter(baseformatter):
399 class templateformatter(baseformatter):
399 def __init__(self, ui, out, topic, opts):
400 def __init__(self, ui, out, topic, opts):
400 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
401 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
401 self._out = out
402 self._out = out
402 spec = lookuptemplate(ui, topic, opts.get('template', ''))
403 spec = lookuptemplate(ui, topic, opts.get('template', ''))
403 self._tref = spec.ref
404 self._tref = spec.ref
404 self._t = loadtemplater(ui, spec, defaults=templatekw.keywords,
405 self._t = loadtemplater(ui, spec, defaults=templatekw.keywords,
405 resources=templateresources(ui),
406 resources=templateresources(ui),
406 cache=templatekw.defaulttempl)
407 cache=templatekw.defaulttempl)
407 self._parts = templatepartsmap(spec, self._t,
408 self._parts = templatepartsmap(spec, self._t,
408 ['docheader', 'docfooter', 'separator'])
409 ['docheader', 'docfooter', 'separator'])
409 self._counter = itertools.count()
410 self._counter = itertools.count()
410 self._renderitem('docheader', {})
411 self._renderitem('docheader', {})
411
412
412 def _showitem(self):
413 def _showitem(self):
413 item = self._item.copy()
414 item = self._item.copy()
414 item['index'] = index = next(self._counter)
415 item['index'] = index = next(self._counter)
415 if index > 0:
416 if index > 0:
416 self._renderitem('separator', {})
417 self._renderitem('separator', {})
417 self._renderitem(self._tref, item)
418 self._renderitem(self._tref, item)
418
419
419 def _renderitem(self, part, item):
420 def _renderitem(self, part, item):
420 if part not in self._parts:
421 if part not in self._parts:
421 return
422 return
422 ref = self._parts[part]
423 ref = self._parts[part]
423 self._out.write(self._t.render(ref, item))
424 self._out.write(self._t.render(ref, item))
424
425
425 @util.propertycache
426 @util.propertycache
426 def _symbolsused(self):
427 def _symbolsused(self):
427 return self._t.symbolsused(self._tref)
428 return self._t.symbolsused(self._tref)
428
429
429 def datahint(self):
430 def datahint(self):
430 '''set of field names to be referenced from the template'''
431 '''set of field names to be referenced from the template'''
431 return self._symbolsused[0]
432 return self._symbolsused[0]
432
433
433 def end(self):
434 def end(self):
434 baseformatter.end(self)
435 baseformatter.end(self)
435 self._renderitem('docfooter', {})
436 self._renderitem('docfooter', {})
436
437
437 @attr.s(frozen=True)
438 @attr.s(frozen=True)
438 class templatespec(object):
439 class templatespec(object):
439 ref = attr.ib()
440 ref = attr.ib()
440 tmpl = attr.ib()
441 tmpl = attr.ib()
441 mapfile = attr.ib()
442 mapfile = attr.ib()
442
443
443 def lookuptemplate(ui, topic, tmpl):
444 def lookuptemplate(ui, topic, tmpl):
444 """Find the template matching the given -T/--template spec 'tmpl'
445 """Find the template matching the given -T/--template spec 'tmpl'
445
446
446 'tmpl' can be any of the following:
447 'tmpl' can be any of the following:
447
448
448 - a literal template (e.g. '{rev}')
449 - a literal template (e.g. '{rev}')
449 - a map-file name or path (e.g. 'changelog')
450 - a map-file name or path (e.g. 'changelog')
450 - a reference to [templates] in config file
451 - a reference to [templates] in config file
451 - a path to raw template file
452 - a path to raw template file
452
453
453 A map file defines a stand-alone template environment. If a map file
454 A map file defines a stand-alone template environment. If a map file
454 selected, all templates defined in the file will be loaded, and the
455 selected, all templates defined in the file will be loaded, and the
455 template matching the given topic will be rendered. Aliases won't be
456 template matching the given topic will be rendered. Aliases won't be
456 loaded from user config, but from the map file.
457 loaded from user config, but from the map file.
457
458
458 If no map file selected, all templates in [templates] section will be
459 If no map file selected, all templates in [templates] section will be
459 available as well as aliases in [templatealias].
460 available as well as aliases in [templatealias].
460 """
461 """
461
462
462 # looks like a literal template?
463 # looks like a literal template?
463 if '{' in tmpl:
464 if '{' in tmpl:
464 return templatespec('', tmpl, None)
465 return templatespec('', tmpl, None)
465
466
466 # perhaps a stock style?
467 # perhaps a stock style?
467 if not os.path.split(tmpl)[0]:
468 if not os.path.split(tmpl)[0]:
468 mapname = (templater.templatepath('map-cmdline.' + tmpl)
469 mapname = (templater.templatepath('map-cmdline.' + tmpl)
469 or templater.templatepath(tmpl))
470 or templater.templatepath(tmpl))
470 if mapname and os.path.isfile(mapname):
471 if mapname and os.path.isfile(mapname):
471 return templatespec(topic, None, mapname)
472 return templatespec(topic, None, mapname)
472
473
473 # perhaps it's a reference to [templates]
474 # perhaps it's a reference to [templates]
474 if ui.config('templates', tmpl):
475 if ui.config('templates', tmpl):
475 return templatespec(tmpl, None, None)
476 return templatespec(tmpl, None, None)
476
477
477 if tmpl == 'list':
478 if tmpl == 'list':
478 ui.write(_("available styles: %s\n") % templater.stylelist())
479 ui.write(_("available styles: %s\n") % templater.stylelist())
479 raise error.Abort(_("specify a template"))
480 raise error.Abort(_("specify a template"))
480
481
481 # perhaps it's a path to a map or a template
482 # perhaps it's a path to a map or a template
482 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
483 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
483 # is it a mapfile for a style?
484 # is it a mapfile for a style?
484 if os.path.basename(tmpl).startswith("map-"):
485 if os.path.basename(tmpl).startswith("map-"):
485 return templatespec(topic, None, os.path.realpath(tmpl))
486 return templatespec(topic, None, os.path.realpath(tmpl))
486 with util.posixfile(tmpl, 'rb') as f:
487 with util.posixfile(tmpl, 'rb') as f:
487 tmpl = f.read()
488 tmpl = f.read()
488 return templatespec('', tmpl, None)
489 return templatespec('', tmpl, None)
489
490
490 # constant string?
491 # constant string?
491 return templatespec('', tmpl, None)
492 return templatespec('', tmpl, None)
492
493
493 def templatepartsmap(spec, t, partnames):
494 def templatepartsmap(spec, t, partnames):
494 """Create a mapping of {part: ref}"""
495 """Create a mapping of {part: ref}"""
495 partsmap = {spec.ref: spec.ref} # initial ref must exist in t
496 partsmap = {spec.ref: spec.ref} # initial ref must exist in t
496 if spec.mapfile:
497 if spec.mapfile:
497 partsmap.update((p, p) for p in partnames if p in t)
498 partsmap.update((p, p) for p in partnames if p in t)
498 elif spec.ref:
499 elif spec.ref:
499 for part in partnames:
500 for part in partnames:
500 ref = '%s:%s' % (spec.ref, part) # select config sub-section
501 ref = '%s:%s' % (spec.ref, part) # select config sub-section
501 if ref in t:
502 if ref in t:
502 partsmap[part] = ref
503 partsmap[part] = ref
503 return partsmap
504 return partsmap
504
505
505 def loadtemplater(ui, spec, defaults=None, resources=None, cache=None):
506 def loadtemplater(ui, spec, defaults=None, resources=None, cache=None):
506 """Create a templater from either a literal template or loading from
507 """Create a templater from either a literal template or loading from
507 a map file"""
508 a map file"""
508 assert not (spec.tmpl and spec.mapfile)
509 assert not (spec.tmpl and spec.mapfile)
509 if spec.mapfile:
510 if spec.mapfile:
510 frommapfile = templater.templater.frommapfile
511 frommapfile = templater.templater.frommapfile
511 return frommapfile(spec.mapfile, defaults=defaults, resources=resources,
512 return frommapfile(spec.mapfile, defaults=defaults, resources=resources,
512 cache=cache)
513 cache=cache)
513 return maketemplater(ui, spec.tmpl, defaults=defaults, resources=resources,
514 return maketemplater(ui, spec.tmpl, defaults=defaults, resources=resources,
514 cache=cache)
515 cache=cache)
515
516
516 def maketemplater(ui, tmpl, defaults=None, resources=None, cache=None):
517 def maketemplater(ui, tmpl, defaults=None, resources=None, cache=None):
517 """Create a templater from a string template 'tmpl'"""
518 """Create a templater from a string template 'tmpl'"""
518 aliases = ui.configitems('templatealias')
519 aliases = ui.configitems('templatealias')
519 t = templater.templater(defaults=defaults, resources=resources,
520 t = templater.templater(defaults=defaults, resources=resources,
520 cache=cache, aliases=aliases)
521 cache=cache, aliases=aliases)
521 t.cache.update((k, templater.unquotestring(v))
522 t.cache.update((k, templater.unquotestring(v))
522 for k, v in ui.configitems('templates'))
523 for k, v in ui.configitems('templates'))
523 if tmpl:
524 if tmpl:
524 t.cache[''] = tmpl
525 t.cache[''] = tmpl
525 return t
526 return t
526
527
527 # marker to denote a resource to be loaded on demand based on mapping values
528 # marker to denote a resource to be loaded on demand based on mapping values
528 # (e.g. (ctx, path) -> fctx)
529 # (e.g. (ctx, path) -> fctx)
529 _placeholder = object()
530 _placeholder = object()
530
531
531 class templateresources(templater.resourcemapper):
532 class templateresources(templater.resourcemapper):
532 """Resource mapper designed for the default templatekw and function"""
533 """Resource mapper designed for the default templatekw and function"""
533
534
534 def __init__(self, ui, repo=None):
535 def __init__(self, ui, repo=None):
535 self._resmap = {
536 self._resmap = {
536 'cache': {}, # for templatekw/funcs to store reusable data
537 'cache': {}, # for templatekw/funcs to store reusable data
537 'repo': repo,
538 'repo': repo,
538 'ui': ui,
539 'ui': ui,
539 }
540 }
540
541
541 def availablekeys(self, mapping):
542 def availablekeys(self, mapping):
542 return {k for k in self.knownkeys()
543 return {k for k in self.knownkeys()
543 if self._getsome(mapping, k) is not None}
544 if self._getsome(mapping, k) is not None}
544
545
545 def knownkeys(self):
546 def knownkeys(self):
546 return {'cache', 'ctx', 'fctx', 'repo', 'revcache', 'ui'}
547 return {'cache', 'ctx', 'fctx', 'repo', 'revcache', 'ui'}
547
548
548 def lookup(self, mapping, key):
549 def lookup(self, mapping, key):
549 if key not in self.knownkeys():
550 if key not in self.knownkeys():
550 return None
551 return None
551 v = self._getsome(mapping, key)
552 v = self._getsome(mapping, key)
552 if v is _placeholder:
553 if v is _placeholder:
553 v = mapping[key] = self._loadermap[key](self, mapping)
554 v = mapping[key] = self._loadermap[key](self, mapping)
554 return v
555 return v
555
556
556 def populatemap(self, context, origmapping, newmapping):
557 def populatemap(self, context, origmapping, newmapping):
557 mapping = {}
558 mapping = {}
558 if self._hasnodespec(newmapping):
559 if self._hasnodespec(newmapping):
559 mapping['revcache'] = {} # per-ctx cache
560 mapping['revcache'] = {} # per-ctx cache
560 if self._hasnodespec(origmapping) and self._hasnodespec(newmapping):
561 if self._hasnodespec(origmapping) and self._hasnodespec(newmapping):
561 orignode = templateutil.runsymbol(context, origmapping, 'node')
562 orignode = templateutil.runsymbol(context, origmapping, 'node')
562 mapping['originalnode'] = orignode
563 mapping['originalnode'] = orignode
563 # put marker to override 'ctx'/'fctx' in mapping if any, and flag
564 # put marker to override 'ctx'/'fctx' in mapping if any, and flag
564 # its existence to be reported by availablekeys()
565 # its existence to be reported by availablekeys()
565 if 'ctx' not in newmapping and self._hasliteral(newmapping, 'node'):
566 if 'ctx' not in newmapping and self._hasliteral(newmapping, 'node'):
566 mapping['ctx'] = _placeholder
567 mapping['ctx'] = _placeholder
567 if 'fctx' not in newmapping and self._hasliteral(newmapping, 'path'):
568 if 'fctx' not in newmapping and self._hasliteral(newmapping, 'path'):
568 mapping['fctx'] = _placeholder
569 mapping['fctx'] = _placeholder
569 return mapping
570 return mapping
570
571
571 def _getsome(self, mapping, key):
572 def _getsome(self, mapping, key):
572 v = mapping.get(key)
573 v = mapping.get(key)
573 if v is not None:
574 if v is not None:
574 return v
575 return v
575 return self._resmap.get(key)
576 return self._resmap.get(key)
576
577
577 def _hasliteral(self, mapping, key):
578 def _hasliteral(self, mapping, key):
578 """Test if a literal value is set or unset in the given mapping"""
579 """Test if a literal value is set or unset in the given mapping"""
579 return key in mapping and not callable(mapping[key])
580 return key in mapping and not callable(mapping[key])
580
581
581 def _getliteral(self, mapping, key):
582 def _getliteral(self, mapping, key):
582 """Return value of the given name if it is a literal"""
583 """Return value of the given name if it is a literal"""
583 v = mapping.get(key)
584 v = mapping.get(key)
584 if callable(v):
585 if callable(v):
585 return None
586 return None
586 return v
587 return v
587
588
588 def _hasnodespec(self, mapping):
589 def _hasnodespec(self, mapping):
589 """Test if context revision is set or unset in the given mapping"""
590 """Test if context revision is set or unset in the given mapping"""
590 return 'node' in mapping or 'ctx' in mapping
591 return 'node' in mapping or 'ctx' in mapping
591
592
592 def _loadctx(self, mapping):
593 def _loadctx(self, mapping):
593 repo = self._getsome(mapping, 'repo')
594 repo = self._getsome(mapping, 'repo')
594 node = self._getliteral(mapping, 'node')
595 node = self._getliteral(mapping, 'node')
595 if repo is None or node is None:
596 if repo is None or node is None:
596 return
597 return
597 try:
598 try:
598 return repo[node]
599 return repo[node]
599 except error.RepoLookupError:
600 except error.RepoLookupError:
600 return None # maybe hidden/non-existent node
601 return None # maybe hidden/non-existent node
601
602
602 def _loadfctx(self, mapping):
603 def _loadfctx(self, mapping):
603 ctx = self._getsome(mapping, 'ctx')
604 ctx = self._getsome(mapping, 'ctx')
604 path = self._getliteral(mapping, 'path')
605 path = self._getliteral(mapping, 'path')
605 if ctx is None or path is None:
606 if ctx is None or path is None:
606 return None
607 return None
607 try:
608 try:
608 return ctx[path]
609 return ctx[path]
609 except error.LookupError:
610 except error.LookupError:
610 return None # maybe removed file?
611 return None # maybe removed file?
611
612
612 _loadermap = {
613 _loadermap = {
613 'ctx': _loadctx,
614 'ctx': _loadctx,
614 'fctx': _loadfctx,
615 'fctx': _loadfctx,
615 }
616 }
616
617
617 def formatter(ui, out, topic, opts):
618 def formatter(ui, out, topic, opts):
618 template = opts.get("template", "")
619 template = opts.get("template", "")
619 if template == "json":
620 if template == "json":
620 return jsonformatter(ui, out, topic, opts)
621 return jsonformatter(ui, out, topic, opts)
621 elif template == "pickle":
622 elif template == "pickle":
622 return pickleformatter(ui, out, topic, opts)
623 return pickleformatter(ui, out, topic, opts)
623 elif template == "debug":
624 elif template == "debug":
624 return debugformatter(ui, out, topic, opts)
625 return debugformatter(ui, out, topic, opts)
625 elif template != "":
626 elif template != "":
626 return templateformatter(ui, out, topic, opts)
627 return templateformatter(ui, out, topic, opts)
627 # developer config: ui.formatdebug
628 # developer config: ui.formatdebug
628 elif ui.configbool('ui', 'formatdebug'):
629 elif ui.configbool('ui', 'formatdebug'):
629 return debugformatter(ui, out, topic, opts)
630 return debugformatter(ui, out, topic, opts)
630 # deprecated config: ui.formatjson
631 # deprecated config: ui.formatjson
631 elif ui.configbool('ui', 'formatjson'):
632 elif ui.configbool('ui', 'formatjson'):
632 return jsonformatter(ui, out, topic, opts)
633 return jsonformatter(ui, out, topic, opts)
633 return plainformatter(ui, out, topic, opts)
634 return plainformatter(ui, out, topic, opts)
634
635
635 @contextlib.contextmanager
636 @contextlib.contextmanager
636 def openformatter(ui, filename, topic, opts):
637 def openformatter(ui, filename, topic, opts):
637 """Create a formatter that writes outputs to the specified file
638 """Create a formatter that writes outputs to the specified file
638
639
639 Must be invoked using the 'with' statement.
640 Must be invoked using the 'with' statement.
640 """
641 """
641 with util.posixfile(filename, 'wb') as out:
642 with util.posixfile(filename, 'wb') as out:
642 with formatter(ui, out, topic, opts) as fm:
643 with formatter(ui, out, topic, opts) as fm:
643 yield fm
644 yield fm
644
645
645 @contextlib.contextmanager
646 @contextlib.contextmanager
646 def _neverending(fm):
647 def _neverending(fm):
647 yield fm
648 yield fm
648
649
649 def maybereopen(fm, filename):
650 def maybereopen(fm, filename):
650 """Create a formatter backed by file if filename specified, else return
651 """Create a formatter backed by file if filename specified, else return
651 the given formatter
652 the given formatter
652
653
653 Must be invoked using the 'with' statement. This will never call fm.end()
654 Must be invoked using the 'with' statement. This will never call fm.end()
654 of the given formatter.
655 of the given formatter.
655 """
656 """
656 if filename:
657 if filename:
657 return openformatter(fm._ui, filename, fm._topic, fm._opts)
658 return openformatter(fm._ui, filename, fm._topic, fm._opts)
658 else:
659 else:
659 return _neverending(fm)
660 return _neverending(fm)
@@ -1,623 +1,626
1 $ hg init repo1
1 $ hg init repo1
2 $ cd repo1
2 $ cd repo1
3 $ mkdir a b a/1 b/1 b/2
3 $ mkdir a b a/1 b/1 b/2
4 $ touch in_root a/in_a b/in_b a/1/in_a_1 b/1/in_b_1 b/2/in_b_2
4 $ touch in_root a/in_a b/in_b a/1/in_a_1 b/1/in_b_1 b/2/in_b_2
5
5
6 hg status in repo root:
6 hg status in repo root:
7
7
8 $ hg status
8 $ hg status
9 ? a/1/in_a_1
9 ? a/1/in_a_1
10 ? a/in_a
10 ? a/in_a
11 ? b/1/in_b_1
11 ? b/1/in_b_1
12 ? b/2/in_b_2
12 ? b/2/in_b_2
13 ? b/in_b
13 ? b/in_b
14 ? in_root
14 ? in_root
15
15
16 hg status . in repo root:
16 hg status . in repo root:
17
17
18 $ hg status .
18 $ hg status .
19 ? a/1/in_a_1
19 ? a/1/in_a_1
20 ? a/in_a
20 ? a/in_a
21 ? b/1/in_b_1
21 ? b/1/in_b_1
22 ? b/2/in_b_2
22 ? b/2/in_b_2
23 ? b/in_b
23 ? b/in_b
24 ? in_root
24 ? in_root
25
25
26 $ hg status --cwd a
26 $ hg status --cwd a
27 ? a/1/in_a_1
27 ? a/1/in_a_1
28 ? a/in_a
28 ? a/in_a
29 ? b/1/in_b_1
29 ? b/1/in_b_1
30 ? b/2/in_b_2
30 ? b/2/in_b_2
31 ? b/in_b
31 ? b/in_b
32 ? in_root
32 ? in_root
33 $ hg status --cwd a .
33 $ hg status --cwd a .
34 ? 1/in_a_1
34 ? 1/in_a_1
35 ? in_a
35 ? in_a
36 $ hg status --cwd a ..
36 $ hg status --cwd a ..
37 ? 1/in_a_1
37 ? 1/in_a_1
38 ? in_a
38 ? in_a
39 ? ../b/1/in_b_1
39 ? ../b/1/in_b_1
40 ? ../b/2/in_b_2
40 ? ../b/2/in_b_2
41 ? ../b/in_b
41 ? ../b/in_b
42 ? ../in_root
42 ? ../in_root
43
43
44 $ hg status --cwd b
44 $ hg status --cwd b
45 ? a/1/in_a_1
45 ? a/1/in_a_1
46 ? a/in_a
46 ? a/in_a
47 ? b/1/in_b_1
47 ? b/1/in_b_1
48 ? b/2/in_b_2
48 ? b/2/in_b_2
49 ? b/in_b
49 ? b/in_b
50 ? in_root
50 ? in_root
51 $ hg status --cwd b .
51 $ hg status --cwd b .
52 ? 1/in_b_1
52 ? 1/in_b_1
53 ? 2/in_b_2
53 ? 2/in_b_2
54 ? in_b
54 ? in_b
55 $ hg status --cwd b ..
55 $ hg status --cwd b ..
56 ? ../a/1/in_a_1
56 ? ../a/1/in_a_1
57 ? ../a/in_a
57 ? ../a/in_a
58 ? 1/in_b_1
58 ? 1/in_b_1
59 ? 2/in_b_2
59 ? 2/in_b_2
60 ? in_b
60 ? in_b
61 ? ../in_root
61 ? ../in_root
62
62
63 $ hg status --cwd a/1
63 $ hg status --cwd a/1
64 ? a/1/in_a_1
64 ? a/1/in_a_1
65 ? a/in_a
65 ? a/in_a
66 ? b/1/in_b_1
66 ? b/1/in_b_1
67 ? b/2/in_b_2
67 ? b/2/in_b_2
68 ? b/in_b
68 ? b/in_b
69 ? in_root
69 ? in_root
70 $ hg status --cwd a/1 .
70 $ hg status --cwd a/1 .
71 ? in_a_1
71 ? in_a_1
72 $ hg status --cwd a/1 ..
72 $ hg status --cwd a/1 ..
73 ? in_a_1
73 ? in_a_1
74 ? ../in_a
74 ? ../in_a
75
75
76 $ hg status --cwd b/1
76 $ hg status --cwd b/1
77 ? a/1/in_a_1
77 ? a/1/in_a_1
78 ? a/in_a
78 ? a/in_a
79 ? b/1/in_b_1
79 ? b/1/in_b_1
80 ? b/2/in_b_2
80 ? b/2/in_b_2
81 ? b/in_b
81 ? b/in_b
82 ? in_root
82 ? in_root
83 $ hg status --cwd b/1 .
83 $ hg status --cwd b/1 .
84 ? in_b_1
84 ? in_b_1
85 $ hg status --cwd b/1 ..
85 $ hg status --cwd b/1 ..
86 ? in_b_1
86 ? in_b_1
87 ? ../2/in_b_2
87 ? ../2/in_b_2
88 ? ../in_b
88 ? ../in_b
89
89
90 $ hg status --cwd b/2
90 $ hg status --cwd b/2
91 ? a/1/in_a_1
91 ? a/1/in_a_1
92 ? a/in_a
92 ? a/in_a
93 ? b/1/in_b_1
93 ? b/1/in_b_1
94 ? b/2/in_b_2
94 ? b/2/in_b_2
95 ? b/in_b
95 ? b/in_b
96 ? in_root
96 ? in_root
97 $ hg status --cwd b/2 .
97 $ hg status --cwd b/2 .
98 ? in_b_2
98 ? in_b_2
99 $ hg status --cwd b/2 ..
99 $ hg status --cwd b/2 ..
100 ? ../1/in_b_1
100 ? ../1/in_b_1
101 ? in_b_2
101 ? in_b_2
102 ? ../in_b
102 ? ../in_b
103
103
104 combining patterns with root and patterns without a root works
104 combining patterns with root and patterns without a root works
105
105
106 $ hg st a/in_a re:.*b$
106 $ hg st a/in_a re:.*b$
107 ? a/in_a
107 ? a/in_a
108 ? b/in_b
108 ? b/in_b
109
109
110 tweaking defaults works
110 tweaking defaults works
111 $ hg status --cwd a --config ui.tweakdefaults=yes
111 $ hg status --cwd a --config ui.tweakdefaults=yes
112 ? 1/in_a_1
112 ? 1/in_a_1
113 ? in_a
113 ? in_a
114 ? ../b/1/in_b_1
114 ? ../b/1/in_b_1
115 ? ../b/2/in_b_2
115 ? ../b/2/in_b_2
116 ? ../b/in_b
116 ? ../b/in_b
117 ? ../in_root
117 ? ../in_root
118 $ HGPLAIN=1 hg status --cwd a --config ui.tweakdefaults=yes
118 $ HGPLAIN=1 hg status --cwd a --config ui.tweakdefaults=yes
119 ? a/1/in_a_1 (glob)
119 ? a/1/in_a_1 (glob)
120 ? a/in_a (glob)
120 ? a/in_a (glob)
121 ? b/1/in_b_1 (glob)
121 ? b/1/in_b_1 (glob)
122 ? b/2/in_b_2 (glob)
122 ? b/2/in_b_2 (glob)
123 ? b/in_b (glob)
123 ? b/in_b (glob)
124 ? in_root
124 ? in_root
125 $ HGPLAINEXCEPT=tweakdefaults hg status --cwd a --config ui.tweakdefaults=yes
125 $ HGPLAINEXCEPT=tweakdefaults hg status --cwd a --config ui.tweakdefaults=yes
126 ? 1/in_a_1
126 ? 1/in_a_1
127 ? in_a
127 ? in_a
128 ? ../b/1/in_b_1
128 ? ../b/1/in_b_1
129 ? ../b/2/in_b_2
129 ? ../b/2/in_b_2
130 ? ../b/in_b
130 ? ../b/in_b
131 ? ../in_root (glob)
131 ? ../in_root (glob)
132
132
133 relative paths can be requested
133 relative paths can be requested
134
134
135 $ cat >> $HGRCPATH <<EOF
135 $ cat >> $HGRCPATH <<EOF
136 > [commands]
136 > [commands]
137 > status.relative = True
137 > status.relative = True
138 > EOF
138 > EOF
139 $ hg status --cwd a
139 $ hg status --cwd a
140 ? 1/in_a_1
140 ? 1/in_a_1
141 ? in_a
141 ? in_a
142 ? ../b/1/in_b_1
142 ? ../b/1/in_b_1
143 ? ../b/2/in_b_2
143 ? ../b/2/in_b_2
144 ? ../b/in_b
144 ? ../b/in_b
145 ? ../in_root
145 ? ../in_root
146 $ HGPLAIN=1 hg status --cwd a
146 $ HGPLAIN=1 hg status --cwd a
147 ? a/1/in_a_1 (glob)
147 ? a/1/in_a_1 (glob)
148 ? a/in_a (glob)
148 ? a/in_a (glob)
149 ? b/1/in_b_1 (glob)
149 ? b/1/in_b_1 (glob)
150 ? b/2/in_b_2 (glob)
150 ? b/2/in_b_2 (glob)
151 ? b/in_b (glob)
151 ? b/in_b (glob)
152 ? in_root
152 ? in_root
153
153
154 if relative paths are explicitly off, tweakdefaults doesn't change it
154 if relative paths are explicitly off, tweakdefaults doesn't change it
155 $ cat >> $HGRCPATH <<EOF
155 $ cat >> $HGRCPATH <<EOF
156 > [commands]
156 > [commands]
157 > status.relative = False
157 > status.relative = False
158 > EOF
158 > EOF
159 $ hg status --cwd a --config ui.tweakdefaults=yes
159 $ hg status --cwd a --config ui.tweakdefaults=yes
160 ? a/1/in_a_1
160 ? a/1/in_a_1
161 ? a/in_a
161 ? a/in_a
162 ? b/1/in_b_1
162 ? b/1/in_b_1
163 ? b/2/in_b_2
163 ? b/2/in_b_2
164 ? b/in_b
164 ? b/in_b
165 ? in_root
165 ? in_root
166
166
167 $ cd ..
167 $ cd ..
168
168
169 $ hg init repo2
169 $ hg init repo2
170 $ cd repo2
170 $ cd repo2
171 $ touch modified removed deleted ignored
171 $ touch modified removed deleted ignored
172 $ echo "^ignored$" > .hgignore
172 $ echo "^ignored$" > .hgignore
173 $ hg ci -A -m 'initial checkin'
173 $ hg ci -A -m 'initial checkin'
174 adding .hgignore
174 adding .hgignore
175 adding deleted
175 adding deleted
176 adding modified
176 adding modified
177 adding removed
177 adding removed
178 $ touch modified added unknown ignored
178 $ touch modified added unknown ignored
179 $ hg add added
179 $ hg add added
180 $ hg remove removed
180 $ hg remove removed
181 $ rm deleted
181 $ rm deleted
182
182
183 hg status:
183 hg status:
184
184
185 $ hg status
185 $ hg status
186 A added
186 A added
187 R removed
187 R removed
188 ! deleted
188 ! deleted
189 ? unknown
189 ? unknown
190
190
191 hg status modified added removed deleted unknown never-existed ignored:
191 hg status modified added removed deleted unknown never-existed ignored:
192
192
193 $ hg status modified added removed deleted unknown never-existed ignored
193 $ hg status modified added removed deleted unknown never-existed ignored
194 never-existed: * (glob)
194 never-existed: * (glob)
195 A added
195 A added
196 R removed
196 R removed
197 ! deleted
197 ! deleted
198 ? unknown
198 ? unknown
199
199
200 $ hg copy modified copied
200 $ hg copy modified copied
201
201
202 hg status -C:
202 hg status -C:
203
203
204 $ hg status -C
204 $ hg status -C
205 A added
205 A added
206 A copied
206 A copied
207 modified
207 modified
208 R removed
208 R removed
209 ! deleted
209 ! deleted
210 ? unknown
210 ? unknown
211
211
212 hg status -A:
212 hg status -A:
213
213
214 $ hg status -A
214 $ hg status -A
215 A added
215 A added
216 A copied
216 A copied
217 modified
217 modified
218 R removed
218 R removed
219 ! deleted
219 ! deleted
220 ? unknown
220 ? unknown
221 I ignored
221 I ignored
222 C .hgignore
222 C .hgignore
223 C modified
223 C modified
224
224
225 $ hg status -A -T '{status} {path} {node|shortest}\n'
225 $ hg status -A -T '{status} {path} {node|shortest}\n'
226 A added ffff
226 A added ffff
227 A copied ffff
227 A copied ffff
228 R removed ffff
228 R removed ffff
229 ! deleted ffff
229 ! deleted ffff
230 ? unknown ffff
230 ? unknown ffff
231 I ignored ffff
231 I ignored ffff
232 C .hgignore ffff
232 C .hgignore ffff
233 C modified ffff
233 C modified ffff
234
234
235 $ hg status -A -Tjson
235 $ hg status -A -Tjson
236 [
236 [
237 {
237 {
238 "path": "added",
238 "path": "added",
239 "status": "A"
239 "status": "A"
240 },
240 },
241 {
241 {
242 "path": "copied",
242 "path": "copied",
243 "source": "modified",
243 "source": "modified",
244 "status": "A"
244 "status": "A"
245 },
245 },
246 {
246 {
247 "path": "removed",
247 "path": "removed",
248 "status": "R"
248 "status": "R"
249 },
249 },
250 {
250 {
251 "path": "deleted",
251 "path": "deleted",
252 "status": "!"
252 "status": "!"
253 },
253 },
254 {
254 {
255 "path": "unknown",
255 "path": "unknown",
256 "status": "?"
256 "status": "?"
257 },
257 },
258 {
258 {
259 "path": "ignored",
259 "path": "ignored",
260 "status": "I"
260 "status": "I"
261 },
261 },
262 {
262 {
263 "path": ".hgignore",
263 "path": ".hgignore",
264 "status": "C"
264 "status": "C"
265 },
265 },
266 {
266 {
267 "path": "modified",
267 "path": "modified",
268 "status": "C"
268 "status": "C"
269 }
269 }
270 ]
270 ]
271
271
272 $ hg status -A -Tpickle > pickle
272 $ hg status -A -Tpickle > pickle
273 >>> from __future__ import print_function
273 >>> from __future__ import print_function
274 >>> import pickle
274 >>> import pickle
275 >>> print(sorted((x['status'], x['path']) for x in pickle.load(open("pickle"))))
275 >>> print(sorted((x['status'], x['path']) for x in pickle.load(open("pickle"))))
276 [('!', 'deleted'), ('?', 'pickle'), ('?', 'unknown'), ('A', 'added'), ('A', 'copied'), ('C', '.hgignore'), ('C', 'modified'), ('I', 'ignored'), ('R', 'removed')]
276 [('!', 'deleted'), ('?', 'pickle'), ('?', 'unknown'), ('A', 'added'), ('A', 'copied'), ('C', '.hgignore'), ('C', 'modified'), ('I', 'ignored'), ('R', 'removed')]
277 $ rm pickle
277 $ rm pickle
278
278
279 $ echo "^ignoreddir$" > .hgignore
279 $ echo "^ignoreddir$" > .hgignore
280 $ mkdir ignoreddir
280 $ mkdir ignoreddir
281 $ touch ignoreddir/file
281 $ touch ignoreddir/file
282
282
283 Test templater support:
283 Test templater support:
284
284
285 $ hg status -AT "[{status}]\t{if(source, '{source} -> ')}{path}\n"
285 $ hg status -AT "[{status}]\t{if(source, '{source} -> ')}{path}\n"
286 [M] .hgignore
286 [M] .hgignore
287 [A] added
287 [A] added
288 [A] modified -> copied
288 [A] modified -> copied
289 [R] removed
289 [R] removed
290 [!] deleted
290 [!] deleted
291 [?] ignored
291 [?] ignored
292 [?] unknown
292 [?] unknown
293 [I] ignoreddir/file
293 [I] ignoreddir/file
294 [C] modified
294 [C] modified
295 $ hg status -AT default
295 $ hg status -AT default
296 M .hgignore
296 M .hgignore
297 A added
297 A added
298 A copied
298 A copied
299 modified
299 modified
300 R removed
300 R removed
301 ! deleted
301 ! deleted
302 ? ignored
302 ? ignored
303 ? unknown
303 ? unknown
304 I ignoreddir/file
304 I ignoreddir/file
305 C modified
305 C modified
306 $ hg status -T compact
306 $ hg status -T compact
307 abort: "status" not in template map
307 abort: "status" not in template map
308 [255]
308 [255]
309
309
310 hg status ignoreddir/file:
310 hg status ignoreddir/file:
311
311
312 $ hg status ignoreddir/file
312 $ hg status ignoreddir/file
313
313
314 hg status -i ignoreddir/file:
314 hg status -i ignoreddir/file:
315
315
316 $ hg status -i ignoreddir/file
316 $ hg status -i ignoreddir/file
317 I ignoreddir/file
317 I ignoreddir/file
318 $ cd ..
318 $ cd ..
319
319
320 Check 'status -q' and some combinations
320 Check 'status -q' and some combinations
321
321
322 $ hg init repo3
322 $ hg init repo3
323 $ cd repo3
323 $ cd repo3
324 $ touch modified removed deleted ignored
324 $ touch modified removed deleted ignored
325 $ echo "^ignored$" > .hgignore
325 $ echo "^ignored$" > .hgignore
326 $ hg commit -A -m 'initial checkin'
326 $ hg commit -A -m 'initial checkin'
327 adding .hgignore
327 adding .hgignore
328 adding deleted
328 adding deleted
329 adding modified
329 adding modified
330 adding removed
330 adding removed
331 $ touch added unknown ignored
331 $ touch added unknown ignored
332 $ hg add added
332 $ hg add added
333 $ echo "test" >> modified
333 $ echo "test" >> modified
334 $ hg remove removed
334 $ hg remove removed
335 $ rm deleted
335 $ rm deleted
336 $ hg copy modified copied
336 $ hg copy modified copied
337
337
338 Specify working directory revision explicitly, that should be the same as
338 Specify working directory revision explicitly, that should be the same as
339 "hg status"
339 "hg status"
340
340
341 $ hg status --change "wdir()"
341 $ hg status --change "wdir()"
342 M modified
342 M modified
343 A added
343 A added
344 A copied
344 A copied
345 R removed
345 R removed
346 ! deleted
346 ! deleted
347 ? unknown
347 ? unknown
348
348
349 Run status with 2 different flags.
349 Run status with 2 different flags.
350 Check if result is the same or different.
350 Check if result is the same or different.
351 If result is not as expected, raise error
351 If result is not as expected, raise error
352
352
353 $ assert() {
353 $ assert() {
354 > hg status $1 > ../a
354 > hg status $1 > ../a
355 > hg status $2 > ../b
355 > hg status $2 > ../b
356 > if diff ../a ../b > /dev/null; then
356 > if diff ../a ../b > /dev/null; then
357 > out=0
357 > out=0
358 > else
358 > else
359 > out=1
359 > out=1
360 > fi
360 > fi
361 > if [ $3 -eq 0 ]; then
361 > if [ $3 -eq 0 ]; then
362 > df="same"
362 > df="same"
363 > else
363 > else
364 > df="different"
364 > df="different"
365 > fi
365 > fi
366 > if [ $out -ne $3 ]; then
366 > if [ $out -ne $3 ]; then
367 > echo "Error on $1 and $2, should be $df."
367 > echo "Error on $1 and $2, should be $df."
368 > fi
368 > fi
369 > }
369 > }
370
370
371 Assert flag1 flag2 [0-same | 1-different]
371 Assert flag1 flag2 [0-same | 1-different]
372
372
373 $ assert "-q" "-mard" 0
373 $ assert "-q" "-mard" 0
374 $ assert "-A" "-marduicC" 0
374 $ assert "-A" "-marduicC" 0
375 $ assert "-qA" "-mardcC" 0
375 $ assert "-qA" "-mardcC" 0
376 $ assert "-qAui" "-A" 0
376 $ assert "-qAui" "-A" 0
377 $ assert "-qAu" "-marducC" 0
377 $ assert "-qAu" "-marducC" 0
378 $ assert "-qAi" "-mardicC" 0
378 $ assert "-qAi" "-mardicC" 0
379 $ assert "-qu" "-u" 0
379 $ assert "-qu" "-u" 0
380 $ assert "-q" "-u" 1
380 $ assert "-q" "-u" 1
381 $ assert "-m" "-a" 1
381 $ assert "-m" "-a" 1
382 $ assert "-r" "-d" 1
382 $ assert "-r" "-d" 1
383 $ cd ..
383 $ cd ..
384
384
385 $ hg init repo4
385 $ hg init repo4
386 $ cd repo4
386 $ cd repo4
387 $ touch modified removed deleted
387 $ touch modified removed deleted
388 $ hg ci -q -A -m 'initial checkin'
388 $ hg ci -q -A -m 'initial checkin'
389 $ touch added unknown
389 $ touch added unknown
390 $ hg add added
390 $ hg add added
391 $ hg remove removed
391 $ hg remove removed
392 $ rm deleted
392 $ rm deleted
393 $ echo x > modified
393 $ echo x > modified
394 $ hg copy modified copied
394 $ hg copy modified copied
395 $ hg ci -m 'test checkin' -d "1000001 0"
395 $ hg ci -m 'test checkin' -d "1000001 0"
396 $ rm *
396 $ rm *
397 $ touch unrelated
397 $ touch unrelated
398 $ hg ci -q -A -m 'unrelated checkin' -d "1000002 0"
398 $ hg ci -q -A -m 'unrelated checkin' -d "1000002 0"
399
399
400 hg status --change 1:
400 hg status --change 1:
401
401
402 $ hg status --change 1
402 $ hg status --change 1
403 M modified
403 M modified
404 A added
404 A added
405 A copied
405 A copied
406 R removed
406 R removed
407
407
408 hg status --change 1 unrelated:
408 hg status --change 1 unrelated:
409
409
410 $ hg status --change 1 unrelated
410 $ hg status --change 1 unrelated
411
411
412 hg status -C --change 1 added modified copied removed deleted:
412 hg status -C --change 1 added modified copied removed deleted:
413
413
414 $ hg status -C --change 1 added modified copied removed deleted
414 $ hg status -C --change 1 added modified copied removed deleted
415 M modified
415 M modified
416 A added
416 A added
417 A copied
417 A copied
418 modified
418 modified
419 R removed
419 R removed
420
420
421 hg status -A --change 1 and revset:
421 hg status -A --change 1 and revset:
422
422
423 $ hg status -A --change '1|1'
423 $ hg status -A --change '1|1'
424 M modified
424 M modified
425 A added
425 A added
426 A copied
426 A copied
427 modified
427 modified
428 R removed
428 R removed
429 C deleted
429 C deleted
430
430
431 $ cd ..
431 $ cd ..
432
432
433 hg status with --rev and reverted changes:
433 hg status with --rev and reverted changes:
434
434
435 $ hg init reverted-changes-repo
435 $ hg init reverted-changes-repo
436 $ cd reverted-changes-repo
436 $ cd reverted-changes-repo
437 $ echo a > file
437 $ echo a > file
438 $ hg add file
438 $ hg add file
439 $ hg ci -m a
439 $ hg ci -m a
440 $ echo b > file
440 $ echo b > file
441 $ hg ci -m b
441 $ hg ci -m b
442
442
443 reverted file should appear clean
443 reverted file should appear clean
444
444
445 $ hg revert -r 0 .
445 $ hg revert -r 0 .
446 reverting file
446 reverting file
447 $ hg status -A --rev 0
447 $ hg status -A --rev 0
448 C file
448 C file
449
449
450 #if execbit
450 #if execbit
451 reverted file with changed flag should appear modified
451 reverted file with changed flag should appear modified
452
452
453 $ chmod +x file
453 $ chmod +x file
454 $ hg status -A --rev 0
454 $ hg status -A --rev 0
455 M file
455 M file
456
456
457 $ hg revert -r 0 .
457 $ hg revert -r 0 .
458 reverting file
458 reverting file
459
459
460 reverted and committed file with changed flag should appear modified
460 reverted and committed file with changed flag should appear modified
461
461
462 $ hg co -C .
462 $ hg co -C .
463 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
463 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
464 $ chmod +x file
464 $ chmod +x file
465 $ hg ci -m 'change flag'
465 $ hg ci -m 'change flag'
466 $ hg status -A --rev 1 --rev 2
466 $ hg status -A --rev 1 --rev 2
467 M file
467 M file
468 $ hg diff -r 1 -r 2
468 $ hg diff -r 1 -r 2
469
469
470 #endif
470 #endif
471
471
472 $ cd ..
472 $ cd ..
473
473
474 hg status of binary file starting with '\1\n', a separator for metadata:
474 hg status of binary file starting with '\1\n', a separator for metadata:
475
475
476 $ hg init repo5
476 $ hg init repo5
477 $ cd repo5
477 $ cd repo5
478 >>> open("010a", r"wb").write(b"\1\nfoo") and None
478 >>> open("010a", r"wb").write(b"\1\nfoo") and None
479 $ hg ci -q -A -m 'initial checkin'
479 $ hg ci -q -A -m 'initial checkin'
480 $ hg status -A
480 $ hg status -A
481 C 010a
481 C 010a
482
482
483 >>> open("010a", r"wb").write(b"\1\nbar") and None
483 >>> open("010a", r"wb").write(b"\1\nbar") and None
484 $ hg status -A
484 $ hg status -A
485 M 010a
485 M 010a
486 $ hg ci -q -m 'modify 010a'
486 $ hg ci -q -m 'modify 010a'
487 $ hg status -A --rev 0:1
487 $ hg status -A --rev 0:1
488 M 010a
488 M 010a
489
489
490 $ touch empty
490 $ touch empty
491 $ hg ci -q -A -m 'add another file'
491 $ hg ci -q -A -m 'add another file'
492 $ hg status -A --rev 1:2 010a
492 $ hg status -A --rev 1:2 010a
493 C 010a
493 C 010a
494
494
495 $ cd ..
495 $ cd ..
496
496
497 test "hg status" with "directory pattern" which matches against files
497 test "hg status" with "directory pattern" which matches against files
498 only known on target revision.
498 only known on target revision.
499
499
500 $ hg init repo6
500 $ hg init repo6
501 $ cd repo6
501 $ cd repo6
502
502
503 $ echo a > a.txt
503 $ echo a > a.txt
504 $ hg add a.txt
504 $ hg add a.txt
505 $ hg commit -m '#0'
505 $ hg commit -m '#0'
506 $ mkdir -p 1/2/3/4/5
506 $ mkdir -p 1/2/3/4/5
507 $ echo b > 1/2/3/4/5/b.txt
507 $ echo b > 1/2/3/4/5/b.txt
508 $ hg add 1/2/3/4/5/b.txt
508 $ hg add 1/2/3/4/5/b.txt
509 $ hg commit -m '#1'
509 $ hg commit -m '#1'
510
510
511 $ hg update -C 0 > /dev/null
511 $ hg update -C 0 > /dev/null
512 $ hg status -A
512 $ hg status -A
513 C a.txt
513 C a.txt
514
514
515 the directory matching against specified pattern should be removed,
515 the directory matching against specified pattern should be removed,
516 because directory existence prevents 'dirstate.walk()' from showing
516 because directory existence prevents 'dirstate.walk()' from showing
517 warning message about such pattern.
517 warning message about such pattern.
518
518
519 $ test ! -d 1
519 $ test ! -d 1
520 $ hg status -A --rev 1 1/2/3/4/5/b.txt
520 $ hg status -A --rev 1 1/2/3/4/5/b.txt
521 R 1/2/3/4/5/b.txt
521 R 1/2/3/4/5/b.txt
522 $ hg status -A --rev 1 1/2/3/4/5
522 $ hg status -A --rev 1 1/2/3/4/5
523 R 1/2/3/4/5/b.txt
523 R 1/2/3/4/5/b.txt
524 $ hg status -A --rev 1 1/2/3
524 $ hg status -A --rev 1 1/2/3
525 R 1/2/3/4/5/b.txt
525 R 1/2/3/4/5/b.txt
526 $ hg status -A --rev 1 1
526 $ hg status -A --rev 1 1
527 R 1/2/3/4/5/b.txt
527 R 1/2/3/4/5/b.txt
528
528
529 $ hg status --config ui.formatdebug=True --rev 1 1
529 $ hg status --config ui.formatdebug=True --rev 1 1
530 status = [
530 status = [
531 {*'path': '1/2/3/4/5/b.txt'*}, (glob)
531 {
532 'path': '1/2/3/4/5/b.txt',
533 'status': 'R'
534 },
532 ]
535 ]
533
536
534 #if windows
537 #if windows
535 $ hg --config ui.slash=false status -A --rev 1 1
538 $ hg --config ui.slash=false status -A --rev 1 1
536 R 1\2\3\4\5\b.txt
539 R 1\2\3\4\5\b.txt
537 #endif
540 #endif
538
541
539 $ cd ..
542 $ cd ..
540
543
541 Status after move overwriting a file (issue4458)
544 Status after move overwriting a file (issue4458)
542 =================================================
545 =================================================
543
546
544
547
545 $ hg init issue4458
548 $ hg init issue4458
546 $ cd issue4458
549 $ cd issue4458
547 $ echo a > a
550 $ echo a > a
548 $ echo b > b
551 $ echo b > b
549 $ hg commit -Am base
552 $ hg commit -Am base
550 adding a
553 adding a
551 adding b
554 adding b
552
555
553
556
554 with --force
557 with --force
555
558
556 $ hg mv b --force a
559 $ hg mv b --force a
557 $ hg st --copies
560 $ hg st --copies
558 M a
561 M a
559 b
562 b
560 R b
563 R b
561 $ hg revert --all
564 $ hg revert --all
562 reverting a
565 reverting a
563 undeleting b
566 undeleting b
564 $ rm *.orig
567 $ rm *.orig
565
568
566 without force
569 without force
567
570
568 $ hg rm a
571 $ hg rm a
569 $ hg st --copies
572 $ hg st --copies
570 R a
573 R a
571 $ hg mv b a
574 $ hg mv b a
572 $ hg st --copies
575 $ hg st --copies
573 M a
576 M a
574 b
577 b
575 R b
578 R b
576
579
577 using ui.statuscopies setting
580 using ui.statuscopies setting
578 $ hg st --config ui.statuscopies=true
581 $ hg st --config ui.statuscopies=true
579 M a
582 M a
580 b
583 b
581 R b
584 R b
582 $ hg st --config ui.statuscopies=false
585 $ hg st --config ui.statuscopies=false
583 M a
586 M a
584 R b
587 R b
585 $ hg st --config ui.tweakdefaults=yes
588 $ hg st --config ui.tweakdefaults=yes
586 M a
589 M a
587 b
590 b
588 R b
591 R b
589
592
590 using log status template (issue5155)
593 using log status template (issue5155)
591 $ hg log -Tstatus -r 'wdir()' -C
594 $ hg log -Tstatus -r 'wdir()' -C
592 changeset: 2147483647:ffffffffffff
595 changeset: 2147483647:ffffffffffff
593 parent: 0:8c55c58b4c0e
596 parent: 0:8c55c58b4c0e
594 user: test
597 user: test
595 date: * (glob)
598 date: * (glob)
596 files:
599 files:
597 M a
600 M a
598 b
601 b
599 R b
602 R b
600
603
601
604
602 Other "bug" highlight, the revision status does not report the copy information.
605 Other "bug" highlight, the revision status does not report the copy information.
603 This is buggy behavior.
606 This is buggy behavior.
604
607
605 $ hg commit -m 'blah'
608 $ hg commit -m 'blah'
606 $ hg st --copies --change .
609 $ hg st --copies --change .
607 M a
610 M a
608 R b
611 R b
609
612
610 using log status template, the copy information is displayed correctly.
613 using log status template, the copy information is displayed correctly.
611 $ hg log -Tstatus -r. -C
614 $ hg log -Tstatus -r. -C
612 changeset: 1:6685fde43d21
615 changeset: 1:6685fde43d21
613 tag: tip
616 tag: tip
614 user: test
617 user: test
615 date: * (glob)
618 date: * (glob)
616 summary: blah
619 summary: blah
617 files:
620 files:
618 M a
621 M a
619 b
622 b
620 R b
623 R b
621
624
622
625
623 $ cd ..
626 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now