##// END OF EJS Templates
templating: make -T much more flexible...
Matt Mackall -
r20668:3a35ba26 default
parent child Browse files
Show More
@@ -1,735 +1,735 b''
1 # keyword.py - $Keyword$ expansion for Mercurial
1 # keyword.py - $Keyword$ expansion for Mercurial
2 #
2 #
3 # Copyright 2007-2012 Christian Ebert <blacktrash@gmx.net>
3 # Copyright 2007-2012 Christian Ebert <blacktrash@gmx.net>
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 # $Id$
8 # $Id$
9 #
9 #
10 # Keyword expansion hack against the grain of a Distributed SCM
10 # Keyword expansion hack against the grain of a Distributed SCM
11 #
11 #
12 # There are many good reasons why this is not needed in a distributed
12 # There are many good reasons why this is not needed in a distributed
13 # SCM, still it may be useful in very small projects based on single
13 # SCM, still it may be useful in very small projects based on single
14 # files (like LaTeX packages), that are mostly addressed to an
14 # files (like LaTeX packages), that are mostly addressed to an
15 # audience not running a version control system.
15 # audience not running a version control system.
16 #
16 #
17 # For in-depth discussion refer to
17 # For in-depth discussion refer to
18 # <http://mercurial.selenic.com/wiki/KeywordPlan>.
18 # <http://mercurial.selenic.com/wiki/KeywordPlan>.
19 #
19 #
20 # Keyword expansion is based on Mercurial's changeset template mappings.
20 # Keyword expansion is based on Mercurial's changeset template mappings.
21 #
21 #
22 # Binary files are not touched.
22 # Binary files are not touched.
23 #
23 #
24 # Files to act upon/ignore are specified in the [keyword] section.
24 # Files to act upon/ignore are specified in the [keyword] section.
25 # Customized keyword template mappings in the [keywordmaps] section.
25 # Customized keyword template mappings in the [keywordmaps] section.
26 #
26 #
27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
28
28
29 '''expand keywords in tracked files
29 '''expand keywords in tracked files
30
30
31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
32 tracked text files selected by your configuration.
32 tracked text files selected by your configuration.
33
33
34 Keywords are only expanded in local repositories and not stored in the
34 Keywords are only expanded in local repositories and not stored in the
35 change history. The mechanism can be regarded as a convenience for the
35 change history. The mechanism can be regarded as a convenience for the
36 current user or for archive distribution.
36 current user or for archive distribution.
37
37
38 Keywords expand to the changeset data pertaining to the latest change
38 Keywords expand to the changeset data pertaining to the latest change
39 relative to the working directory parent of each file.
39 relative to the working directory parent of each file.
40
40
41 Configuration is done in the [keyword], [keywordset] and [keywordmaps]
41 Configuration is done in the [keyword], [keywordset] and [keywordmaps]
42 sections of hgrc files.
42 sections of hgrc files.
43
43
44 Example::
44 Example::
45
45
46 [keyword]
46 [keyword]
47 # expand keywords in every python file except those matching "x*"
47 # expand keywords in every python file except those matching "x*"
48 **.py =
48 **.py =
49 x* = ignore
49 x* = ignore
50
50
51 [keywordset]
51 [keywordset]
52 # prefer svn- over cvs-like default keywordmaps
52 # prefer svn- over cvs-like default keywordmaps
53 svn = True
53 svn = True
54
54
55 .. note::
55 .. note::
56
56
57 The more specific you are in your filename patterns the less you
57 The more specific you are in your filename patterns the less you
58 lose speed in huge repositories.
58 lose speed in huge repositories.
59
59
60 For [keywordmaps] template mapping and expansion demonstration and
60 For [keywordmaps] template mapping and expansion demonstration and
61 control run :hg:`kwdemo`. See :hg:`help templates` for a list of
61 control run :hg:`kwdemo`. See :hg:`help templates` for a list of
62 available templates and filters.
62 available templates and filters.
63
63
64 Three additional date template filters are provided:
64 Three additional date template filters are provided:
65
65
66 :``utcdate``: "2006/09/18 15:13:13"
66 :``utcdate``: "2006/09/18 15:13:13"
67 :``svnutcdate``: "2006-09-18 15:13:13Z"
67 :``svnutcdate``: "2006-09-18 15:13:13Z"
68 :``svnisodate``: "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
68 :``svnisodate``: "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
69
69
70 The default template mappings (view with :hg:`kwdemo -d`) can be
70 The default template mappings (view with :hg:`kwdemo -d`) can be
71 replaced with customized keywords and templates. Again, run
71 replaced with customized keywords and templates. Again, run
72 :hg:`kwdemo` to control the results of your configuration changes.
72 :hg:`kwdemo` to control the results of your configuration changes.
73
73
74 Before changing/disabling active keywords, you must run :hg:`kwshrink`
74 Before changing/disabling active keywords, you must run :hg:`kwshrink`
75 to avoid storing expanded keywords in the change history.
75 to avoid storing expanded keywords in the change history.
76
76
77 To force expansion after enabling it, or a configuration change, run
77 To force expansion after enabling it, or a configuration change, run
78 :hg:`kwexpand`.
78 :hg:`kwexpand`.
79
79
80 Expansions spanning more than one line and incremental expansions,
80 Expansions spanning more than one line and incremental expansions,
81 like CVS' $Log$, are not supported. A keyword template map "Log =
81 like CVS' $Log$, are not supported. A keyword template map "Log =
82 {desc}" expands to the first line of the changeset description.
82 {desc}" expands to the first line of the changeset description.
83 '''
83 '''
84
84
85 from mercurial import commands, context, cmdutil, dispatch, filelog, extensions
85 from mercurial import commands, context, cmdutil, dispatch, filelog, extensions
86 from mercurial import localrepo, match, patch, templatefilters, templater, util
86 from mercurial import localrepo, match, patch, templatefilters, templater, util
87 from mercurial import scmutil, pathutil
87 from mercurial import scmutil, pathutil
88 from mercurial.hgweb import webcommands
88 from mercurial.hgweb import webcommands
89 from mercurial.i18n import _
89 from mercurial.i18n import _
90 import os, re, shutil, tempfile
90 import os, re, shutil, tempfile
91
91
92 commands.optionalrepo += ' kwdemo'
92 commands.optionalrepo += ' kwdemo'
93 commands.inferrepo += ' kwexpand kwfiles kwshrink'
93 commands.inferrepo += ' kwexpand kwfiles kwshrink'
94
94
95 cmdtable = {}
95 cmdtable = {}
96 command = cmdutil.command(cmdtable)
96 command = cmdutil.command(cmdtable)
97 testedwith = 'internal'
97 testedwith = 'internal'
98
98
99 # hg commands that do not act on keywords
99 # hg commands that do not act on keywords
100 nokwcommands = ('add addremove annotate bundle export grep incoming init log'
100 nokwcommands = ('add addremove annotate bundle export grep incoming init log'
101 ' outgoing push tip verify convert email glog')
101 ' outgoing push tip verify convert email glog')
102
102
103 # hg commands that trigger expansion only when writing to working dir,
103 # hg commands that trigger expansion only when writing to working dir,
104 # not when reading filelog, and unexpand when reading from working dir
104 # not when reading filelog, and unexpand when reading from working dir
105 restricted = 'merge kwexpand kwshrink record qrecord resolve transplant'
105 restricted = 'merge kwexpand kwshrink record qrecord resolve transplant'
106
106
107 # names of extensions using dorecord
107 # names of extensions using dorecord
108 recordextensions = 'record'
108 recordextensions = 'record'
109
109
110 colortable = {
110 colortable = {
111 'kwfiles.enabled': 'green bold',
111 'kwfiles.enabled': 'green bold',
112 'kwfiles.deleted': 'cyan bold underline',
112 'kwfiles.deleted': 'cyan bold underline',
113 'kwfiles.enabledunknown': 'green',
113 'kwfiles.enabledunknown': 'green',
114 'kwfiles.ignored': 'bold',
114 'kwfiles.ignored': 'bold',
115 'kwfiles.ignoredunknown': 'none'
115 'kwfiles.ignoredunknown': 'none'
116 }
116 }
117
117
118 # date like in cvs' $Date
118 # date like in cvs' $Date
119 def utcdate(text):
119 def utcdate(text):
120 ''':utcdate: Date. Returns a UTC-date in this format: "2009/08/18 11:00:13".
120 ''':utcdate: Date. Returns a UTC-date in this format: "2009/08/18 11:00:13".
121 '''
121 '''
122 return util.datestr((util.parsedate(text)[0], 0), '%Y/%m/%d %H:%M:%S')
122 return util.datestr((util.parsedate(text)[0], 0), '%Y/%m/%d %H:%M:%S')
123 # date like in svn's $Date
123 # date like in svn's $Date
124 def svnisodate(text):
124 def svnisodate(text):
125 ''':svnisodate: Date. Returns a date in this format: "2009-08-18 13:00:13
125 ''':svnisodate: Date. Returns a date in this format: "2009-08-18 13:00:13
126 +0200 (Tue, 18 Aug 2009)".
126 +0200 (Tue, 18 Aug 2009)".
127 '''
127 '''
128 return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
128 return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
129 # date like in svn's $Id
129 # date like in svn's $Id
130 def svnutcdate(text):
130 def svnutcdate(text):
131 ''':svnutcdate: Date. Returns a UTC-date in this format: "2009-08-18
131 ''':svnutcdate: Date. Returns a UTC-date in this format: "2009-08-18
132 11:00:13Z".
132 11:00:13Z".
133 '''
133 '''
134 return util.datestr((util.parsedate(text)[0], 0), '%Y-%m-%d %H:%M:%SZ')
134 return util.datestr((util.parsedate(text)[0], 0), '%Y-%m-%d %H:%M:%SZ')
135
135
136 templatefilters.filters.update({'utcdate': utcdate,
136 templatefilters.filters.update({'utcdate': utcdate,
137 'svnisodate': svnisodate,
137 'svnisodate': svnisodate,
138 'svnutcdate': svnutcdate})
138 'svnutcdate': svnutcdate})
139
139
140 # make keyword tools accessible
140 # make keyword tools accessible
141 kwtools = {'templater': None, 'hgcmd': ''}
141 kwtools = {'templater': None, 'hgcmd': ''}
142
142
143 def _defaultkwmaps(ui):
143 def _defaultkwmaps(ui):
144 '''Returns default keywordmaps according to keywordset configuration.'''
144 '''Returns default keywordmaps according to keywordset configuration.'''
145 templates = {
145 templates = {
146 'Revision': '{node|short}',
146 'Revision': '{node|short}',
147 'Author': '{author|user}',
147 'Author': '{author|user}',
148 }
148 }
149 kwsets = ({
149 kwsets = ({
150 'Date': '{date|utcdate}',
150 'Date': '{date|utcdate}',
151 'RCSfile': '{file|basename},v',
151 'RCSfile': '{file|basename},v',
152 'RCSFile': '{file|basename},v', # kept for backwards compatibility
152 'RCSFile': '{file|basename},v', # kept for backwards compatibility
153 # with hg-keyword
153 # with hg-keyword
154 'Source': '{root}/{file},v',
154 'Source': '{root}/{file},v',
155 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
155 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
156 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
156 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
157 }, {
157 }, {
158 'Date': '{date|svnisodate}',
158 'Date': '{date|svnisodate}',
159 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
159 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
160 'LastChangedRevision': '{node|short}',
160 'LastChangedRevision': '{node|short}',
161 'LastChangedBy': '{author|user}',
161 'LastChangedBy': '{author|user}',
162 'LastChangedDate': '{date|svnisodate}',
162 'LastChangedDate': '{date|svnisodate}',
163 })
163 })
164 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
164 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
165 return templates
165 return templates
166
166
167 def _shrinktext(text, subfunc):
167 def _shrinktext(text, subfunc):
168 '''Helper for keyword expansion removal in text.
168 '''Helper for keyword expansion removal in text.
169 Depending on subfunc also returns number of substitutions.'''
169 Depending on subfunc also returns number of substitutions.'''
170 return subfunc(r'$\1$', text)
170 return subfunc(r'$\1$', text)
171
171
172 def _preselect(wstatus, changed):
172 def _preselect(wstatus, changed):
173 '''Retrieves modified and added files from a working directory state
173 '''Retrieves modified and added files from a working directory state
174 and returns the subset of each contained in given changed files
174 and returns the subset of each contained in given changed files
175 retrieved from a change context.'''
175 retrieved from a change context.'''
176 modified, added = wstatus[:2]
176 modified, added = wstatus[:2]
177 modified = [f for f in modified if f in changed]
177 modified = [f for f in modified if f in changed]
178 added = [f for f in added if f in changed]
178 added = [f for f in added if f in changed]
179 return modified, added
179 return modified, added
180
180
181
181
182 class kwtemplater(object):
182 class kwtemplater(object):
183 '''
183 '''
184 Sets up keyword templates, corresponding keyword regex, and
184 Sets up keyword templates, corresponding keyword regex, and
185 provides keyword substitution functions.
185 provides keyword substitution functions.
186 '''
186 '''
187
187
188 def __init__(self, ui, repo, inc, exc):
188 def __init__(self, ui, repo, inc, exc):
189 self.ui = ui
189 self.ui = ui
190 self.repo = repo
190 self.repo = repo
191 self.match = match.match(repo.root, '', [], inc, exc)
191 self.match = match.match(repo.root, '', [], inc, exc)
192 self.restrict = kwtools['hgcmd'] in restricted.split()
192 self.restrict = kwtools['hgcmd'] in restricted.split()
193 self.postcommit = False
193 self.postcommit = False
194
194
195 kwmaps = self.ui.configitems('keywordmaps')
195 kwmaps = self.ui.configitems('keywordmaps')
196 if kwmaps: # override default templates
196 if kwmaps: # override default templates
197 self.templates = dict((k, templater.parsestring(v, False))
197 self.templates = dict((k, templater.parsestring(v, False))
198 for k, v in kwmaps)
198 for k, v in kwmaps)
199 else:
199 else:
200 self.templates = _defaultkwmaps(self.ui)
200 self.templates = _defaultkwmaps(self.ui)
201
201
202 @util.propertycache
202 @util.propertycache
203 def escape(self):
203 def escape(self):
204 '''Returns bar-separated and escaped keywords.'''
204 '''Returns bar-separated and escaped keywords.'''
205 return '|'.join(map(re.escape, self.templates.keys()))
205 return '|'.join(map(re.escape, self.templates.keys()))
206
206
207 @util.propertycache
207 @util.propertycache
208 def rekw(self):
208 def rekw(self):
209 '''Returns regex for unexpanded keywords.'''
209 '''Returns regex for unexpanded keywords.'''
210 return re.compile(r'\$(%s)\$' % self.escape)
210 return re.compile(r'\$(%s)\$' % self.escape)
211
211
212 @util.propertycache
212 @util.propertycache
213 def rekwexp(self):
213 def rekwexp(self):
214 '''Returns regex for expanded keywords.'''
214 '''Returns regex for expanded keywords.'''
215 return re.compile(r'\$(%s): [^$\n\r]*? \$' % self.escape)
215 return re.compile(r'\$(%s): [^$\n\r]*? \$' % self.escape)
216
216
217 def substitute(self, data, path, ctx, subfunc):
217 def substitute(self, data, path, ctx, subfunc):
218 '''Replaces keywords in data with expanded template.'''
218 '''Replaces keywords in data with expanded template.'''
219 def kwsub(mobj):
219 def kwsub(mobj):
220 kw = mobj.group(1)
220 kw = mobj.group(1)
221 ct = cmdutil.changeset_templater(self.ui, self.repo, False, None
221 ct = cmdutil.changeset_templater(self.ui, self.repo, False, None,
222 self.templates[kw], '', False)
222 self.templates[kw], '', False)
223 self.ui.pushbuffer()
223 self.ui.pushbuffer()
224 ct.show(ctx, root=self.repo.root, file=path)
224 ct.show(ctx, root=self.repo.root, file=path)
225 ekw = templatefilters.firstline(self.ui.popbuffer())
225 ekw = templatefilters.firstline(self.ui.popbuffer())
226 return '$%s: %s $' % (kw, ekw)
226 return '$%s: %s $' % (kw, ekw)
227 return subfunc(kwsub, data)
227 return subfunc(kwsub, data)
228
228
229 def linkctx(self, path, fileid):
229 def linkctx(self, path, fileid):
230 '''Similar to filelog.linkrev, but returns a changectx.'''
230 '''Similar to filelog.linkrev, but returns a changectx.'''
231 return self.repo.filectx(path, fileid=fileid).changectx()
231 return self.repo.filectx(path, fileid=fileid).changectx()
232
232
233 def expand(self, path, node, data):
233 def expand(self, path, node, data):
234 '''Returns data with keywords expanded.'''
234 '''Returns data with keywords expanded.'''
235 if not self.restrict and self.match(path) and not util.binary(data):
235 if not self.restrict and self.match(path) and not util.binary(data):
236 ctx = self.linkctx(path, node)
236 ctx = self.linkctx(path, node)
237 return self.substitute(data, path, ctx, self.rekw.sub)
237 return self.substitute(data, path, ctx, self.rekw.sub)
238 return data
238 return data
239
239
240 def iskwfile(self, cand, ctx):
240 def iskwfile(self, cand, ctx):
241 '''Returns subset of candidates which are configured for keyword
241 '''Returns subset of candidates which are configured for keyword
242 expansion but are not symbolic links.'''
242 expansion but are not symbolic links.'''
243 return [f for f in cand if self.match(f) and 'l' not in ctx.flags(f)]
243 return [f for f in cand if self.match(f) and 'l' not in ctx.flags(f)]
244
244
245 def overwrite(self, ctx, candidates, lookup, expand, rekw=False):
245 def overwrite(self, ctx, candidates, lookup, expand, rekw=False):
246 '''Overwrites selected files expanding/shrinking keywords.'''
246 '''Overwrites selected files expanding/shrinking keywords.'''
247 if self.restrict or lookup or self.postcommit: # exclude kw_copy
247 if self.restrict or lookup or self.postcommit: # exclude kw_copy
248 candidates = self.iskwfile(candidates, ctx)
248 candidates = self.iskwfile(candidates, ctx)
249 if not candidates:
249 if not candidates:
250 return
250 return
251 kwcmd = self.restrict and lookup # kwexpand/kwshrink
251 kwcmd = self.restrict and lookup # kwexpand/kwshrink
252 if self.restrict or expand and lookup:
252 if self.restrict or expand and lookup:
253 mf = ctx.manifest()
253 mf = ctx.manifest()
254 if self.restrict or rekw:
254 if self.restrict or rekw:
255 re_kw = self.rekw
255 re_kw = self.rekw
256 else:
256 else:
257 re_kw = self.rekwexp
257 re_kw = self.rekwexp
258 if expand:
258 if expand:
259 msg = _('overwriting %s expanding keywords\n')
259 msg = _('overwriting %s expanding keywords\n')
260 else:
260 else:
261 msg = _('overwriting %s shrinking keywords\n')
261 msg = _('overwriting %s shrinking keywords\n')
262 for f in candidates:
262 for f in candidates:
263 if self.restrict:
263 if self.restrict:
264 data = self.repo.file(f).read(mf[f])
264 data = self.repo.file(f).read(mf[f])
265 else:
265 else:
266 data = self.repo.wread(f)
266 data = self.repo.wread(f)
267 if util.binary(data):
267 if util.binary(data):
268 continue
268 continue
269 if expand:
269 if expand:
270 if lookup:
270 if lookup:
271 ctx = self.linkctx(f, mf[f])
271 ctx = self.linkctx(f, mf[f])
272 data, found = self.substitute(data, f, ctx, re_kw.subn)
272 data, found = self.substitute(data, f, ctx, re_kw.subn)
273 elif self.restrict:
273 elif self.restrict:
274 found = re_kw.search(data)
274 found = re_kw.search(data)
275 else:
275 else:
276 data, found = _shrinktext(data, re_kw.subn)
276 data, found = _shrinktext(data, re_kw.subn)
277 if found:
277 if found:
278 self.ui.note(msg % f)
278 self.ui.note(msg % f)
279 fp = self.repo.wopener(f, "wb", atomictemp=True)
279 fp = self.repo.wopener(f, "wb", atomictemp=True)
280 fp.write(data)
280 fp.write(data)
281 fp.close()
281 fp.close()
282 if kwcmd:
282 if kwcmd:
283 self.repo.dirstate.normal(f)
283 self.repo.dirstate.normal(f)
284 elif self.postcommit:
284 elif self.postcommit:
285 self.repo.dirstate.normallookup(f)
285 self.repo.dirstate.normallookup(f)
286
286
287 def shrink(self, fname, text):
287 def shrink(self, fname, text):
288 '''Returns text with all keyword substitutions removed.'''
288 '''Returns text with all keyword substitutions removed.'''
289 if self.match(fname) and not util.binary(text):
289 if self.match(fname) and not util.binary(text):
290 return _shrinktext(text, self.rekwexp.sub)
290 return _shrinktext(text, self.rekwexp.sub)
291 return text
291 return text
292
292
293 def shrinklines(self, fname, lines):
293 def shrinklines(self, fname, lines):
294 '''Returns lines with keyword substitutions removed.'''
294 '''Returns lines with keyword substitutions removed.'''
295 if self.match(fname):
295 if self.match(fname):
296 text = ''.join(lines)
296 text = ''.join(lines)
297 if not util.binary(text):
297 if not util.binary(text):
298 return _shrinktext(text, self.rekwexp.sub).splitlines(True)
298 return _shrinktext(text, self.rekwexp.sub).splitlines(True)
299 return lines
299 return lines
300
300
301 def wread(self, fname, data):
301 def wread(self, fname, data):
302 '''If in restricted mode returns data read from wdir with
302 '''If in restricted mode returns data read from wdir with
303 keyword substitutions removed.'''
303 keyword substitutions removed.'''
304 if self.restrict:
304 if self.restrict:
305 return self.shrink(fname, data)
305 return self.shrink(fname, data)
306 return data
306 return data
307
307
308 class kwfilelog(filelog.filelog):
308 class kwfilelog(filelog.filelog):
309 '''
309 '''
310 Subclass of filelog to hook into its read, add, cmp methods.
310 Subclass of filelog to hook into its read, add, cmp methods.
311 Keywords are "stored" unexpanded, and processed on reading.
311 Keywords are "stored" unexpanded, and processed on reading.
312 '''
312 '''
313 def __init__(self, opener, kwt, path):
313 def __init__(self, opener, kwt, path):
314 super(kwfilelog, self).__init__(opener, path)
314 super(kwfilelog, self).__init__(opener, path)
315 self.kwt = kwt
315 self.kwt = kwt
316 self.path = path
316 self.path = path
317
317
318 def read(self, node):
318 def read(self, node):
319 '''Expands keywords when reading filelog.'''
319 '''Expands keywords when reading filelog.'''
320 data = super(kwfilelog, self).read(node)
320 data = super(kwfilelog, self).read(node)
321 if self.renamed(node):
321 if self.renamed(node):
322 return data
322 return data
323 return self.kwt.expand(self.path, node, data)
323 return self.kwt.expand(self.path, node, data)
324
324
325 def add(self, text, meta, tr, link, p1=None, p2=None):
325 def add(self, text, meta, tr, link, p1=None, p2=None):
326 '''Removes keyword substitutions when adding to filelog.'''
326 '''Removes keyword substitutions when adding to filelog.'''
327 text = self.kwt.shrink(self.path, text)
327 text = self.kwt.shrink(self.path, text)
328 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
328 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
329
329
330 def cmp(self, node, text):
330 def cmp(self, node, text):
331 '''Removes keyword substitutions for comparison.'''
331 '''Removes keyword substitutions for comparison.'''
332 text = self.kwt.shrink(self.path, text)
332 text = self.kwt.shrink(self.path, text)
333 return super(kwfilelog, self).cmp(node, text)
333 return super(kwfilelog, self).cmp(node, text)
334
334
335 def _status(ui, repo, wctx, kwt, *pats, **opts):
335 def _status(ui, repo, wctx, kwt, *pats, **opts):
336 '''Bails out if [keyword] configuration is not active.
336 '''Bails out if [keyword] configuration is not active.
337 Returns status of working directory.'''
337 Returns status of working directory.'''
338 if kwt:
338 if kwt:
339 return repo.status(match=scmutil.match(wctx, pats, opts), clean=True,
339 return repo.status(match=scmutil.match(wctx, pats, opts), clean=True,
340 unknown=opts.get('unknown') or opts.get('all'))
340 unknown=opts.get('unknown') or opts.get('all'))
341 if ui.configitems('keyword'):
341 if ui.configitems('keyword'):
342 raise util.Abort(_('[keyword] patterns cannot match'))
342 raise util.Abort(_('[keyword] patterns cannot match'))
343 raise util.Abort(_('no [keyword] patterns configured'))
343 raise util.Abort(_('no [keyword] patterns configured'))
344
344
345 def _kwfwrite(ui, repo, expand, *pats, **opts):
345 def _kwfwrite(ui, repo, expand, *pats, **opts):
346 '''Selects files and passes them to kwtemplater.overwrite.'''
346 '''Selects files and passes them to kwtemplater.overwrite.'''
347 wctx = repo[None]
347 wctx = repo[None]
348 if len(wctx.parents()) > 1:
348 if len(wctx.parents()) > 1:
349 raise util.Abort(_('outstanding uncommitted merge'))
349 raise util.Abort(_('outstanding uncommitted merge'))
350 kwt = kwtools['templater']
350 kwt = kwtools['templater']
351 wlock = repo.wlock()
351 wlock = repo.wlock()
352 try:
352 try:
353 status = _status(ui, repo, wctx, kwt, *pats, **opts)
353 status = _status(ui, repo, wctx, kwt, *pats, **opts)
354 modified, added, removed, deleted, unknown, ignored, clean = status
354 modified, added, removed, deleted, unknown, ignored, clean = status
355 if modified or added or removed or deleted:
355 if modified or added or removed or deleted:
356 raise util.Abort(_('outstanding uncommitted changes'))
356 raise util.Abort(_('outstanding uncommitted changes'))
357 kwt.overwrite(wctx, clean, True, expand)
357 kwt.overwrite(wctx, clean, True, expand)
358 finally:
358 finally:
359 wlock.release()
359 wlock.release()
360
360
361 @command('kwdemo',
361 @command('kwdemo',
362 [('d', 'default', None, _('show default keyword template maps')),
362 [('d', 'default', None, _('show default keyword template maps')),
363 ('f', 'rcfile', '',
363 ('f', 'rcfile', '',
364 _('read maps from rcfile'), _('FILE'))],
364 _('read maps from rcfile'), _('FILE'))],
365 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...'))
365 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...'))
366 def demo(ui, repo, *args, **opts):
366 def demo(ui, repo, *args, **opts):
367 '''print [keywordmaps] configuration and an expansion example
367 '''print [keywordmaps] configuration and an expansion example
368
368
369 Show current, custom, or default keyword template maps and their
369 Show current, custom, or default keyword template maps and their
370 expansions.
370 expansions.
371
371
372 Extend the current configuration by specifying maps as arguments
372 Extend the current configuration by specifying maps as arguments
373 and using -f/--rcfile to source an external hgrc file.
373 and using -f/--rcfile to source an external hgrc file.
374
374
375 Use -d/--default to disable current configuration.
375 Use -d/--default to disable current configuration.
376
376
377 See :hg:`help templates` for information on templates and filters.
377 See :hg:`help templates` for information on templates and filters.
378 '''
378 '''
379 def demoitems(section, items):
379 def demoitems(section, items):
380 ui.write('[%s]\n' % section)
380 ui.write('[%s]\n' % section)
381 for k, v in sorted(items):
381 for k, v in sorted(items):
382 ui.write('%s = %s\n' % (k, v))
382 ui.write('%s = %s\n' % (k, v))
383
383
384 fn = 'demo.txt'
384 fn = 'demo.txt'
385 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
385 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
386 ui.note(_('creating temporary repository at %s\n') % tmpdir)
386 ui.note(_('creating temporary repository at %s\n') % tmpdir)
387 repo = localrepo.localrepository(repo.baseui, tmpdir, True)
387 repo = localrepo.localrepository(repo.baseui, tmpdir, True)
388 ui.setconfig('keyword', fn, '')
388 ui.setconfig('keyword', fn, '')
389 svn = ui.configbool('keywordset', 'svn')
389 svn = ui.configbool('keywordset', 'svn')
390 # explicitly set keywordset for demo output
390 # explicitly set keywordset for demo output
391 ui.setconfig('keywordset', 'svn', svn)
391 ui.setconfig('keywordset', 'svn', svn)
392
392
393 uikwmaps = ui.configitems('keywordmaps')
393 uikwmaps = ui.configitems('keywordmaps')
394 if args or opts.get('rcfile'):
394 if args or opts.get('rcfile'):
395 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
395 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
396 if uikwmaps:
396 if uikwmaps:
397 ui.status(_('\textending current template maps\n'))
397 ui.status(_('\textending current template maps\n'))
398 if opts.get('default') or not uikwmaps:
398 if opts.get('default') or not uikwmaps:
399 if svn:
399 if svn:
400 ui.status(_('\toverriding default svn keywordset\n'))
400 ui.status(_('\toverriding default svn keywordset\n'))
401 else:
401 else:
402 ui.status(_('\toverriding default cvs keywordset\n'))
402 ui.status(_('\toverriding default cvs keywordset\n'))
403 if opts.get('rcfile'):
403 if opts.get('rcfile'):
404 ui.readconfig(opts.get('rcfile'))
404 ui.readconfig(opts.get('rcfile'))
405 if args:
405 if args:
406 # simulate hgrc parsing
406 # simulate hgrc parsing
407 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
407 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
408 fp = repo.opener('hgrc', 'w')
408 fp = repo.opener('hgrc', 'w')
409 fp.writelines(rcmaps)
409 fp.writelines(rcmaps)
410 fp.close()
410 fp.close()
411 ui.readconfig(repo.join('hgrc'))
411 ui.readconfig(repo.join('hgrc'))
412 kwmaps = dict(ui.configitems('keywordmaps'))
412 kwmaps = dict(ui.configitems('keywordmaps'))
413 elif opts.get('default'):
413 elif opts.get('default'):
414 if svn:
414 if svn:
415 ui.status(_('\n\tconfiguration using default svn keywordset\n'))
415 ui.status(_('\n\tconfiguration using default svn keywordset\n'))
416 else:
416 else:
417 ui.status(_('\n\tconfiguration using default cvs keywordset\n'))
417 ui.status(_('\n\tconfiguration using default cvs keywordset\n'))
418 kwmaps = _defaultkwmaps(ui)
418 kwmaps = _defaultkwmaps(ui)
419 if uikwmaps:
419 if uikwmaps:
420 ui.status(_('\tdisabling current template maps\n'))
420 ui.status(_('\tdisabling current template maps\n'))
421 for k, v in kwmaps.iteritems():
421 for k, v in kwmaps.iteritems():
422 ui.setconfig('keywordmaps', k, v)
422 ui.setconfig('keywordmaps', k, v)
423 else:
423 else:
424 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
424 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
425 if uikwmaps:
425 if uikwmaps:
426 kwmaps = dict(uikwmaps)
426 kwmaps = dict(uikwmaps)
427 else:
427 else:
428 kwmaps = _defaultkwmaps(ui)
428 kwmaps = _defaultkwmaps(ui)
429
429
430 uisetup(ui)
430 uisetup(ui)
431 reposetup(ui, repo)
431 reposetup(ui, repo)
432 ui.write('[extensions]\nkeyword =\n')
432 ui.write('[extensions]\nkeyword =\n')
433 demoitems('keyword', ui.configitems('keyword'))
433 demoitems('keyword', ui.configitems('keyword'))
434 demoitems('keywordset', ui.configitems('keywordset'))
434 demoitems('keywordset', ui.configitems('keywordset'))
435 demoitems('keywordmaps', kwmaps.iteritems())
435 demoitems('keywordmaps', kwmaps.iteritems())
436 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
436 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
437 repo.wopener.write(fn, keywords)
437 repo.wopener.write(fn, keywords)
438 repo[None].add([fn])
438 repo[None].add([fn])
439 ui.note(_('\nkeywords written to %s:\n') % fn)
439 ui.note(_('\nkeywords written to %s:\n') % fn)
440 ui.note(keywords)
440 ui.note(keywords)
441 wlock = repo.wlock()
441 wlock = repo.wlock()
442 try:
442 try:
443 repo.dirstate.setbranch('demobranch')
443 repo.dirstate.setbranch('demobranch')
444 finally:
444 finally:
445 wlock.release()
445 wlock.release()
446 for name, cmd in ui.configitems('hooks'):
446 for name, cmd in ui.configitems('hooks'):
447 if name.split('.', 1)[0].find('commit') > -1:
447 if name.split('.', 1)[0].find('commit') > -1:
448 repo.ui.setconfig('hooks', name, '')
448 repo.ui.setconfig('hooks', name, '')
449 msg = _('hg keyword configuration and expansion example')
449 msg = _('hg keyword configuration and expansion example')
450 ui.note(("hg ci -m '%s'\n" % msg))
450 ui.note(("hg ci -m '%s'\n" % msg))
451 repo.commit(text=msg)
451 repo.commit(text=msg)
452 ui.status(_('\n\tkeywords expanded\n'))
452 ui.status(_('\n\tkeywords expanded\n'))
453 ui.write(repo.wread(fn))
453 ui.write(repo.wread(fn))
454 shutil.rmtree(tmpdir, ignore_errors=True)
454 shutil.rmtree(tmpdir, ignore_errors=True)
455
455
456 @command('kwexpand', commands.walkopts, _('hg kwexpand [OPTION]... [FILE]...'))
456 @command('kwexpand', commands.walkopts, _('hg kwexpand [OPTION]... [FILE]...'))
457 def expand(ui, repo, *pats, **opts):
457 def expand(ui, repo, *pats, **opts):
458 '''expand keywords in the working directory
458 '''expand keywords in the working directory
459
459
460 Run after (re)enabling keyword expansion.
460 Run after (re)enabling keyword expansion.
461
461
462 kwexpand refuses to run if given files contain local changes.
462 kwexpand refuses to run if given files contain local changes.
463 '''
463 '''
464 # 3rd argument sets expansion to True
464 # 3rd argument sets expansion to True
465 _kwfwrite(ui, repo, True, *pats, **opts)
465 _kwfwrite(ui, repo, True, *pats, **opts)
466
466
467 @command('kwfiles',
467 @command('kwfiles',
468 [('A', 'all', None, _('show keyword status flags of all files')),
468 [('A', 'all', None, _('show keyword status flags of all files')),
469 ('i', 'ignore', None, _('show files excluded from expansion')),
469 ('i', 'ignore', None, _('show files excluded from expansion')),
470 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
470 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
471 ] + commands.walkopts,
471 ] + commands.walkopts,
472 _('hg kwfiles [OPTION]... [FILE]...'))
472 _('hg kwfiles [OPTION]... [FILE]...'))
473 def files(ui, repo, *pats, **opts):
473 def files(ui, repo, *pats, **opts):
474 '''show files configured for keyword expansion
474 '''show files configured for keyword expansion
475
475
476 List which files in the working directory are matched by the
476 List which files in the working directory are matched by the
477 [keyword] configuration patterns.
477 [keyword] configuration patterns.
478
478
479 Useful to prevent inadvertent keyword expansion and to speed up
479 Useful to prevent inadvertent keyword expansion and to speed up
480 execution by including only files that are actual candidates for
480 execution by including only files that are actual candidates for
481 expansion.
481 expansion.
482
482
483 See :hg:`help keyword` on how to construct patterns both for
483 See :hg:`help keyword` on how to construct patterns both for
484 inclusion and exclusion of files.
484 inclusion and exclusion of files.
485
485
486 With -A/--all and -v/--verbose the codes used to show the status
486 With -A/--all and -v/--verbose the codes used to show the status
487 of files are::
487 of files are::
488
488
489 K = keyword expansion candidate
489 K = keyword expansion candidate
490 k = keyword expansion candidate (not tracked)
490 k = keyword expansion candidate (not tracked)
491 I = ignored
491 I = ignored
492 i = ignored (not tracked)
492 i = ignored (not tracked)
493 '''
493 '''
494 kwt = kwtools['templater']
494 kwt = kwtools['templater']
495 wctx = repo[None]
495 wctx = repo[None]
496 status = _status(ui, repo, wctx, kwt, *pats, **opts)
496 status = _status(ui, repo, wctx, kwt, *pats, **opts)
497 cwd = pats and repo.getcwd() or ''
497 cwd = pats and repo.getcwd() or ''
498 modified, added, removed, deleted, unknown, ignored, clean = status
498 modified, added, removed, deleted, unknown, ignored, clean = status
499 files = []
499 files = []
500 if not opts.get('unknown') or opts.get('all'):
500 if not opts.get('unknown') or opts.get('all'):
501 files = sorted(modified + added + clean)
501 files = sorted(modified + added + clean)
502 kwfiles = kwt.iskwfile(files, wctx)
502 kwfiles = kwt.iskwfile(files, wctx)
503 kwdeleted = kwt.iskwfile(deleted, wctx)
503 kwdeleted = kwt.iskwfile(deleted, wctx)
504 kwunknown = kwt.iskwfile(unknown, wctx)
504 kwunknown = kwt.iskwfile(unknown, wctx)
505 if not opts.get('ignore') or opts.get('all'):
505 if not opts.get('ignore') or opts.get('all'):
506 showfiles = kwfiles, kwdeleted, kwunknown
506 showfiles = kwfiles, kwdeleted, kwunknown
507 else:
507 else:
508 showfiles = [], [], []
508 showfiles = [], [], []
509 if opts.get('all') or opts.get('ignore'):
509 if opts.get('all') or opts.get('ignore'):
510 showfiles += ([f for f in files if f not in kwfiles],
510 showfiles += ([f for f in files if f not in kwfiles],
511 [f for f in unknown if f not in kwunknown])
511 [f for f in unknown if f not in kwunknown])
512 kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
512 kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
513 kwstates = zip(kwlabels, 'K!kIi', showfiles)
513 kwstates = zip(kwlabels, 'K!kIi', showfiles)
514 fm = ui.formatter('kwfiles', opts)
514 fm = ui.formatter('kwfiles', opts)
515 fmt = '%.0s%s\n'
515 fmt = '%.0s%s\n'
516 if opts.get('all') or ui.verbose:
516 if opts.get('all') or ui.verbose:
517 fmt = '%s %s\n'
517 fmt = '%s %s\n'
518 for kwstate, char, filenames in kwstates:
518 for kwstate, char, filenames in kwstates:
519 label = 'kwfiles.' + kwstate
519 label = 'kwfiles.' + kwstate
520 for f in filenames:
520 for f in filenames:
521 fm.startitem()
521 fm.startitem()
522 fm.write('kwstatus path', fmt, char,
522 fm.write('kwstatus path', fmt, char,
523 repo.pathto(f, cwd), label=label)
523 repo.pathto(f, cwd), label=label)
524 fm.end()
524 fm.end()
525
525
526 @command('kwshrink', commands.walkopts, _('hg kwshrink [OPTION]... [FILE]...'))
526 @command('kwshrink', commands.walkopts, _('hg kwshrink [OPTION]... [FILE]...'))
527 def shrink(ui, repo, *pats, **opts):
527 def shrink(ui, repo, *pats, **opts):
528 '''revert expanded keywords in the working directory
528 '''revert expanded keywords in the working directory
529
529
530 Must be run before changing/disabling active keywords.
530 Must be run before changing/disabling active keywords.
531
531
532 kwshrink refuses to run if given files contain local changes.
532 kwshrink refuses to run if given files contain local changes.
533 '''
533 '''
534 # 3rd argument sets expansion to False
534 # 3rd argument sets expansion to False
535 _kwfwrite(ui, repo, False, *pats, **opts)
535 _kwfwrite(ui, repo, False, *pats, **opts)
536
536
537
537
538 def uisetup(ui):
538 def uisetup(ui):
539 ''' Monkeypatches dispatch._parse to retrieve user command.'''
539 ''' Monkeypatches dispatch._parse to retrieve user command.'''
540
540
541 def kwdispatch_parse(orig, ui, args):
541 def kwdispatch_parse(orig, ui, args):
542 '''Monkeypatch dispatch._parse to obtain running hg command.'''
542 '''Monkeypatch dispatch._parse to obtain running hg command.'''
543 cmd, func, args, options, cmdoptions = orig(ui, args)
543 cmd, func, args, options, cmdoptions = orig(ui, args)
544 kwtools['hgcmd'] = cmd
544 kwtools['hgcmd'] = cmd
545 return cmd, func, args, options, cmdoptions
545 return cmd, func, args, options, cmdoptions
546
546
547 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
547 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
548
548
549 def reposetup(ui, repo):
549 def reposetup(ui, repo):
550 '''Sets up repo as kwrepo for keyword substitution.
550 '''Sets up repo as kwrepo for keyword substitution.
551 Overrides file method to return kwfilelog instead of filelog
551 Overrides file method to return kwfilelog instead of filelog
552 if file matches user configuration.
552 if file matches user configuration.
553 Wraps commit to overwrite configured files with updated
553 Wraps commit to overwrite configured files with updated
554 keyword substitutions.
554 keyword substitutions.
555 Monkeypatches patch and webcommands.'''
555 Monkeypatches patch and webcommands.'''
556
556
557 try:
557 try:
558 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
558 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
559 or '.hg' in util.splitpath(repo.root)
559 or '.hg' in util.splitpath(repo.root)
560 or repo._url.startswith('bundle:')):
560 or repo._url.startswith('bundle:')):
561 return
561 return
562 except AttributeError:
562 except AttributeError:
563 pass
563 pass
564
564
565 inc, exc = [], ['.hg*']
565 inc, exc = [], ['.hg*']
566 for pat, opt in ui.configitems('keyword'):
566 for pat, opt in ui.configitems('keyword'):
567 if opt != 'ignore':
567 if opt != 'ignore':
568 inc.append(pat)
568 inc.append(pat)
569 else:
569 else:
570 exc.append(pat)
570 exc.append(pat)
571 if not inc:
571 if not inc:
572 return
572 return
573
573
574 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
574 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
575
575
576 class kwrepo(repo.__class__):
576 class kwrepo(repo.__class__):
577 def file(self, f):
577 def file(self, f):
578 if f[0] == '/':
578 if f[0] == '/':
579 f = f[1:]
579 f = f[1:]
580 return kwfilelog(self.sopener, kwt, f)
580 return kwfilelog(self.sopener, kwt, f)
581
581
582 def wread(self, filename):
582 def wread(self, filename):
583 data = super(kwrepo, self).wread(filename)
583 data = super(kwrepo, self).wread(filename)
584 return kwt.wread(filename, data)
584 return kwt.wread(filename, data)
585
585
586 def commit(self, *args, **opts):
586 def commit(self, *args, **opts):
587 # use custom commitctx for user commands
587 # use custom commitctx for user commands
588 # other extensions can still wrap repo.commitctx directly
588 # other extensions can still wrap repo.commitctx directly
589 self.commitctx = self.kwcommitctx
589 self.commitctx = self.kwcommitctx
590 try:
590 try:
591 return super(kwrepo, self).commit(*args, **opts)
591 return super(kwrepo, self).commit(*args, **opts)
592 finally:
592 finally:
593 del self.commitctx
593 del self.commitctx
594
594
595 def kwcommitctx(self, ctx, error=False):
595 def kwcommitctx(self, ctx, error=False):
596 n = super(kwrepo, self).commitctx(ctx, error)
596 n = super(kwrepo, self).commitctx(ctx, error)
597 # no lock needed, only called from repo.commit() which already locks
597 # no lock needed, only called from repo.commit() which already locks
598 if not kwt.postcommit:
598 if not kwt.postcommit:
599 restrict = kwt.restrict
599 restrict = kwt.restrict
600 kwt.restrict = True
600 kwt.restrict = True
601 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
601 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
602 False, True)
602 False, True)
603 kwt.restrict = restrict
603 kwt.restrict = restrict
604 return n
604 return n
605
605
606 def rollback(self, dryrun=False, force=False):
606 def rollback(self, dryrun=False, force=False):
607 wlock = self.wlock()
607 wlock = self.wlock()
608 try:
608 try:
609 if not dryrun:
609 if not dryrun:
610 changed = self['.'].files()
610 changed = self['.'].files()
611 ret = super(kwrepo, self).rollback(dryrun, force)
611 ret = super(kwrepo, self).rollback(dryrun, force)
612 if not dryrun:
612 if not dryrun:
613 ctx = self['.']
613 ctx = self['.']
614 modified, added = _preselect(self[None].status(), changed)
614 modified, added = _preselect(self[None].status(), changed)
615 kwt.overwrite(ctx, modified, True, True)
615 kwt.overwrite(ctx, modified, True, True)
616 kwt.overwrite(ctx, added, True, False)
616 kwt.overwrite(ctx, added, True, False)
617 return ret
617 return ret
618 finally:
618 finally:
619 wlock.release()
619 wlock.release()
620
620
621 # monkeypatches
621 # monkeypatches
622 def kwpatchfile_init(orig, self, ui, gp, backend, store, eolmode=None):
622 def kwpatchfile_init(orig, self, ui, gp, backend, store, eolmode=None):
623 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
623 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
624 rejects or conflicts due to expanded keywords in working dir.'''
624 rejects or conflicts due to expanded keywords in working dir.'''
625 orig(self, ui, gp, backend, store, eolmode)
625 orig(self, ui, gp, backend, store, eolmode)
626 # shrink keywords read from working dir
626 # shrink keywords read from working dir
627 self.lines = kwt.shrinklines(self.fname, self.lines)
627 self.lines = kwt.shrinklines(self.fname, self.lines)
628
628
629 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
629 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
630 opts=None, prefix=''):
630 opts=None, prefix=''):
631 '''Monkeypatch patch.diff to avoid expansion.'''
631 '''Monkeypatch patch.diff to avoid expansion.'''
632 kwt.restrict = True
632 kwt.restrict = True
633 return orig(repo, node1, node2, match, changes, opts, prefix)
633 return orig(repo, node1, node2, match, changes, opts, prefix)
634
634
635 def kwweb_skip(orig, web, req, tmpl):
635 def kwweb_skip(orig, web, req, tmpl):
636 '''Wraps webcommands.x turning off keyword expansion.'''
636 '''Wraps webcommands.x turning off keyword expansion.'''
637 kwt.match = util.never
637 kwt.match = util.never
638 return orig(web, req, tmpl)
638 return orig(web, req, tmpl)
639
639
640 def kw_amend(orig, ui, repo, commitfunc, old, extra, pats, opts):
640 def kw_amend(orig, ui, repo, commitfunc, old, extra, pats, opts):
641 '''Wraps cmdutil.amend expanding keywords after amend.'''
641 '''Wraps cmdutil.amend expanding keywords after amend.'''
642 wlock = repo.wlock()
642 wlock = repo.wlock()
643 try:
643 try:
644 kwt.postcommit = True
644 kwt.postcommit = True
645 newid = orig(ui, repo, commitfunc, old, extra, pats, opts)
645 newid = orig(ui, repo, commitfunc, old, extra, pats, opts)
646 if newid != old.node():
646 if newid != old.node():
647 ctx = repo[newid]
647 ctx = repo[newid]
648 kwt.restrict = True
648 kwt.restrict = True
649 kwt.overwrite(ctx, ctx.files(), False, True)
649 kwt.overwrite(ctx, ctx.files(), False, True)
650 kwt.restrict = False
650 kwt.restrict = False
651 return newid
651 return newid
652 finally:
652 finally:
653 wlock.release()
653 wlock.release()
654
654
655 def kw_copy(orig, ui, repo, pats, opts, rename=False):
655 def kw_copy(orig, ui, repo, pats, opts, rename=False):
656 '''Wraps cmdutil.copy so that copy/rename destinations do not
656 '''Wraps cmdutil.copy so that copy/rename destinations do not
657 contain expanded keywords.
657 contain expanded keywords.
658 Note that the source of a regular file destination may also be a
658 Note that the source of a regular file destination may also be a
659 symlink:
659 symlink:
660 hg cp sym x -> x is symlink
660 hg cp sym x -> x is symlink
661 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords)
661 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords)
662 For the latter we have to follow the symlink to find out whether its
662 For the latter we have to follow the symlink to find out whether its
663 target is configured for expansion and we therefore must unexpand the
663 target is configured for expansion and we therefore must unexpand the
664 keywords in the destination.'''
664 keywords in the destination.'''
665 wlock = repo.wlock()
665 wlock = repo.wlock()
666 try:
666 try:
667 orig(ui, repo, pats, opts, rename)
667 orig(ui, repo, pats, opts, rename)
668 if opts.get('dry_run'):
668 if opts.get('dry_run'):
669 return
669 return
670 wctx = repo[None]
670 wctx = repo[None]
671 cwd = repo.getcwd()
671 cwd = repo.getcwd()
672
672
673 def haskwsource(dest):
673 def haskwsource(dest):
674 '''Returns true if dest is a regular file and configured for
674 '''Returns true if dest is a regular file and configured for
675 expansion or a symlink which points to a file configured for
675 expansion or a symlink which points to a file configured for
676 expansion. '''
676 expansion. '''
677 source = repo.dirstate.copied(dest)
677 source = repo.dirstate.copied(dest)
678 if 'l' in wctx.flags(source):
678 if 'l' in wctx.flags(source):
679 source = pathutil.canonpath(repo.root, cwd,
679 source = pathutil.canonpath(repo.root, cwd,
680 os.path.realpath(source))
680 os.path.realpath(source))
681 return kwt.match(source)
681 return kwt.match(source)
682
682
683 candidates = [f for f in repo.dirstate.copies() if
683 candidates = [f for f in repo.dirstate.copies() if
684 'l' not in wctx.flags(f) and haskwsource(f)]
684 'l' not in wctx.flags(f) and haskwsource(f)]
685 kwt.overwrite(wctx, candidates, False, False)
685 kwt.overwrite(wctx, candidates, False, False)
686 finally:
686 finally:
687 wlock.release()
687 wlock.release()
688
688
689 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
689 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
690 '''Wraps record.dorecord expanding keywords after recording.'''
690 '''Wraps record.dorecord expanding keywords after recording.'''
691 wlock = repo.wlock()
691 wlock = repo.wlock()
692 try:
692 try:
693 # record returns 0 even when nothing has changed
693 # record returns 0 even when nothing has changed
694 # therefore compare nodes before and after
694 # therefore compare nodes before and after
695 kwt.postcommit = True
695 kwt.postcommit = True
696 ctx = repo['.']
696 ctx = repo['.']
697 wstatus = repo[None].status()
697 wstatus = repo[None].status()
698 ret = orig(ui, repo, commitfunc, *pats, **opts)
698 ret = orig(ui, repo, commitfunc, *pats, **opts)
699 recctx = repo['.']
699 recctx = repo['.']
700 if ctx != recctx:
700 if ctx != recctx:
701 modified, added = _preselect(wstatus, recctx.files())
701 modified, added = _preselect(wstatus, recctx.files())
702 kwt.restrict = False
702 kwt.restrict = False
703 kwt.overwrite(recctx, modified, False, True)
703 kwt.overwrite(recctx, modified, False, True)
704 kwt.overwrite(recctx, added, False, True, True)
704 kwt.overwrite(recctx, added, False, True, True)
705 kwt.restrict = True
705 kwt.restrict = True
706 return ret
706 return ret
707 finally:
707 finally:
708 wlock.release()
708 wlock.release()
709
709
710 def kwfilectx_cmp(orig, self, fctx):
710 def kwfilectx_cmp(orig, self, fctx):
711 # keyword affects data size, comparing wdir and filelog size does
711 # keyword affects data size, comparing wdir and filelog size does
712 # not make sense
712 # not make sense
713 if (fctx._filerev is None and
713 if (fctx._filerev is None and
714 (self._repo._encodefilterpats or
714 (self._repo._encodefilterpats or
715 kwt.match(fctx.path()) and 'l' not in fctx.flags() or
715 kwt.match(fctx.path()) and 'l' not in fctx.flags() or
716 self.size() - 4 == fctx.size()) or
716 self.size() - 4 == fctx.size()) or
717 self.size() == fctx.size()):
717 self.size() == fctx.size()):
718 return self._filelog.cmp(self._filenode, fctx.data())
718 return self._filelog.cmp(self._filenode, fctx.data())
719 return True
719 return True
720
720
721 extensions.wrapfunction(context.filectx, 'cmp', kwfilectx_cmp)
721 extensions.wrapfunction(context.filectx, 'cmp', kwfilectx_cmp)
722 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
722 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
723 extensions.wrapfunction(patch, 'diff', kw_diff)
723 extensions.wrapfunction(patch, 'diff', kw_diff)
724 extensions.wrapfunction(cmdutil, 'amend', kw_amend)
724 extensions.wrapfunction(cmdutil, 'amend', kw_amend)
725 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
725 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
726 for c in 'annotate changeset rev filediff diff'.split():
726 for c in 'annotate changeset rev filediff diff'.split():
727 extensions.wrapfunction(webcommands, c, kwweb_skip)
727 extensions.wrapfunction(webcommands, c, kwweb_skip)
728 for name in recordextensions.split():
728 for name in recordextensions.split():
729 try:
729 try:
730 record = extensions.find(name)
730 record = extensions.find(name)
731 extensions.wrapfunction(record, 'dorecord', kw_dorecord)
731 extensions.wrapfunction(record, 'dorecord', kw_dorecord)
732 except KeyError:
732 except KeyError:
733 pass
733 pass
734
734
735 repo.__class__ = kwrepo
735 repo.__class__ = kwrepo
@@ -1,2315 +1,2348 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 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 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, tempfile
10 import os, sys, errno, re, tempfile
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 import match as matchmod
12 import match as matchmod
13 import context, repair, graphmod, revset, phases, obsolete, pathutil
13 import context, repair, graphmod, revset, phases, obsolete, pathutil
14 import changelog
14 import changelog
15 import bookmarks
15 import bookmarks
16 import lock as lockmod
16 import lock as lockmod
17
17
18 def parsealiases(cmd):
18 def parsealiases(cmd):
19 return cmd.lstrip("^").split("|")
19 return cmd.lstrip("^").split("|")
20
20
21 def findpossible(cmd, table, strict=False):
21 def findpossible(cmd, table, strict=False):
22 """
22 """
23 Return cmd -> (aliases, command table entry)
23 Return cmd -> (aliases, command table entry)
24 for each matching command.
24 for each matching command.
25 Return debug commands (or their aliases) only if no normal command matches.
25 Return debug commands (or their aliases) only if no normal command matches.
26 """
26 """
27 choice = {}
27 choice = {}
28 debugchoice = {}
28 debugchoice = {}
29
29
30 if cmd in table:
30 if cmd in table:
31 # short-circuit exact matches, "log" alias beats "^log|history"
31 # short-circuit exact matches, "log" alias beats "^log|history"
32 keys = [cmd]
32 keys = [cmd]
33 else:
33 else:
34 keys = table.keys()
34 keys = table.keys()
35
35
36 for e in keys:
36 for e in keys:
37 aliases = parsealiases(e)
37 aliases = parsealiases(e)
38 found = None
38 found = None
39 if cmd in aliases:
39 if cmd in aliases:
40 found = cmd
40 found = cmd
41 elif not strict:
41 elif not strict:
42 for a in aliases:
42 for a in aliases:
43 if a.startswith(cmd):
43 if a.startswith(cmd):
44 found = a
44 found = a
45 break
45 break
46 if found is not None:
46 if found is not None:
47 if aliases[0].startswith("debug") or found.startswith("debug"):
47 if aliases[0].startswith("debug") or found.startswith("debug"):
48 debugchoice[found] = (aliases, table[e])
48 debugchoice[found] = (aliases, table[e])
49 else:
49 else:
50 choice[found] = (aliases, table[e])
50 choice[found] = (aliases, table[e])
51
51
52 if not choice and debugchoice:
52 if not choice and debugchoice:
53 choice = debugchoice
53 choice = debugchoice
54
54
55 return choice
55 return choice
56
56
57 def findcmd(cmd, table, strict=True):
57 def findcmd(cmd, table, strict=True):
58 """Return (aliases, command table entry) for command string."""
58 """Return (aliases, command table entry) for command string."""
59 choice = findpossible(cmd, table, strict)
59 choice = findpossible(cmd, table, strict)
60
60
61 if cmd in choice:
61 if cmd in choice:
62 return choice[cmd]
62 return choice[cmd]
63
63
64 if len(choice) > 1:
64 if len(choice) > 1:
65 clist = choice.keys()
65 clist = choice.keys()
66 clist.sort()
66 clist.sort()
67 raise error.AmbiguousCommand(cmd, clist)
67 raise error.AmbiguousCommand(cmd, clist)
68
68
69 if choice:
69 if choice:
70 return choice.values()[0]
70 return choice.values()[0]
71
71
72 raise error.UnknownCommand(cmd)
72 raise error.UnknownCommand(cmd)
73
73
74 def findrepo(p):
74 def findrepo(p):
75 while not os.path.isdir(os.path.join(p, ".hg")):
75 while not os.path.isdir(os.path.join(p, ".hg")):
76 oldp, p = p, os.path.dirname(p)
76 oldp, p = p, os.path.dirname(p)
77 if p == oldp:
77 if p == oldp:
78 return None
78 return None
79
79
80 return p
80 return p
81
81
82 def bailifchanged(repo):
82 def bailifchanged(repo):
83 if repo.dirstate.p2() != nullid:
83 if repo.dirstate.p2() != nullid:
84 raise util.Abort(_('outstanding uncommitted merge'))
84 raise util.Abort(_('outstanding uncommitted merge'))
85 modified, added, removed, deleted = repo.status()[:4]
85 modified, added, removed, deleted = repo.status()[:4]
86 if modified or added or removed or deleted:
86 if modified or added or removed or deleted:
87 raise util.Abort(_('uncommitted changes'))
87 raise util.Abort(_('uncommitted changes'))
88 ctx = repo[None]
88 ctx = repo[None]
89 for s in sorted(ctx.substate):
89 for s in sorted(ctx.substate):
90 if ctx.sub(s).dirty():
90 if ctx.sub(s).dirty():
91 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
91 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
92
92
93 def logmessage(ui, opts):
93 def logmessage(ui, opts):
94 """ get the log message according to -m and -l option """
94 """ get the log message according to -m and -l option """
95 message = opts.get('message')
95 message = opts.get('message')
96 logfile = opts.get('logfile')
96 logfile = opts.get('logfile')
97
97
98 if message and logfile:
98 if message and logfile:
99 raise util.Abort(_('options --message and --logfile are mutually '
99 raise util.Abort(_('options --message and --logfile are mutually '
100 'exclusive'))
100 'exclusive'))
101 if not message and logfile:
101 if not message and logfile:
102 try:
102 try:
103 if logfile == '-':
103 if logfile == '-':
104 message = ui.fin.read()
104 message = ui.fin.read()
105 else:
105 else:
106 message = '\n'.join(util.readfile(logfile).splitlines())
106 message = '\n'.join(util.readfile(logfile).splitlines())
107 except IOError, inst:
107 except IOError, inst:
108 raise util.Abort(_("can't read commit message '%s': %s") %
108 raise util.Abort(_("can't read commit message '%s': %s") %
109 (logfile, inst.strerror))
109 (logfile, inst.strerror))
110 return message
110 return message
111
111
112 def loglimit(opts):
112 def loglimit(opts):
113 """get the log limit according to option -l/--limit"""
113 """get the log limit according to option -l/--limit"""
114 limit = opts.get('limit')
114 limit = opts.get('limit')
115 if limit:
115 if limit:
116 try:
116 try:
117 limit = int(limit)
117 limit = int(limit)
118 except ValueError:
118 except ValueError:
119 raise util.Abort(_('limit must be a positive integer'))
119 raise util.Abort(_('limit must be a positive integer'))
120 if limit <= 0:
120 if limit <= 0:
121 raise util.Abort(_('limit must be positive'))
121 raise util.Abort(_('limit must be positive'))
122 else:
122 else:
123 limit = None
123 limit = None
124 return limit
124 return limit
125
125
126 def makefilename(repo, pat, node, desc=None,
126 def makefilename(repo, pat, node, desc=None,
127 total=None, seqno=None, revwidth=None, pathname=None):
127 total=None, seqno=None, revwidth=None, pathname=None):
128 node_expander = {
128 node_expander = {
129 'H': lambda: hex(node),
129 'H': lambda: hex(node),
130 'R': lambda: str(repo.changelog.rev(node)),
130 'R': lambda: str(repo.changelog.rev(node)),
131 'h': lambda: short(node),
131 'h': lambda: short(node),
132 'm': lambda: re.sub('[^\w]', '_', str(desc))
132 'm': lambda: re.sub('[^\w]', '_', str(desc))
133 }
133 }
134 expander = {
134 expander = {
135 '%': lambda: '%',
135 '%': lambda: '%',
136 'b': lambda: os.path.basename(repo.root),
136 'b': lambda: os.path.basename(repo.root),
137 }
137 }
138
138
139 try:
139 try:
140 if node:
140 if node:
141 expander.update(node_expander)
141 expander.update(node_expander)
142 if node:
142 if node:
143 expander['r'] = (lambda:
143 expander['r'] = (lambda:
144 str(repo.changelog.rev(node)).zfill(revwidth or 0))
144 str(repo.changelog.rev(node)).zfill(revwidth or 0))
145 if total is not None:
145 if total is not None:
146 expander['N'] = lambda: str(total)
146 expander['N'] = lambda: str(total)
147 if seqno is not None:
147 if seqno is not None:
148 expander['n'] = lambda: str(seqno)
148 expander['n'] = lambda: str(seqno)
149 if total is not None and seqno is not None:
149 if total is not None and seqno is not None:
150 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
150 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
151 if pathname is not None:
151 if pathname is not None:
152 expander['s'] = lambda: os.path.basename(pathname)
152 expander['s'] = lambda: os.path.basename(pathname)
153 expander['d'] = lambda: os.path.dirname(pathname) or '.'
153 expander['d'] = lambda: os.path.dirname(pathname) or '.'
154 expander['p'] = lambda: pathname
154 expander['p'] = lambda: pathname
155
155
156 newname = []
156 newname = []
157 patlen = len(pat)
157 patlen = len(pat)
158 i = 0
158 i = 0
159 while i < patlen:
159 while i < patlen:
160 c = pat[i]
160 c = pat[i]
161 if c == '%':
161 if c == '%':
162 i += 1
162 i += 1
163 c = pat[i]
163 c = pat[i]
164 c = expander[c]()
164 c = expander[c]()
165 newname.append(c)
165 newname.append(c)
166 i += 1
166 i += 1
167 return ''.join(newname)
167 return ''.join(newname)
168 except KeyError, inst:
168 except KeyError, inst:
169 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
169 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
170 inst.args[0])
170 inst.args[0])
171
171
172 def makefileobj(repo, pat, node=None, desc=None, total=None,
172 def makefileobj(repo, pat, node=None, desc=None, total=None,
173 seqno=None, revwidth=None, mode='wb', modemap=None,
173 seqno=None, revwidth=None, mode='wb', modemap=None,
174 pathname=None):
174 pathname=None):
175
175
176 writable = mode not in ('r', 'rb')
176 writable = mode not in ('r', 'rb')
177
177
178 if not pat or pat == '-':
178 if not pat or pat == '-':
179 fp = writable and repo.ui.fout or repo.ui.fin
179 fp = writable and repo.ui.fout or repo.ui.fin
180 if util.safehasattr(fp, 'fileno'):
180 if util.safehasattr(fp, 'fileno'):
181 return os.fdopen(os.dup(fp.fileno()), mode)
181 return os.fdopen(os.dup(fp.fileno()), mode)
182 else:
182 else:
183 # if this fp can't be duped properly, return
183 # if this fp can't be duped properly, return
184 # a dummy object that can be closed
184 # a dummy object that can be closed
185 class wrappedfileobj(object):
185 class wrappedfileobj(object):
186 noop = lambda x: None
186 noop = lambda x: None
187 def __init__(self, f):
187 def __init__(self, f):
188 self.f = f
188 self.f = f
189 def __getattr__(self, attr):
189 def __getattr__(self, attr):
190 if attr == 'close':
190 if attr == 'close':
191 return self.noop
191 return self.noop
192 else:
192 else:
193 return getattr(self.f, attr)
193 return getattr(self.f, attr)
194
194
195 return wrappedfileobj(fp)
195 return wrappedfileobj(fp)
196 if util.safehasattr(pat, 'write') and writable:
196 if util.safehasattr(pat, 'write') and writable:
197 return pat
197 return pat
198 if util.safehasattr(pat, 'read') and 'r' in mode:
198 if util.safehasattr(pat, 'read') and 'r' in mode:
199 return pat
199 return pat
200 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
200 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
201 if modemap is not None:
201 if modemap is not None:
202 mode = modemap.get(fn, mode)
202 mode = modemap.get(fn, mode)
203 if mode == 'wb':
203 if mode == 'wb':
204 modemap[fn] = 'ab'
204 modemap[fn] = 'ab'
205 return open(fn, mode)
205 return open(fn, mode)
206
206
207 def openrevlog(repo, cmd, file_, opts):
207 def openrevlog(repo, cmd, file_, opts):
208 """opens the changelog, manifest, a filelog or a given revlog"""
208 """opens the changelog, manifest, a filelog or a given revlog"""
209 cl = opts['changelog']
209 cl = opts['changelog']
210 mf = opts['manifest']
210 mf = opts['manifest']
211 msg = None
211 msg = None
212 if cl and mf:
212 if cl and mf:
213 msg = _('cannot specify --changelog and --manifest at the same time')
213 msg = _('cannot specify --changelog and --manifest at the same time')
214 elif cl or mf:
214 elif cl or mf:
215 if file_:
215 if file_:
216 msg = _('cannot specify filename with --changelog or --manifest')
216 msg = _('cannot specify filename with --changelog or --manifest')
217 elif not repo:
217 elif not repo:
218 msg = _('cannot specify --changelog or --manifest '
218 msg = _('cannot specify --changelog or --manifest '
219 'without a repository')
219 'without a repository')
220 if msg:
220 if msg:
221 raise util.Abort(msg)
221 raise util.Abort(msg)
222
222
223 r = None
223 r = None
224 if repo:
224 if repo:
225 if cl:
225 if cl:
226 r = repo.changelog
226 r = repo.changelog
227 elif mf:
227 elif mf:
228 r = repo.manifest
228 r = repo.manifest
229 elif file_:
229 elif file_:
230 filelog = repo.file(file_)
230 filelog = repo.file(file_)
231 if len(filelog):
231 if len(filelog):
232 r = filelog
232 r = filelog
233 if not r:
233 if not r:
234 if not file_:
234 if not file_:
235 raise error.CommandError(cmd, _('invalid arguments'))
235 raise error.CommandError(cmd, _('invalid arguments'))
236 if not os.path.isfile(file_):
236 if not os.path.isfile(file_):
237 raise util.Abort(_("revlog '%s' not found") % file_)
237 raise util.Abort(_("revlog '%s' not found") % file_)
238 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
238 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
239 file_[:-2] + ".i")
239 file_[:-2] + ".i")
240 return r
240 return r
241
241
242 def copy(ui, repo, pats, opts, rename=False):
242 def copy(ui, repo, pats, opts, rename=False):
243 # called with the repo lock held
243 # called with the repo lock held
244 #
244 #
245 # hgsep => pathname that uses "/" to separate directories
245 # hgsep => pathname that uses "/" to separate directories
246 # ossep => pathname that uses os.sep to separate directories
246 # ossep => pathname that uses os.sep to separate directories
247 cwd = repo.getcwd()
247 cwd = repo.getcwd()
248 targets = {}
248 targets = {}
249 after = opts.get("after")
249 after = opts.get("after")
250 dryrun = opts.get("dry_run")
250 dryrun = opts.get("dry_run")
251 wctx = repo[None]
251 wctx = repo[None]
252
252
253 def walkpat(pat):
253 def walkpat(pat):
254 srcs = []
254 srcs = []
255 badstates = after and '?' or '?r'
255 badstates = after and '?' or '?r'
256 m = scmutil.match(repo[None], [pat], opts, globbed=True)
256 m = scmutil.match(repo[None], [pat], opts, globbed=True)
257 for abs in repo.walk(m):
257 for abs in repo.walk(m):
258 state = repo.dirstate[abs]
258 state = repo.dirstate[abs]
259 rel = m.rel(abs)
259 rel = m.rel(abs)
260 exact = m.exact(abs)
260 exact = m.exact(abs)
261 if state in badstates:
261 if state in badstates:
262 if exact and state == '?':
262 if exact and state == '?':
263 ui.warn(_('%s: not copying - file is not managed\n') % rel)
263 ui.warn(_('%s: not copying - file is not managed\n') % rel)
264 if exact and state == 'r':
264 if exact and state == 'r':
265 ui.warn(_('%s: not copying - file has been marked for'
265 ui.warn(_('%s: not copying - file has been marked for'
266 ' remove\n') % rel)
266 ' remove\n') % rel)
267 continue
267 continue
268 # abs: hgsep
268 # abs: hgsep
269 # rel: ossep
269 # rel: ossep
270 srcs.append((abs, rel, exact))
270 srcs.append((abs, rel, exact))
271 return srcs
271 return srcs
272
272
273 # abssrc: hgsep
273 # abssrc: hgsep
274 # relsrc: ossep
274 # relsrc: ossep
275 # otarget: ossep
275 # otarget: ossep
276 def copyfile(abssrc, relsrc, otarget, exact):
276 def copyfile(abssrc, relsrc, otarget, exact):
277 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
277 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
278 if '/' in abstarget:
278 if '/' in abstarget:
279 # We cannot normalize abstarget itself, this would prevent
279 # We cannot normalize abstarget itself, this would prevent
280 # case only renames, like a => A.
280 # case only renames, like a => A.
281 abspath, absname = abstarget.rsplit('/', 1)
281 abspath, absname = abstarget.rsplit('/', 1)
282 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
282 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
283 reltarget = repo.pathto(abstarget, cwd)
283 reltarget = repo.pathto(abstarget, cwd)
284 target = repo.wjoin(abstarget)
284 target = repo.wjoin(abstarget)
285 src = repo.wjoin(abssrc)
285 src = repo.wjoin(abssrc)
286 state = repo.dirstate[abstarget]
286 state = repo.dirstate[abstarget]
287
287
288 scmutil.checkportable(ui, abstarget)
288 scmutil.checkportable(ui, abstarget)
289
289
290 # check for collisions
290 # check for collisions
291 prevsrc = targets.get(abstarget)
291 prevsrc = targets.get(abstarget)
292 if prevsrc is not None:
292 if prevsrc is not None:
293 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
293 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
294 (reltarget, repo.pathto(abssrc, cwd),
294 (reltarget, repo.pathto(abssrc, cwd),
295 repo.pathto(prevsrc, cwd)))
295 repo.pathto(prevsrc, cwd)))
296 return
296 return
297
297
298 # check for overwrites
298 # check for overwrites
299 exists = os.path.lexists(target)
299 exists = os.path.lexists(target)
300 samefile = False
300 samefile = False
301 if exists and abssrc != abstarget:
301 if exists and abssrc != abstarget:
302 if (repo.dirstate.normalize(abssrc) ==
302 if (repo.dirstate.normalize(abssrc) ==
303 repo.dirstate.normalize(abstarget)):
303 repo.dirstate.normalize(abstarget)):
304 if not rename:
304 if not rename:
305 ui.warn(_("%s: can't copy - same file\n") % reltarget)
305 ui.warn(_("%s: can't copy - same file\n") % reltarget)
306 return
306 return
307 exists = False
307 exists = False
308 samefile = True
308 samefile = True
309
309
310 if not after and exists or after and state in 'mn':
310 if not after and exists or after and state in 'mn':
311 if not opts['force']:
311 if not opts['force']:
312 ui.warn(_('%s: not overwriting - file exists\n') %
312 ui.warn(_('%s: not overwriting - file exists\n') %
313 reltarget)
313 reltarget)
314 return
314 return
315
315
316 if after:
316 if after:
317 if not exists:
317 if not exists:
318 if rename:
318 if rename:
319 ui.warn(_('%s: not recording move - %s does not exist\n') %
319 ui.warn(_('%s: not recording move - %s does not exist\n') %
320 (relsrc, reltarget))
320 (relsrc, reltarget))
321 else:
321 else:
322 ui.warn(_('%s: not recording copy - %s does not exist\n') %
322 ui.warn(_('%s: not recording copy - %s does not exist\n') %
323 (relsrc, reltarget))
323 (relsrc, reltarget))
324 return
324 return
325 elif not dryrun:
325 elif not dryrun:
326 try:
326 try:
327 if exists:
327 if exists:
328 os.unlink(target)
328 os.unlink(target)
329 targetdir = os.path.dirname(target) or '.'
329 targetdir = os.path.dirname(target) or '.'
330 if not os.path.isdir(targetdir):
330 if not os.path.isdir(targetdir):
331 os.makedirs(targetdir)
331 os.makedirs(targetdir)
332 if samefile:
332 if samefile:
333 tmp = target + "~hgrename"
333 tmp = target + "~hgrename"
334 os.rename(src, tmp)
334 os.rename(src, tmp)
335 os.rename(tmp, target)
335 os.rename(tmp, target)
336 else:
336 else:
337 util.copyfile(src, target)
337 util.copyfile(src, target)
338 srcexists = True
338 srcexists = True
339 except IOError, inst:
339 except IOError, inst:
340 if inst.errno == errno.ENOENT:
340 if inst.errno == errno.ENOENT:
341 ui.warn(_('%s: deleted in working copy\n') % relsrc)
341 ui.warn(_('%s: deleted in working copy\n') % relsrc)
342 srcexists = False
342 srcexists = False
343 else:
343 else:
344 ui.warn(_('%s: cannot copy - %s\n') %
344 ui.warn(_('%s: cannot copy - %s\n') %
345 (relsrc, inst.strerror))
345 (relsrc, inst.strerror))
346 return True # report a failure
346 return True # report a failure
347
347
348 if ui.verbose or not exact:
348 if ui.verbose or not exact:
349 if rename:
349 if rename:
350 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
350 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
351 else:
351 else:
352 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
352 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
353
353
354 targets[abstarget] = abssrc
354 targets[abstarget] = abssrc
355
355
356 # fix up dirstate
356 # fix up dirstate
357 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
357 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
358 dryrun=dryrun, cwd=cwd)
358 dryrun=dryrun, cwd=cwd)
359 if rename and not dryrun:
359 if rename and not dryrun:
360 if not after and srcexists and not samefile:
360 if not after and srcexists and not samefile:
361 util.unlinkpath(repo.wjoin(abssrc))
361 util.unlinkpath(repo.wjoin(abssrc))
362 wctx.forget([abssrc])
362 wctx.forget([abssrc])
363
363
364 # pat: ossep
364 # pat: ossep
365 # dest ossep
365 # dest ossep
366 # srcs: list of (hgsep, hgsep, ossep, bool)
366 # srcs: list of (hgsep, hgsep, ossep, bool)
367 # return: function that takes hgsep and returns ossep
367 # return: function that takes hgsep and returns ossep
368 def targetpathfn(pat, dest, srcs):
368 def targetpathfn(pat, dest, srcs):
369 if os.path.isdir(pat):
369 if os.path.isdir(pat):
370 abspfx = pathutil.canonpath(repo.root, cwd, pat)
370 abspfx = pathutil.canonpath(repo.root, cwd, pat)
371 abspfx = util.localpath(abspfx)
371 abspfx = util.localpath(abspfx)
372 if destdirexists:
372 if destdirexists:
373 striplen = len(os.path.split(abspfx)[0])
373 striplen = len(os.path.split(abspfx)[0])
374 else:
374 else:
375 striplen = len(abspfx)
375 striplen = len(abspfx)
376 if striplen:
376 if striplen:
377 striplen += len(os.sep)
377 striplen += len(os.sep)
378 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
378 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
379 elif destdirexists:
379 elif destdirexists:
380 res = lambda p: os.path.join(dest,
380 res = lambda p: os.path.join(dest,
381 os.path.basename(util.localpath(p)))
381 os.path.basename(util.localpath(p)))
382 else:
382 else:
383 res = lambda p: dest
383 res = lambda p: dest
384 return res
384 return res
385
385
386 # pat: ossep
386 # pat: ossep
387 # dest ossep
387 # dest ossep
388 # srcs: list of (hgsep, hgsep, ossep, bool)
388 # srcs: list of (hgsep, hgsep, ossep, bool)
389 # return: function that takes hgsep and returns ossep
389 # return: function that takes hgsep and returns ossep
390 def targetpathafterfn(pat, dest, srcs):
390 def targetpathafterfn(pat, dest, srcs):
391 if matchmod.patkind(pat):
391 if matchmod.patkind(pat):
392 # a mercurial pattern
392 # a mercurial pattern
393 res = lambda p: os.path.join(dest,
393 res = lambda p: os.path.join(dest,
394 os.path.basename(util.localpath(p)))
394 os.path.basename(util.localpath(p)))
395 else:
395 else:
396 abspfx = pathutil.canonpath(repo.root, cwd, pat)
396 abspfx = pathutil.canonpath(repo.root, cwd, pat)
397 if len(abspfx) < len(srcs[0][0]):
397 if len(abspfx) < len(srcs[0][0]):
398 # A directory. Either the target path contains the last
398 # A directory. Either the target path contains the last
399 # component of the source path or it does not.
399 # component of the source path or it does not.
400 def evalpath(striplen):
400 def evalpath(striplen):
401 score = 0
401 score = 0
402 for s in srcs:
402 for s in srcs:
403 t = os.path.join(dest, util.localpath(s[0])[striplen:])
403 t = os.path.join(dest, util.localpath(s[0])[striplen:])
404 if os.path.lexists(t):
404 if os.path.lexists(t):
405 score += 1
405 score += 1
406 return score
406 return score
407
407
408 abspfx = util.localpath(abspfx)
408 abspfx = util.localpath(abspfx)
409 striplen = len(abspfx)
409 striplen = len(abspfx)
410 if striplen:
410 if striplen:
411 striplen += len(os.sep)
411 striplen += len(os.sep)
412 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
412 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
413 score = evalpath(striplen)
413 score = evalpath(striplen)
414 striplen1 = len(os.path.split(abspfx)[0])
414 striplen1 = len(os.path.split(abspfx)[0])
415 if striplen1:
415 if striplen1:
416 striplen1 += len(os.sep)
416 striplen1 += len(os.sep)
417 if evalpath(striplen1) > score:
417 if evalpath(striplen1) > score:
418 striplen = striplen1
418 striplen = striplen1
419 res = lambda p: os.path.join(dest,
419 res = lambda p: os.path.join(dest,
420 util.localpath(p)[striplen:])
420 util.localpath(p)[striplen:])
421 else:
421 else:
422 # a file
422 # a file
423 if destdirexists:
423 if destdirexists:
424 res = lambda p: os.path.join(dest,
424 res = lambda p: os.path.join(dest,
425 os.path.basename(util.localpath(p)))
425 os.path.basename(util.localpath(p)))
426 else:
426 else:
427 res = lambda p: dest
427 res = lambda p: dest
428 return res
428 return res
429
429
430
430
431 pats = scmutil.expandpats(pats)
431 pats = scmutil.expandpats(pats)
432 if not pats:
432 if not pats:
433 raise util.Abort(_('no source or destination specified'))
433 raise util.Abort(_('no source or destination specified'))
434 if len(pats) == 1:
434 if len(pats) == 1:
435 raise util.Abort(_('no destination specified'))
435 raise util.Abort(_('no destination specified'))
436 dest = pats.pop()
436 dest = pats.pop()
437 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
437 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
438 if not destdirexists:
438 if not destdirexists:
439 if len(pats) > 1 or matchmod.patkind(pats[0]):
439 if len(pats) > 1 or matchmod.patkind(pats[0]):
440 raise util.Abort(_('with multiple sources, destination must be an '
440 raise util.Abort(_('with multiple sources, destination must be an '
441 'existing directory'))
441 'existing directory'))
442 if util.endswithsep(dest):
442 if util.endswithsep(dest):
443 raise util.Abort(_('destination %s is not a directory') % dest)
443 raise util.Abort(_('destination %s is not a directory') % dest)
444
444
445 tfn = targetpathfn
445 tfn = targetpathfn
446 if after:
446 if after:
447 tfn = targetpathafterfn
447 tfn = targetpathafterfn
448 copylist = []
448 copylist = []
449 for pat in pats:
449 for pat in pats:
450 srcs = walkpat(pat)
450 srcs = walkpat(pat)
451 if not srcs:
451 if not srcs:
452 continue
452 continue
453 copylist.append((tfn(pat, dest, srcs), srcs))
453 copylist.append((tfn(pat, dest, srcs), srcs))
454 if not copylist:
454 if not copylist:
455 raise util.Abort(_('no files to copy'))
455 raise util.Abort(_('no files to copy'))
456
456
457 errors = 0
457 errors = 0
458 for targetpath, srcs in copylist:
458 for targetpath, srcs in copylist:
459 for abssrc, relsrc, exact in srcs:
459 for abssrc, relsrc, exact in srcs:
460 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
460 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
461 errors += 1
461 errors += 1
462
462
463 if errors:
463 if errors:
464 ui.warn(_('(consider using --after)\n'))
464 ui.warn(_('(consider using --after)\n'))
465
465
466 return errors != 0
466 return errors != 0
467
467
468 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
468 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
469 runargs=None, appendpid=False):
469 runargs=None, appendpid=False):
470 '''Run a command as a service.'''
470 '''Run a command as a service.'''
471
471
472 def writepid(pid):
472 def writepid(pid):
473 if opts['pid_file']:
473 if opts['pid_file']:
474 mode = appendpid and 'a' or 'w'
474 mode = appendpid and 'a' or 'w'
475 fp = open(opts['pid_file'], mode)
475 fp = open(opts['pid_file'], mode)
476 fp.write(str(pid) + '\n')
476 fp.write(str(pid) + '\n')
477 fp.close()
477 fp.close()
478
478
479 if opts['daemon'] and not opts['daemon_pipefds']:
479 if opts['daemon'] and not opts['daemon_pipefds']:
480 # Signal child process startup with file removal
480 # Signal child process startup with file removal
481 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
481 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
482 os.close(lockfd)
482 os.close(lockfd)
483 try:
483 try:
484 if not runargs:
484 if not runargs:
485 runargs = util.hgcmd() + sys.argv[1:]
485 runargs = util.hgcmd() + sys.argv[1:]
486 runargs.append('--daemon-pipefds=%s' % lockpath)
486 runargs.append('--daemon-pipefds=%s' % lockpath)
487 # Don't pass --cwd to the child process, because we've already
487 # Don't pass --cwd to the child process, because we've already
488 # changed directory.
488 # changed directory.
489 for i in xrange(1, len(runargs)):
489 for i in xrange(1, len(runargs)):
490 if runargs[i].startswith('--cwd='):
490 if runargs[i].startswith('--cwd='):
491 del runargs[i]
491 del runargs[i]
492 break
492 break
493 elif runargs[i].startswith('--cwd'):
493 elif runargs[i].startswith('--cwd'):
494 del runargs[i:i + 2]
494 del runargs[i:i + 2]
495 break
495 break
496 def condfn():
496 def condfn():
497 return not os.path.exists(lockpath)
497 return not os.path.exists(lockpath)
498 pid = util.rundetached(runargs, condfn)
498 pid = util.rundetached(runargs, condfn)
499 if pid < 0:
499 if pid < 0:
500 raise util.Abort(_('child process failed to start'))
500 raise util.Abort(_('child process failed to start'))
501 writepid(pid)
501 writepid(pid)
502 finally:
502 finally:
503 try:
503 try:
504 os.unlink(lockpath)
504 os.unlink(lockpath)
505 except OSError, e:
505 except OSError, e:
506 if e.errno != errno.ENOENT:
506 if e.errno != errno.ENOENT:
507 raise
507 raise
508 if parentfn:
508 if parentfn:
509 return parentfn(pid)
509 return parentfn(pid)
510 else:
510 else:
511 return
511 return
512
512
513 if initfn:
513 if initfn:
514 initfn()
514 initfn()
515
515
516 if not opts['daemon']:
516 if not opts['daemon']:
517 writepid(os.getpid())
517 writepid(os.getpid())
518
518
519 if opts['daemon_pipefds']:
519 if opts['daemon_pipefds']:
520 lockpath = opts['daemon_pipefds']
520 lockpath = opts['daemon_pipefds']
521 try:
521 try:
522 os.setsid()
522 os.setsid()
523 except AttributeError:
523 except AttributeError:
524 pass
524 pass
525 os.unlink(lockpath)
525 os.unlink(lockpath)
526 util.hidewindow()
526 util.hidewindow()
527 sys.stdout.flush()
527 sys.stdout.flush()
528 sys.stderr.flush()
528 sys.stderr.flush()
529
529
530 nullfd = os.open(os.devnull, os.O_RDWR)
530 nullfd = os.open(os.devnull, os.O_RDWR)
531 logfilefd = nullfd
531 logfilefd = nullfd
532 if logfile:
532 if logfile:
533 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
533 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
534 os.dup2(nullfd, 0)
534 os.dup2(nullfd, 0)
535 os.dup2(logfilefd, 1)
535 os.dup2(logfilefd, 1)
536 os.dup2(logfilefd, 2)
536 os.dup2(logfilefd, 2)
537 if nullfd not in (0, 1, 2):
537 if nullfd not in (0, 1, 2):
538 os.close(nullfd)
538 os.close(nullfd)
539 if logfile and logfilefd not in (0, 1, 2):
539 if logfile and logfilefd not in (0, 1, 2):
540 os.close(logfilefd)
540 os.close(logfilefd)
541
541
542 if runfn:
542 if runfn:
543 return runfn()
543 return runfn()
544
544
545 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
545 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
546 """Utility function used by commands.import to import a single patch
546 """Utility function used by commands.import to import a single patch
547
547
548 This function is explicitly defined here to help the evolve extension to
548 This function is explicitly defined here to help the evolve extension to
549 wrap this part of the import logic.
549 wrap this part of the import logic.
550
550
551 The API is currently a bit ugly because it a simple code translation from
551 The API is currently a bit ugly because it a simple code translation from
552 the import command. Feel free to make it better.
552 the import command. Feel free to make it better.
553
553
554 :hunk: a patch (as a binary string)
554 :hunk: a patch (as a binary string)
555 :parents: nodes that will be parent of the created commit
555 :parents: nodes that will be parent of the created commit
556 :opts: the full dict of option passed to the import command
556 :opts: the full dict of option passed to the import command
557 :msgs: list to save commit message to.
557 :msgs: list to save commit message to.
558 (used in case we need to save it when failing)
558 (used in case we need to save it when failing)
559 :updatefunc: a function that update a repo to a given node
559 :updatefunc: a function that update a repo to a given node
560 updatefunc(<repo>, <node>)
560 updatefunc(<repo>, <node>)
561 """
561 """
562 tmpname, message, user, date, branch, nodeid, p1, p2 = \
562 tmpname, message, user, date, branch, nodeid, p1, p2 = \
563 patch.extract(ui, hunk)
563 patch.extract(ui, hunk)
564
564
565 editor = commiteditor
565 editor = commiteditor
566 if opts.get('edit'):
566 if opts.get('edit'):
567 editor = commitforceeditor
567 editor = commitforceeditor
568 update = not opts.get('bypass')
568 update = not opts.get('bypass')
569 strip = opts["strip"]
569 strip = opts["strip"]
570 sim = float(opts.get('similarity') or 0)
570 sim = float(opts.get('similarity') or 0)
571 if not tmpname:
571 if not tmpname:
572 return (None, None)
572 return (None, None)
573 msg = _('applied to working directory')
573 msg = _('applied to working directory')
574
574
575 try:
575 try:
576 cmdline_message = logmessage(ui, opts)
576 cmdline_message = logmessage(ui, opts)
577 if cmdline_message:
577 if cmdline_message:
578 # pickup the cmdline msg
578 # pickup the cmdline msg
579 message = cmdline_message
579 message = cmdline_message
580 elif message:
580 elif message:
581 # pickup the patch msg
581 # pickup the patch msg
582 message = message.strip()
582 message = message.strip()
583 else:
583 else:
584 # launch the editor
584 # launch the editor
585 message = None
585 message = None
586 ui.debug('message:\n%s\n' % message)
586 ui.debug('message:\n%s\n' % message)
587
587
588 if len(parents) == 1:
588 if len(parents) == 1:
589 parents.append(repo[nullid])
589 parents.append(repo[nullid])
590 if opts.get('exact'):
590 if opts.get('exact'):
591 if not nodeid or not p1:
591 if not nodeid or not p1:
592 raise util.Abort(_('not a Mercurial patch'))
592 raise util.Abort(_('not a Mercurial patch'))
593 p1 = repo[p1]
593 p1 = repo[p1]
594 p2 = repo[p2 or nullid]
594 p2 = repo[p2 or nullid]
595 elif p2:
595 elif p2:
596 try:
596 try:
597 p1 = repo[p1]
597 p1 = repo[p1]
598 p2 = repo[p2]
598 p2 = repo[p2]
599 # Without any options, consider p2 only if the
599 # Without any options, consider p2 only if the
600 # patch is being applied on top of the recorded
600 # patch is being applied on top of the recorded
601 # first parent.
601 # first parent.
602 if p1 != parents[0]:
602 if p1 != parents[0]:
603 p1 = parents[0]
603 p1 = parents[0]
604 p2 = repo[nullid]
604 p2 = repo[nullid]
605 except error.RepoError:
605 except error.RepoError:
606 p1, p2 = parents
606 p1, p2 = parents
607 else:
607 else:
608 p1, p2 = parents
608 p1, p2 = parents
609
609
610 n = None
610 n = None
611 if update:
611 if update:
612 if p1 != parents[0]:
612 if p1 != parents[0]:
613 updatefunc(repo, p1.node())
613 updatefunc(repo, p1.node())
614 if p2 != parents[1]:
614 if p2 != parents[1]:
615 repo.setparents(p1.node(), p2.node())
615 repo.setparents(p1.node(), p2.node())
616
616
617 if opts.get('exact') or opts.get('import_branch'):
617 if opts.get('exact') or opts.get('import_branch'):
618 repo.dirstate.setbranch(branch or 'default')
618 repo.dirstate.setbranch(branch or 'default')
619
619
620 files = set()
620 files = set()
621 patch.patch(ui, repo, tmpname, strip=strip, files=files,
621 patch.patch(ui, repo, tmpname, strip=strip, files=files,
622 eolmode=None, similarity=sim / 100.0)
622 eolmode=None, similarity=sim / 100.0)
623 files = list(files)
623 files = list(files)
624 if opts.get('no_commit'):
624 if opts.get('no_commit'):
625 if message:
625 if message:
626 msgs.append(message)
626 msgs.append(message)
627 else:
627 else:
628 if opts.get('exact') or p2:
628 if opts.get('exact') or p2:
629 # If you got here, you either use --force and know what
629 # If you got here, you either use --force and know what
630 # you are doing or used --exact or a merge patch while
630 # you are doing or used --exact or a merge patch while
631 # being updated to its first parent.
631 # being updated to its first parent.
632 m = None
632 m = None
633 else:
633 else:
634 m = scmutil.matchfiles(repo, files or [])
634 m = scmutil.matchfiles(repo, files or [])
635 n = repo.commit(message, opts.get('user') or user,
635 n = repo.commit(message, opts.get('user') or user,
636 opts.get('date') or date, match=m,
636 opts.get('date') or date, match=m,
637 editor=editor)
637 editor=editor)
638 else:
638 else:
639 if opts.get('exact') or opts.get('import_branch'):
639 if opts.get('exact') or opts.get('import_branch'):
640 branch = branch or 'default'
640 branch = branch or 'default'
641 else:
641 else:
642 branch = p1.branch()
642 branch = p1.branch()
643 store = patch.filestore()
643 store = patch.filestore()
644 try:
644 try:
645 files = set()
645 files = set()
646 try:
646 try:
647 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
647 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
648 files, eolmode=None)
648 files, eolmode=None)
649 except patch.PatchError, e:
649 except patch.PatchError, e:
650 raise util.Abort(str(e))
650 raise util.Abort(str(e))
651 memctx = context.makememctx(repo, (p1.node(), p2.node()),
651 memctx = context.makememctx(repo, (p1.node(), p2.node()),
652 message,
652 message,
653 opts.get('user') or user,
653 opts.get('user') or user,
654 opts.get('date') or date,
654 opts.get('date') or date,
655 branch, files, store,
655 branch, files, store,
656 editor=commiteditor)
656 editor=commiteditor)
657 repo.savecommitmessage(memctx.description())
657 repo.savecommitmessage(memctx.description())
658 n = memctx.commit()
658 n = memctx.commit()
659 finally:
659 finally:
660 store.close()
660 store.close()
661 if opts.get('exact') and hex(n) != nodeid:
661 if opts.get('exact') and hex(n) != nodeid:
662 raise util.Abort(_('patch is damaged or loses information'))
662 raise util.Abort(_('patch is damaged or loses information'))
663 if n:
663 if n:
664 # i18n: refers to a short changeset id
664 # i18n: refers to a short changeset id
665 msg = _('created %s') % short(n)
665 msg = _('created %s') % short(n)
666 return (msg, n)
666 return (msg, n)
667 finally:
667 finally:
668 os.unlink(tmpname)
668 os.unlink(tmpname)
669
669
670 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
670 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
671 opts=None):
671 opts=None):
672 '''export changesets as hg patches.'''
672 '''export changesets as hg patches.'''
673
673
674 total = len(revs)
674 total = len(revs)
675 revwidth = max([len(str(rev)) for rev in revs])
675 revwidth = max([len(str(rev)) for rev in revs])
676 filemode = {}
676 filemode = {}
677
677
678 def single(rev, seqno, fp):
678 def single(rev, seqno, fp):
679 ctx = repo[rev]
679 ctx = repo[rev]
680 node = ctx.node()
680 node = ctx.node()
681 parents = [p.node() for p in ctx.parents() if p]
681 parents = [p.node() for p in ctx.parents() if p]
682 branch = ctx.branch()
682 branch = ctx.branch()
683 if switch_parent:
683 if switch_parent:
684 parents.reverse()
684 parents.reverse()
685 prev = (parents and parents[0]) or nullid
685 prev = (parents and parents[0]) or nullid
686
686
687 shouldclose = False
687 shouldclose = False
688 if not fp and len(template) > 0:
688 if not fp and len(template) > 0:
689 desc_lines = ctx.description().rstrip().split('\n')
689 desc_lines = ctx.description().rstrip().split('\n')
690 desc = desc_lines[0] #Commit always has a first line.
690 desc = desc_lines[0] #Commit always has a first line.
691 fp = makefileobj(repo, template, node, desc=desc, total=total,
691 fp = makefileobj(repo, template, node, desc=desc, total=total,
692 seqno=seqno, revwidth=revwidth, mode='wb',
692 seqno=seqno, revwidth=revwidth, mode='wb',
693 modemap=filemode)
693 modemap=filemode)
694 if fp != template:
694 if fp != template:
695 shouldclose = True
695 shouldclose = True
696 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
696 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
697 repo.ui.note("%s\n" % fp.name)
697 repo.ui.note("%s\n" % fp.name)
698
698
699 if not fp:
699 if not fp:
700 write = repo.ui.write
700 write = repo.ui.write
701 else:
701 else:
702 def write(s, **kw):
702 def write(s, **kw):
703 fp.write(s)
703 fp.write(s)
704
704
705
705
706 write("# HG changeset patch\n")
706 write("# HG changeset patch\n")
707 write("# User %s\n" % ctx.user())
707 write("# User %s\n" % ctx.user())
708 write("# Date %d %d\n" % ctx.date())
708 write("# Date %d %d\n" % ctx.date())
709 write("# %s\n" % util.datestr(ctx.date()))
709 write("# %s\n" % util.datestr(ctx.date()))
710 if branch and branch != 'default':
710 if branch and branch != 'default':
711 write("# Branch %s\n" % branch)
711 write("# Branch %s\n" % branch)
712 write("# Node ID %s\n" % hex(node))
712 write("# Node ID %s\n" % hex(node))
713 write("# Parent %s\n" % hex(prev))
713 write("# Parent %s\n" % hex(prev))
714 if len(parents) > 1:
714 if len(parents) > 1:
715 write("# Parent %s\n" % hex(parents[1]))
715 write("# Parent %s\n" % hex(parents[1]))
716 write(ctx.description().rstrip())
716 write(ctx.description().rstrip())
717 write("\n\n")
717 write("\n\n")
718
718
719 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
719 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
720 write(chunk, label=label)
720 write(chunk, label=label)
721
721
722 if shouldclose:
722 if shouldclose:
723 fp.close()
723 fp.close()
724
724
725 for seqno, rev in enumerate(revs):
725 for seqno, rev in enumerate(revs):
726 single(rev, seqno + 1, fp)
726 single(rev, seqno + 1, fp)
727
727
728 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
728 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
729 changes=None, stat=False, fp=None, prefix='',
729 changes=None, stat=False, fp=None, prefix='',
730 listsubrepos=False):
730 listsubrepos=False):
731 '''show diff or diffstat.'''
731 '''show diff or diffstat.'''
732 if fp is None:
732 if fp is None:
733 write = ui.write
733 write = ui.write
734 else:
734 else:
735 def write(s, **kw):
735 def write(s, **kw):
736 fp.write(s)
736 fp.write(s)
737
737
738 if stat:
738 if stat:
739 diffopts = diffopts.copy(context=0)
739 diffopts = diffopts.copy(context=0)
740 width = 80
740 width = 80
741 if not ui.plain():
741 if not ui.plain():
742 width = ui.termwidth()
742 width = ui.termwidth()
743 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
743 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
744 prefix=prefix)
744 prefix=prefix)
745 for chunk, label in patch.diffstatui(util.iterlines(chunks),
745 for chunk, label in patch.diffstatui(util.iterlines(chunks),
746 width=width,
746 width=width,
747 git=diffopts.git):
747 git=diffopts.git):
748 write(chunk, label=label)
748 write(chunk, label=label)
749 else:
749 else:
750 for chunk, label in patch.diffui(repo, node1, node2, match,
750 for chunk, label in patch.diffui(repo, node1, node2, match,
751 changes, diffopts, prefix=prefix):
751 changes, diffopts, prefix=prefix):
752 write(chunk, label=label)
752 write(chunk, label=label)
753
753
754 if listsubrepos:
754 if listsubrepos:
755 ctx1 = repo[node1]
755 ctx1 = repo[node1]
756 ctx2 = repo[node2]
756 ctx2 = repo[node2]
757 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
757 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
758 tempnode2 = node2
758 tempnode2 = node2
759 try:
759 try:
760 if node2 is not None:
760 if node2 is not None:
761 tempnode2 = ctx2.substate[subpath][1]
761 tempnode2 = ctx2.substate[subpath][1]
762 except KeyError:
762 except KeyError:
763 # A subrepo that existed in node1 was deleted between node1 and
763 # A subrepo that existed in node1 was deleted between node1 and
764 # node2 (inclusive). Thus, ctx2's substate won't contain that
764 # node2 (inclusive). Thus, ctx2's substate won't contain that
765 # subpath. The best we can do is to ignore it.
765 # subpath. The best we can do is to ignore it.
766 tempnode2 = None
766 tempnode2 = None
767 submatch = matchmod.narrowmatcher(subpath, match)
767 submatch = matchmod.narrowmatcher(subpath, match)
768 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
768 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
769 stat=stat, fp=fp, prefix=prefix)
769 stat=stat, fp=fp, prefix=prefix)
770
770
771 class changeset_printer(object):
771 class changeset_printer(object):
772 '''show changeset information when templating not requested.'''
772 '''show changeset information when templating not requested.'''
773
773
774 def __init__(self, ui, repo, patch, diffopts, buffered):
774 def __init__(self, ui, repo, patch, diffopts, buffered):
775 self.ui = ui
775 self.ui = ui
776 self.repo = repo
776 self.repo = repo
777 self.buffered = buffered
777 self.buffered = buffered
778 self.patch = patch
778 self.patch = patch
779 self.diffopts = diffopts
779 self.diffopts = diffopts
780 self.header = {}
780 self.header = {}
781 self.hunk = {}
781 self.hunk = {}
782 self.lastheader = None
782 self.lastheader = None
783 self.footer = None
783 self.footer = None
784
784
785 def flush(self, rev):
785 def flush(self, rev):
786 if rev in self.header:
786 if rev in self.header:
787 h = self.header[rev]
787 h = self.header[rev]
788 if h != self.lastheader:
788 if h != self.lastheader:
789 self.lastheader = h
789 self.lastheader = h
790 self.ui.write(h)
790 self.ui.write(h)
791 del self.header[rev]
791 del self.header[rev]
792 if rev in self.hunk:
792 if rev in self.hunk:
793 self.ui.write(self.hunk[rev])
793 self.ui.write(self.hunk[rev])
794 del self.hunk[rev]
794 del self.hunk[rev]
795 return 1
795 return 1
796 return 0
796 return 0
797
797
798 def close(self):
798 def close(self):
799 if self.footer:
799 if self.footer:
800 self.ui.write(self.footer)
800 self.ui.write(self.footer)
801
801
802 def show(self, ctx, copies=None, matchfn=None, **props):
802 def show(self, ctx, copies=None, matchfn=None, **props):
803 if self.buffered:
803 if self.buffered:
804 self.ui.pushbuffer()
804 self.ui.pushbuffer()
805 self._show(ctx, copies, matchfn, props)
805 self._show(ctx, copies, matchfn, props)
806 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
806 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
807 else:
807 else:
808 self._show(ctx, copies, matchfn, props)
808 self._show(ctx, copies, matchfn, props)
809
809
810 def _show(self, ctx, copies, matchfn, props):
810 def _show(self, ctx, copies, matchfn, props):
811 '''show a single changeset or file revision'''
811 '''show a single changeset or file revision'''
812 changenode = ctx.node()
812 changenode = ctx.node()
813 rev = ctx.rev()
813 rev = ctx.rev()
814
814
815 if self.ui.quiet:
815 if self.ui.quiet:
816 self.ui.write("%d:%s\n" % (rev, short(changenode)),
816 self.ui.write("%d:%s\n" % (rev, short(changenode)),
817 label='log.node')
817 label='log.node')
818 return
818 return
819
819
820 log = self.repo.changelog
820 log = self.repo.changelog
821 date = util.datestr(ctx.date())
821 date = util.datestr(ctx.date())
822
822
823 hexfunc = self.ui.debugflag and hex or short
823 hexfunc = self.ui.debugflag and hex or short
824
824
825 parents = [(p, hexfunc(log.node(p)))
825 parents = [(p, hexfunc(log.node(p)))
826 for p in self._meaningful_parentrevs(log, rev)]
826 for p in self._meaningful_parentrevs(log, rev)]
827
827
828 # i18n: column positioning for "hg log"
828 # i18n: column positioning for "hg log"
829 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
829 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
830 label='log.changeset changeset.%s' % ctx.phasestr())
830 label='log.changeset changeset.%s' % ctx.phasestr())
831
831
832 branch = ctx.branch()
832 branch = ctx.branch()
833 # don't show the default branch name
833 # don't show the default branch name
834 if branch != 'default':
834 if branch != 'default':
835 # i18n: column positioning for "hg log"
835 # i18n: column positioning for "hg log"
836 self.ui.write(_("branch: %s\n") % branch,
836 self.ui.write(_("branch: %s\n") % branch,
837 label='log.branch')
837 label='log.branch')
838 for bookmark in self.repo.nodebookmarks(changenode):
838 for bookmark in self.repo.nodebookmarks(changenode):
839 # i18n: column positioning for "hg log"
839 # i18n: column positioning for "hg log"
840 self.ui.write(_("bookmark: %s\n") % bookmark,
840 self.ui.write(_("bookmark: %s\n") % bookmark,
841 label='log.bookmark')
841 label='log.bookmark')
842 for tag in self.repo.nodetags(changenode):
842 for tag in self.repo.nodetags(changenode):
843 # i18n: column positioning for "hg log"
843 # i18n: column positioning for "hg log"
844 self.ui.write(_("tag: %s\n") % tag,
844 self.ui.write(_("tag: %s\n") % tag,
845 label='log.tag')
845 label='log.tag')
846 if self.ui.debugflag and ctx.phase():
846 if self.ui.debugflag and ctx.phase():
847 # i18n: column positioning for "hg log"
847 # i18n: column positioning for "hg log"
848 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
848 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
849 label='log.phase')
849 label='log.phase')
850 for parent in parents:
850 for parent in parents:
851 # i18n: column positioning for "hg log"
851 # i18n: column positioning for "hg log"
852 self.ui.write(_("parent: %d:%s\n") % parent,
852 self.ui.write(_("parent: %d:%s\n") % parent,
853 label='log.parent changeset.%s' % ctx.phasestr())
853 label='log.parent changeset.%s' % ctx.phasestr())
854
854
855 if self.ui.debugflag:
855 if self.ui.debugflag:
856 mnode = ctx.manifestnode()
856 mnode = ctx.manifestnode()
857 # i18n: column positioning for "hg log"
857 # i18n: column positioning for "hg log"
858 self.ui.write(_("manifest: %d:%s\n") %
858 self.ui.write(_("manifest: %d:%s\n") %
859 (self.repo.manifest.rev(mnode), hex(mnode)),
859 (self.repo.manifest.rev(mnode), hex(mnode)),
860 label='ui.debug log.manifest')
860 label='ui.debug log.manifest')
861 # i18n: column positioning for "hg log"
861 # i18n: column positioning for "hg log"
862 self.ui.write(_("user: %s\n") % ctx.user(),
862 self.ui.write(_("user: %s\n") % ctx.user(),
863 label='log.user')
863 label='log.user')
864 # i18n: column positioning for "hg log"
864 # i18n: column positioning for "hg log"
865 self.ui.write(_("date: %s\n") % date,
865 self.ui.write(_("date: %s\n") % date,
866 label='log.date')
866 label='log.date')
867
867
868 if self.ui.debugflag:
868 if self.ui.debugflag:
869 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
869 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
870 for key, value in zip([# i18n: column positioning for "hg log"
870 for key, value in zip([# i18n: column positioning for "hg log"
871 _("files:"),
871 _("files:"),
872 # i18n: column positioning for "hg log"
872 # i18n: column positioning for "hg log"
873 _("files+:"),
873 _("files+:"),
874 # i18n: column positioning for "hg log"
874 # i18n: column positioning for "hg log"
875 _("files-:")], files):
875 _("files-:")], files):
876 if value:
876 if value:
877 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
877 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
878 label='ui.debug log.files')
878 label='ui.debug log.files')
879 elif ctx.files() and self.ui.verbose:
879 elif ctx.files() and self.ui.verbose:
880 # i18n: column positioning for "hg log"
880 # i18n: column positioning for "hg log"
881 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
881 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
882 label='ui.note log.files')
882 label='ui.note log.files')
883 if copies and self.ui.verbose:
883 if copies and self.ui.verbose:
884 copies = ['%s (%s)' % c for c in copies]
884 copies = ['%s (%s)' % c for c in copies]
885 # i18n: column positioning for "hg log"
885 # i18n: column positioning for "hg log"
886 self.ui.write(_("copies: %s\n") % ' '.join(copies),
886 self.ui.write(_("copies: %s\n") % ' '.join(copies),
887 label='ui.note log.copies')
887 label='ui.note log.copies')
888
888
889 extra = ctx.extra()
889 extra = ctx.extra()
890 if extra and self.ui.debugflag:
890 if extra and self.ui.debugflag:
891 for key, value in sorted(extra.items()):
891 for key, value in sorted(extra.items()):
892 # i18n: column positioning for "hg log"
892 # i18n: column positioning for "hg log"
893 self.ui.write(_("extra: %s=%s\n")
893 self.ui.write(_("extra: %s=%s\n")
894 % (key, value.encode('string_escape')),
894 % (key, value.encode('string_escape')),
895 label='ui.debug log.extra')
895 label='ui.debug log.extra')
896
896
897 description = ctx.description().strip()
897 description = ctx.description().strip()
898 if description:
898 if description:
899 if self.ui.verbose:
899 if self.ui.verbose:
900 self.ui.write(_("description:\n"),
900 self.ui.write(_("description:\n"),
901 label='ui.note log.description')
901 label='ui.note log.description')
902 self.ui.write(description,
902 self.ui.write(description,
903 label='ui.note log.description')
903 label='ui.note log.description')
904 self.ui.write("\n\n")
904 self.ui.write("\n\n")
905 else:
905 else:
906 # i18n: column positioning for "hg log"
906 # i18n: column positioning for "hg log"
907 self.ui.write(_("summary: %s\n") %
907 self.ui.write(_("summary: %s\n") %
908 description.splitlines()[0],
908 description.splitlines()[0],
909 label='log.summary')
909 label='log.summary')
910 self.ui.write("\n")
910 self.ui.write("\n")
911
911
912 self.showpatch(changenode, matchfn)
912 self.showpatch(changenode, matchfn)
913
913
914 def showpatch(self, node, matchfn):
914 def showpatch(self, node, matchfn):
915 if not matchfn:
915 if not matchfn:
916 matchfn = self.patch
916 matchfn = self.patch
917 if matchfn:
917 if matchfn:
918 stat = self.diffopts.get('stat')
918 stat = self.diffopts.get('stat')
919 diff = self.diffopts.get('patch')
919 diff = self.diffopts.get('patch')
920 diffopts = patch.diffopts(self.ui, self.diffopts)
920 diffopts = patch.diffopts(self.ui, self.diffopts)
921 prev = self.repo.changelog.parents(node)[0]
921 prev = self.repo.changelog.parents(node)[0]
922 if stat:
922 if stat:
923 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
923 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
924 match=matchfn, stat=True)
924 match=matchfn, stat=True)
925 if diff:
925 if diff:
926 if stat:
926 if stat:
927 self.ui.write("\n")
927 self.ui.write("\n")
928 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
928 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
929 match=matchfn, stat=False)
929 match=matchfn, stat=False)
930 self.ui.write("\n")
930 self.ui.write("\n")
931
931
932 def _meaningful_parentrevs(self, log, rev):
932 def _meaningful_parentrevs(self, log, rev):
933 """Return list of meaningful (or all if debug) parentrevs for rev.
933 """Return list of meaningful (or all if debug) parentrevs for rev.
934
934
935 For merges (two non-nullrev revisions) both parents are meaningful.
935 For merges (two non-nullrev revisions) both parents are meaningful.
936 Otherwise the first parent revision is considered meaningful if it
936 Otherwise the first parent revision is considered meaningful if it
937 is not the preceding revision.
937 is not the preceding revision.
938 """
938 """
939 parents = log.parentrevs(rev)
939 parents = log.parentrevs(rev)
940 if not self.ui.debugflag and parents[1] == nullrev:
940 if not self.ui.debugflag and parents[1] == nullrev:
941 if parents[0] >= rev - 1:
941 if parents[0] >= rev - 1:
942 parents = []
942 parents = []
943 else:
943 else:
944 parents = [parents[0]]
944 parents = [parents[0]]
945 return parents
945 return parents
946
946
947
947
948 class changeset_templater(changeset_printer):
948 class changeset_templater(changeset_printer):
949 '''format changeset information.'''
949 '''format changeset information.'''
950
950
951 def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
951 def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
952 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
952 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
953 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
953 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
954 defaulttempl = {
954 defaulttempl = {
955 'parent': '{rev}:{node|formatnode} ',
955 'parent': '{rev}:{node|formatnode} ',
956 'manifest': '{rev}:{node|formatnode}',
956 'manifest': '{rev}:{node|formatnode}',
957 'file_copy': '{name} ({source})',
957 'file_copy': '{name} ({source})',
958 'extra': '{key}={value|stringescape}'
958 'extra': '{key}={value|stringescape}'
959 }
959 }
960 # filecopy is preserved for compatibility reasons
960 # filecopy is preserved for compatibility reasons
961 defaulttempl['filecopy'] = defaulttempl['file_copy']
961 defaulttempl['filecopy'] = defaulttempl['file_copy']
962 self.t = templater.templater(mapfile, {'formatnode': formatnode},
962 self.t = templater.templater(mapfile, {'formatnode': formatnode},
963 cache=defaulttempl)
963 cache=defaulttempl)
964 if tmpl:
964 if tmpl:
965 self.t.cache['changeset'] = tmpl
965 self.t.cache['changeset'] = tmpl
966
966
967 self.cache = {}
967 self.cache = {}
968
968
969 def _meaningful_parentrevs(self, ctx):
969 def _meaningful_parentrevs(self, ctx):
970 """Return list of meaningful (or all if debug) parentrevs for rev.
970 """Return list of meaningful (or all if debug) parentrevs for rev.
971 """
971 """
972 parents = ctx.parents()
972 parents = ctx.parents()
973 if len(parents) > 1:
973 if len(parents) > 1:
974 return parents
974 return parents
975 if self.ui.debugflag:
975 if self.ui.debugflag:
976 return [parents[0], self.repo['null']]
976 return [parents[0], self.repo['null']]
977 if parents[0].rev() >= ctx.rev() - 1:
977 if parents[0].rev() >= ctx.rev() - 1:
978 return []
978 return []
979 return parents
979 return parents
980
980
981 def _show(self, ctx, copies, matchfn, props):
981 def _show(self, ctx, copies, matchfn, props):
982 '''show a single changeset or file revision'''
982 '''show a single changeset or file revision'''
983
983
984 showlist = templatekw.showlist
984 showlist = templatekw.showlist
985
985
986 # showparents() behaviour depends on ui trace level which
986 # showparents() behaviour depends on ui trace level which
987 # causes unexpected behaviours at templating level and makes
987 # causes unexpected behaviours at templating level and makes
988 # it harder to extract it in a standalone function. Its
988 # it harder to extract it in a standalone function. Its
989 # behaviour cannot be changed so leave it here for now.
989 # behaviour cannot be changed so leave it here for now.
990 def showparents(**args):
990 def showparents(**args):
991 ctx = args['ctx']
991 ctx = args['ctx']
992 parents = [[('rev', p.rev()), ('node', p.hex())]
992 parents = [[('rev', p.rev()), ('node', p.hex())]
993 for p in self._meaningful_parentrevs(ctx)]
993 for p in self._meaningful_parentrevs(ctx)]
994 return showlist('parent', parents, **args)
994 return showlist('parent', parents, **args)
995
995
996 props = props.copy()
996 props = props.copy()
997 props.update(templatekw.keywords)
997 props.update(templatekw.keywords)
998 props['parents'] = showparents
998 props['parents'] = showparents
999 props['templ'] = self.t
999 props['templ'] = self.t
1000 props['ctx'] = ctx
1000 props['ctx'] = ctx
1001 props['repo'] = self.repo
1001 props['repo'] = self.repo
1002 props['revcache'] = {'copies': copies}
1002 props['revcache'] = {'copies': copies}
1003 props['cache'] = self.cache
1003 props['cache'] = self.cache
1004
1004
1005 # find correct templates for current mode
1005 # find correct templates for current mode
1006
1006
1007 tmplmodes = [
1007 tmplmodes = [
1008 (True, None),
1008 (True, None),
1009 (self.ui.verbose, 'verbose'),
1009 (self.ui.verbose, 'verbose'),
1010 (self.ui.quiet, 'quiet'),
1010 (self.ui.quiet, 'quiet'),
1011 (self.ui.debugflag, 'debug'),
1011 (self.ui.debugflag, 'debug'),
1012 ]
1012 ]
1013
1013
1014 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1014 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1015 for mode, postfix in tmplmodes:
1015 for mode, postfix in tmplmodes:
1016 for type in types:
1016 for type in types:
1017 cur = postfix and ('%s_%s' % (type, postfix)) or type
1017 cur = postfix and ('%s_%s' % (type, postfix)) or type
1018 if mode and cur in self.t:
1018 if mode and cur in self.t:
1019 types[type] = cur
1019 types[type] = cur
1020
1020
1021 try:
1021 try:
1022
1022
1023 # write header
1023 # write header
1024 if types['header']:
1024 if types['header']:
1025 h = templater.stringify(self.t(types['header'], **props))
1025 h = templater.stringify(self.t(types['header'], **props))
1026 if self.buffered:
1026 if self.buffered:
1027 self.header[ctx.rev()] = h
1027 self.header[ctx.rev()] = h
1028 else:
1028 else:
1029 if self.lastheader != h:
1029 if self.lastheader != h:
1030 self.lastheader = h
1030 self.lastheader = h
1031 self.ui.write(h)
1031 self.ui.write(h)
1032
1032
1033 # write changeset metadata, then patch if requested
1033 # write changeset metadata, then patch if requested
1034 key = types['changeset']
1034 key = types['changeset']
1035 self.ui.write(templater.stringify(self.t(key, **props)))
1035 self.ui.write(templater.stringify(self.t(key, **props)))
1036 self.showpatch(ctx.node(), matchfn)
1036 self.showpatch(ctx.node(), matchfn)
1037
1037
1038 if types['footer']:
1038 if types['footer']:
1039 if not self.footer:
1039 if not self.footer:
1040 self.footer = templater.stringify(self.t(types['footer'],
1040 self.footer = templater.stringify(self.t(types['footer'],
1041 **props))
1041 **props))
1042
1042
1043 except KeyError, inst:
1043 except KeyError, inst:
1044 msg = _("%s: no key named '%s'")
1044 msg = _("%s: no key named '%s'")
1045 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1045 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1046 except SyntaxError, inst:
1046 except SyntaxError, inst:
1047 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1047 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1048
1048
1049 def gettemplate(ui, tmpl, style):
1049 def gettemplate(ui, tmpl, style):
1050 """
1050 """
1051 Find the template matching the given template spec or style.
1051 Find the template matching the given template spec or style.
1052 """
1052 """
1053
1053
1054 # ui settings
1054 # ui settings
1055 if not tmpl and not style:
1055 if not tmpl and not style:
1056 tmpl = ui.config('ui', 'logtemplate')
1056 tmpl = ui.config('ui', 'logtemplate')
1057 if tmpl:
1057 if tmpl:
1058 try:
1058 try:
1059 tmpl = templater.parsestring(tmpl)
1059 tmpl = templater.parsestring(tmpl)
1060 except SyntaxError:
1060 except SyntaxError:
1061 tmpl = templater.parsestring(tmpl, quoted=False)
1061 tmpl = templater.parsestring(tmpl, quoted=False)
1062 return tmpl, None
1062 else:
1063 else:
1063 style = util.expandpath(ui.config('ui', 'style', ''))
1064 style = util.expandpath(ui.config('ui', 'style', ''))
1064
1065
1065 if style:
1066 if style:
1066 mapfile = style
1067 mapfile = style
1067 if not os.path.split(mapfile)[0]:
1068 if not os.path.split(mapfile)[0]:
1068 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1069 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1069 or templater.templatepath(mapfile))
1070 or templater.templatepath(mapfile))
1070 if mapname:
1071 if mapname:
1071 mapfile = mapname
1072 mapfile = mapname
1072 return None, mapfile
1073 return None, mapfile
1073
1074
1075 if not tmpl:
1076 return None, None
1077
1078 # looks like a literal template?
1079 if '{' in tmpl:
1080 return tmpl, None
1081
1082 # perhaps a stock style?
1083 if not os.path.split(tmpl)[0]:
1084 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1085 or templater.templatepath(tmpl))
1086 if mapname and os.path.isfile(mapname):
1087 return None, mapname
1088
1089 # perhaps it's a reference to [templates]
1090 t = ui.config('templates', tmpl)
1091 if t:
1092 try:
1093 tmpl = templater.parsestring(t)
1094 except SyntaxError:
1095 tmpl = templater.parsestring(t, quoted=False)
1096 return tmpl, None
1097
1098 # perhaps it's a path to a map or a template
1099 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1100 # is it a mapfile for a style?
1101 if os.path.basename(tmpl).startswith("map-"):
1102 return None, os.path.realpath(tmpl)
1103 tmpl = open(tmpl).read()
1104 return tmpl, None
1105
1106 # constant string?
1074 return tmpl, None
1107 return tmpl, None
1075
1108
1076 def show_changeset(ui, repo, opts, buffered=False):
1109 def show_changeset(ui, repo, opts, buffered=False):
1077 """show one changeset using template or regular display.
1110 """show one changeset using template or regular display.
1078
1111
1079 Display format will be the first non-empty hit of:
1112 Display format will be the first non-empty hit of:
1080 1. option 'template'
1113 1. option 'template'
1081 2. option 'style'
1114 2. option 'style'
1082 3. [ui] setting 'logtemplate'
1115 3. [ui] setting 'logtemplate'
1083 4. [ui] setting 'style'
1116 4. [ui] setting 'style'
1084 If all of these values are either the unset or the empty string,
1117 If all of these values are either the unset or the empty string,
1085 regular display via changeset_printer() is done.
1118 regular display via changeset_printer() is done.
1086 """
1119 """
1087 # options
1120 # options
1088 patch = None
1121 patch = None
1089 if opts.get('patch') or opts.get('stat'):
1122 if opts.get('patch') or opts.get('stat'):
1090 patch = scmutil.matchall(repo)
1123 patch = scmutil.matchall(repo)
1091
1124
1092 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1125 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1093
1126
1094 if not tmpl and not mapfile:
1127 if not tmpl and not mapfile:
1095 return changeset_printer(ui, repo, patch, opts, buffered)
1128 return changeset_printer(ui, repo, patch, opts, buffered)
1096
1129
1097 try:
1130 try:
1098 t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
1131 t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
1099 except SyntaxError, inst:
1132 except SyntaxError, inst:
1100 raise util.Abort(inst.args[0])
1133 raise util.Abort(inst.args[0])
1101 return t
1134 return t
1102
1135
1103 def showmarker(ui, marker):
1136 def showmarker(ui, marker):
1104 """utility function to display obsolescence marker in a readable way
1137 """utility function to display obsolescence marker in a readable way
1105
1138
1106 To be used by debug function."""
1139 To be used by debug function."""
1107 ui.write(hex(marker.precnode()))
1140 ui.write(hex(marker.precnode()))
1108 for repl in marker.succnodes():
1141 for repl in marker.succnodes():
1109 ui.write(' ')
1142 ui.write(' ')
1110 ui.write(hex(repl))
1143 ui.write(hex(repl))
1111 ui.write(' %X ' % marker._data[2])
1144 ui.write(' %X ' % marker._data[2])
1112 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1145 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1113 sorted(marker.metadata().items()))))
1146 sorted(marker.metadata().items()))))
1114 ui.write('\n')
1147 ui.write('\n')
1115
1148
1116 def finddate(ui, repo, date):
1149 def finddate(ui, repo, date):
1117 """Find the tipmost changeset that matches the given date spec"""
1150 """Find the tipmost changeset that matches the given date spec"""
1118
1151
1119 df = util.matchdate(date)
1152 df = util.matchdate(date)
1120 m = scmutil.matchall(repo)
1153 m = scmutil.matchall(repo)
1121 results = {}
1154 results = {}
1122
1155
1123 def prep(ctx, fns):
1156 def prep(ctx, fns):
1124 d = ctx.date()
1157 d = ctx.date()
1125 if df(d[0]):
1158 if df(d[0]):
1126 results[ctx.rev()] = d
1159 results[ctx.rev()] = d
1127
1160
1128 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1161 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1129 rev = ctx.rev()
1162 rev = ctx.rev()
1130 if rev in results:
1163 if rev in results:
1131 ui.status(_("found revision %s from %s\n") %
1164 ui.status(_("found revision %s from %s\n") %
1132 (rev, util.datestr(results[rev])))
1165 (rev, util.datestr(results[rev])))
1133 return str(rev)
1166 return str(rev)
1134
1167
1135 raise util.Abort(_("revision matching date not found"))
1168 raise util.Abort(_("revision matching date not found"))
1136
1169
1137 def increasingwindows(windowsize=8, sizelimit=512):
1170 def increasingwindows(windowsize=8, sizelimit=512):
1138 while True:
1171 while True:
1139 yield windowsize
1172 yield windowsize
1140 if windowsize < sizelimit:
1173 if windowsize < sizelimit:
1141 windowsize *= 2
1174 windowsize *= 2
1142
1175
1143 class FileWalkError(Exception):
1176 class FileWalkError(Exception):
1144 pass
1177 pass
1145
1178
1146 def walkfilerevs(repo, match, follow, revs, fncache):
1179 def walkfilerevs(repo, match, follow, revs, fncache):
1147 '''Walks the file history for the matched files.
1180 '''Walks the file history for the matched files.
1148
1181
1149 Returns the changeset revs that are involved in the file history.
1182 Returns the changeset revs that are involved in the file history.
1150
1183
1151 Throws FileWalkError if the file history can't be walked using
1184 Throws FileWalkError if the file history can't be walked using
1152 filelogs alone.
1185 filelogs alone.
1153 '''
1186 '''
1154 wanted = set()
1187 wanted = set()
1155 copies = []
1188 copies = []
1156 minrev, maxrev = min(revs), max(revs)
1189 minrev, maxrev = min(revs), max(revs)
1157 def filerevgen(filelog, last):
1190 def filerevgen(filelog, last):
1158 """
1191 """
1159 Only files, no patterns. Check the history of each file.
1192 Only files, no patterns. Check the history of each file.
1160
1193
1161 Examines filelog entries within minrev, maxrev linkrev range
1194 Examines filelog entries within minrev, maxrev linkrev range
1162 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1195 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1163 tuples in backwards order
1196 tuples in backwards order
1164 """
1197 """
1165 cl_count = len(repo)
1198 cl_count = len(repo)
1166 revs = []
1199 revs = []
1167 for j in xrange(0, last + 1):
1200 for j in xrange(0, last + 1):
1168 linkrev = filelog.linkrev(j)
1201 linkrev = filelog.linkrev(j)
1169 if linkrev < minrev:
1202 if linkrev < minrev:
1170 continue
1203 continue
1171 # only yield rev for which we have the changelog, it can
1204 # only yield rev for which we have the changelog, it can
1172 # happen while doing "hg log" during a pull or commit
1205 # happen while doing "hg log" during a pull or commit
1173 if linkrev >= cl_count:
1206 if linkrev >= cl_count:
1174 break
1207 break
1175
1208
1176 parentlinkrevs = []
1209 parentlinkrevs = []
1177 for p in filelog.parentrevs(j):
1210 for p in filelog.parentrevs(j):
1178 if p != nullrev:
1211 if p != nullrev:
1179 parentlinkrevs.append(filelog.linkrev(p))
1212 parentlinkrevs.append(filelog.linkrev(p))
1180 n = filelog.node(j)
1213 n = filelog.node(j)
1181 revs.append((linkrev, parentlinkrevs,
1214 revs.append((linkrev, parentlinkrevs,
1182 follow and filelog.renamed(n)))
1215 follow and filelog.renamed(n)))
1183
1216
1184 return reversed(revs)
1217 return reversed(revs)
1185 def iterfiles():
1218 def iterfiles():
1186 pctx = repo['.']
1219 pctx = repo['.']
1187 for filename in match.files():
1220 for filename in match.files():
1188 if follow:
1221 if follow:
1189 if filename not in pctx:
1222 if filename not in pctx:
1190 raise util.Abort(_('cannot follow file not in parent '
1223 raise util.Abort(_('cannot follow file not in parent '
1191 'revision: "%s"') % filename)
1224 'revision: "%s"') % filename)
1192 yield filename, pctx[filename].filenode()
1225 yield filename, pctx[filename].filenode()
1193 else:
1226 else:
1194 yield filename, None
1227 yield filename, None
1195 for filename_node in copies:
1228 for filename_node in copies:
1196 yield filename_node
1229 yield filename_node
1197
1230
1198 for file_, node in iterfiles():
1231 for file_, node in iterfiles():
1199 filelog = repo.file(file_)
1232 filelog = repo.file(file_)
1200 if not len(filelog):
1233 if not len(filelog):
1201 if node is None:
1234 if node is None:
1202 # A zero count may be a directory or deleted file, so
1235 # A zero count may be a directory or deleted file, so
1203 # try to find matching entries on the slow path.
1236 # try to find matching entries on the slow path.
1204 if follow:
1237 if follow:
1205 raise util.Abort(
1238 raise util.Abort(
1206 _('cannot follow nonexistent file: "%s"') % file_)
1239 _('cannot follow nonexistent file: "%s"') % file_)
1207 raise FileWalkError("Cannot walk via filelog")
1240 raise FileWalkError("Cannot walk via filelog")
1208 else:
1241 else:
1209 continue
1242 continue
1210
1243
1211 if node is None:
1244 if node is None:
1212 last = len(filelog) - 1
1245 last = len(filelog) - 1
1213 else:
1246 else:
1214 last = filelog.rev(node)
1247 last = filelog.rev(node)
1215
1248
1216
1249
1217 # keep track of all ancestors of the file
1250 # keep track of all ancestors of the file
1218 ancestors = set([filelog.linkrev(last)])
1251 ancestors = set([filelog.linkrev(last)])
1219
1252
1220 # iterate from latest to oldest revision
1253 # iterate from latest to oldest revision
1221 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1254 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1222 if not follow:
1255 if not follow:
1223 if rev > maxrev:
1256 if rev > maxrev:
1224 continue
1257 continue
1225 else:
1258 else:
1226 # Note that last might not be the first interesting
1259 # Note that last might not be the first interesting
1227 # rev to us:
1260 # rev to us:
1228 # if the file has been changed after maxrev, we'll
1261 # if the file has been changed after maxrev, we'll
1229 # have linkrev(last) > maxrev, and we still need
1262 # have linkrev(last) > maxrev, and we still need
1230 # to explore the file graph
1263 # to explore the file graph
1231 if rev not in ancestors:
1264 if rev not in ancestors:
1232 continue
1265 continue
1233 # XXX insert 1327 fix here
1266 # XXX insert 1327 fix here
1234 if flparentlinkrevs:
1267 if flparentlinkrevs:
1235 ancestors.update(flparentlinkrevs)
1268 ancestors.update(flparentlinkrevs)
1236
1269
1237 fncache.setdefault(rev, []).append(file_)
1270 fncache.setdefault(rev, []).append(file_)
1238 wanted.add(rev)
1271 wanted.add(rev)
1239 if copied:
1272 if copied:
1240 copies.append(copied)
1273 copies.append(copied)
1241
1274
1242 return wanted
1275 return wanted
1243
1276
1244 def walkchangerevs(repo, match, opts, prepare):
1277 def walkchangerevs(repo, match, opts, prepare):
1245 '''Iterate over files and the revs in which they changed.
1278 '''Iterate over files and the revs in which they changed.
1246
1279
1247 Callers most commonly need to iterate backwards over the history
1280 Callers most commonly need to iterate backwards over the history
1248 in which they are interested. Doing so has awful (quadratic-looking)
1281 in which they are interested. Doing so has awful (quadratic-looking)
1249 performance, so we use iterators in a "windowed" way.
1282 performance, so we use iterators in a "windowed" way.
1250
1283
1251 We walk a window of revisions in the desired order. Within the
1284 We walk a window of revisions in the desired order. Within the
1252 window, we first walk forwards to gather data, then in the desired
1285 window, we first walk forwards to gather data, then in the desired
1253 order (usually backwards) to display it.
1286 order (usually backwards) to display it.
1254
1287
1255 This function returns an iterator yielding contexts. Before
1288 This function returns an iterator yielding contexts. Before
1256 yielding each context, the iterator will first call the prepare
1289 yielding each context, the iterator will first call the prepare
1257 function on each context in the window in forward order.'''
1290 function on each context in the window in forward order.'''
1258
1291
1259 follow = opts.get('follow') or opts.get('follow_first')
1292 follow = opts.get('follow') or opts.get('follow_first')
1260
1293
1261 if opts.get('rev'):
1294 if opts.get('rev'):
1262 revs = scmutil.revrange(repo, opts.get('rev'))
1295 revs = scmutil.revrange(repo, opts.get('rev'))
1263 elif follow:
1296 elif follow:
1264 revs = repo.revs('reverse(:.)')
1297 revs = repo.revs('reverse(:.)')
1265 else:
1298 else:
1266 revs = revset.baseset(repo)
1299 revs = revset.baseset(repo)
1267 revs.reverse()
1300 revs.reverse()
1268 if not revs:
1301 if not revs:
1269 return []
1302 return []
1270 wanted = set()
1303 wanted = set()
1271 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1304 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1272 fncache = {}
1305 fncache = {}
1273 change = repo.changectx
1306 change = repo.changectx
1274
1307
1275 # First step is to fill wanted, the set of revisions that we want to yield.
1308 # First step is to fill wanted, the set of revisions that we want to yield.
1276 # When it does not induce extra cost, we also fill fncache for revisions in
1309 # When it does not induce extra cost, we also fill fncache for revisions in
1277 # wanted: a cache of filenames that were changed (ctx.files()) and that
1310 # wanted: a cache of filenames that were changed (ctx.files()) and that
1278 # match the file filtering conditions.
1311 # match the file filtering conditions.
1279
1312
1280 if not slowpath and not match.files():
1313 if not slowpath and not match.files():
1281 # No files, no patterns. Display all revs.
1314 # No files, no patterns. Display all revs.
1282 wanted = revs
1315 wanted = revs
1283
1316
1284 if not slowpath and match.files():
1317 if not slowpath and match.files():
1285 # We only have to read through the filelog to find wanted revisions
1318 # We only have to read through the filelog to find wanted revisions
1286
1319
1287 try:
1320 try:
1288 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1321 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1289 except FileWalkError:
1322 except FileWalkError:
1290 slowpath = True
1323 slowpath = True
1291
1324
1292 # We decided to fall back to the slowpath because at least one
1325 # We decided to fall back to the slowpath because at least one
1293 # of the paths was not a file. Check to see if at least one of them
1326 # of the paths was not a file. Check to see if at least one of them
1294 # existed in history, otherwise simply return
1327 # existed in history, otherwise simply return
1295 for path in match.files():
1328 for path in match.files():
1296 if path == '.' or path in repo.store:
1329 if path == '.' or path in repo.store:
1297 break
1330 break
1298 else:
1331 else:
1299 return []
1332 return []
1300
1333
1301 if slowpath:
1334 if slowpath:
1302 # We have to read the changelog to match filenames against
1335 # We have to read the changelog to match filenames against
1303 # changed files
1336 # changed files
1304
1337
1305 if follow:
1338 if follow:
1306 raise util.Abort(_('can only follow copies/renames for explicit '
1339 raise util.Abort(_('can only follow copies/renames for explicit '
1307 'filenames'))
1340 'filenames'))
1308
1341
1309 # The slow path checks files modified in every changeset.
1342 # The slow path checks files modified in every changeset.
1310 # This is really slow on large repos, so compute the set lazily.
1343 # This is really slow on large repos, so compute the set lazily.
1311 class lazywantedset(object):
1344 class lazywantedset(object):
1312 def __init__(self):
1345 def __init__(self):
1313 self.set = set()
1346 self.set = set()
1314 self.revs = set(revs)
1347 self.revs = set(revs)
1315
1348
1316 # No need to worry about locality here because it will be accessed
1349 # No need to worry about locality here because it will be accessed
1317 # in the same order as the increasing window below.
1350 # in the same order as the increasing window below.
1318 def __contains__(self, value):
1351 def __contains__(self, value):
1319 if value in self.set:
1352 if value in self.set:
1320 return True
1353 return True
1321 elif not value in self.revs:
1354 elif not value in self.revs:
1322 return False
1355 return False
1323 else:
1356 else:
1324 self.revs.discard(value)
1357 self.revs.discard(value)
1325 ctx = change(value)
1358 ctx = change(value)
1326 matches = filter(match, ctx.files())
1359 matches = filter(match, ctx.files())
1327 if matches:
1360 if matches:
1328 fncache[value] = matches
1361 fncache[value] = matches
1329 self.set.add(value)
1362 self.set.add(value)
1330 return True
1363 return True
1331 return False
1364 return False
1332
1365
1333 def discard(self, value):
1366 def discard(self, value):
1334 self.revs.discard(value)
1367 self.revs.discard(value)
1335 self.set.discard(value)
1368 self.set.discard(value)
1336
1369
1337 wanted = lazywantedset()
1370 wanted = lazywantedset()
1338
1371
1339 class followfilter(object):
1372 class followfilter(object):
1340 def __init__(self, onlyfirst=False):
1373 def __init__(self, onlyfirst=False):
1341 self.startrev = nullrev
1374 self.startrev = nullrev
1342 self.roots = set()
1375 self.roots = set()
1343 self.onlyfirst = onlyfirst
1376 self.onlyfirst = onlyfirst
1344
1377
1345 def match(self, rev):
1378 def match(self, rev):
1346 def realparents(rev):
1379 def realparents(rev):
1347 if self.onlyfirst:
1380 if self.onlyfirst:
1348 return repo.changelog.parentrevs(rev)[0:1]
1381 return repo.changelog.parentrevs(rev)[0:1]
1349 else:
1382 else:
1350 return filter(lambda x: x != nullrev,
1383 return filter(lambda x: x != nullrev,
1351 repo.changelog.parentrevs(rev))
1384 repo.changelog.parentrevs(rev))
1352
1385
1353 if self.startrev == nullrev:
1386 if self.startrev == nullrev:
1354 self.startrev = rev
1387 self.startrev = rev
1355 return True
1388 return True
1356
1389
1357 if rev > self.startrev:
1390 if rev > self.startrev:
1358 # forward: all descendants
1391 # forward: all descendants
1359 if not self.roots:
1392 if not self.roots:
1360 self.roots.add(self.startrev)
1393 self.roots.add(self.startrev)
1361 for parent in realparents(rev):
1394 for parent in realparents(rev):
1362 if parent in self.roots:
1395 if parent in self.roots:
1363 self.roots.add(rev)
1396 self.roots.add(rev)
1364 return True
1397 return True
1365 else:
1398 else:
1366 # backwards: all parents
1399 # backwards: all parents
1367 if not self.roots:
1400 if not self.roots:
1368 self.roots.update(realparents(self.startrev))
1401 self.roots.update(realparents(self.startrev))
1369 if rev in self.roots:
1402 if rev in self.roots:
1370 self.roots.remove(rev)
1403 self.roots.remove(rev)
1371 self.roots.update(realparents(rev))
1404 self.roots.update(realparents(rev))
1372 return True
1405 return True
1373
1406
1374 return False
1407 return False
1375
1408
1376 # it might be worthwhile to do this in the iterator if the rev range
1409 # it might be worthwhile to do this in the iterator if the rev range
1377 # is descending and the prune args are all within that range
1410 # is descending and the prune args are all within that range
1378 for rev in opts.get('prune', ()):
1411 for rev in opts.get('prune', ()):
1379 rev = repo[rev].rev()
1412 rev = repo[rev].rev()
1380 ff = followfilter()
1413 ff = followfilter()
1381 stop = min(revs[0], revs[-1])
1414 stop = min(revs[0], revs[-1])
1382 for x in xrange(rev, stop - 1, -1):
1415 for x in xrange(rev, stop - 1, -1):
1383 if ff.match(x):
1416 if ff.match(x):
1384 wanted = wanted - [x]
1417 wanted = wanted - [x]
1385
1418
1386 # Now that wanted is correctly initialized, we can iterate over the
1419 # Now that wanted is correctly initialized, we can iterate over the
1387 # revision range, yielding only revisions in wanted.
1420 # revision range, yielding only revisions in wanted.
1388 def iterate():
1421 def iterate():
1389 if follow and not match.files():
1422 if follow and not match.files():
1390 ff = followfilter(onlyfirst=opts.get('follow_first'))
1423 ff = followfilter(onlyfirst=opts.get('follow_first'))
1391 def want(rev):
1424 def want(rev):
1392 return ff.match(rev) and rev in wanted
1425 return ff.match(rev) and rev in wanted
1393 else:
1426 else:
1394 def want(rev):
1427 def want(rev):
1395 return rev in wanted
1428 return rev in wanted
1396
1429
1397 it = iter(revs)
1430 it = iter(revs)
1398 stopiteration = False
1431 stopiteration = False
1399 for windowsize in increasingwindows():
1432 for windowsize in increasingwindows():
1400 nrevs = []
1433 nrevs = []
1401 for i in xrange(windowsize):
1434 for i in xrange(windowsize):
1402 try:
1435 try:
1403 rev = it.next()
1436 rev = it.next()
1404 if want(rev):
1437 if want(rev):
1405 nrevs.append(rev)
1438 nrevs.append(rev)
1406 except (StopIteration):
1439 except (StopIteration):
1407 stopiteration = True
1440 stopiteration = True
1408 break
1441 break
1409 for rev in sorted(nrevs):
1442 for rev in sorted(nrevs):
1410 fns = fncache.get(rev)
1443 fns = fncache.get(rev)
1411 ctx = change(rev)
1444 ctx = change(rev)
1412 if not fns:
1445 if not fns:
1413 def fns_generator():
1446 def fns_generator():
1414 for f in ctx.files():
1447 for f in ctx.files():
1415 if match(f):
1448 if match(f):
1416 yield f
1449 yield f
1417 fns = fns_generator()
1450 fns = fns_generator()
1418 prepare(ctx, fns)
1451 prepare(ctx, fns)
1419 for rev in nrevs:
1452 for rev in nrevs:
1420 yield change(rev)
1453 yield change(rev)
1421
1454
1422 if stopiteration:
1455 if stopiteration:
1423 break
1456 break
1424
1457
1425 return iterate()
1458 return iterate()
1426
1459
1427 def _makegraphfilematcher(repo, pats, followfirst):
1460 def _makegraphfilematcher(repo, pats, followfirst):
1428 # When displaying a revision with --patch --follow FILE, we have
1461 # When displaying a revision with --patch --follow FILE, we have
1429 # to know which file of the revision must be diffed. With
1462 # to know which file of the revision must be diffed. With
1430 # --follow, we want the names of the ancestors of FILE in the
1463 # --follow, we want the names of the ancestors of FILE in the
1431 # revision, stored in "fcache". "fcache" is populated by
1464 # revision, stored in "fcache". "fcache" is populated by
1432 # reproducing the graph traversal already done by --follow revset
1465 # reproducing the graph traversal already done by --follow revset
1433 # and relating linkrevs to file names (which is not "correct" but
1466 # and relating linkrevs to file names (which is not "correct" but
1434 # good enough).
1467 # good enough).
1435 fcache = {}
1468 fcache = {}
1436 fcacheready = [False]
1469 fcacheready = [False]
1437 pctx = repo['.']
1470 pctx = repo['.']
1438 wctx = repo[None]
1471 wctx = repo[None]
1439
1472
1440 def populate():
1473 def populate():
1441 for fn in pats:
1474 for fn in pats:
1442 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1475 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1443 for c in i:
1476 for c in i:
1444 fcache.setdefault(c.linkrev(), set()).add(c.path())
1477 fcache.setdefault(c.linkrev(), set()).add(c.path())
1445
1478
1446 def filematcher(rev):
1479 def filematcher(rev):
1447 if not fcacheready[0]:
1480 if not fcacheready[0]:
1448 # Lazy initialization
1481 # Lazy initialization
1449 fcacheready[0] = True
1482 fcacheready[0] = True
1450 populate()
1483 populate()
1451 return scmutil.match(wctx, fcache.get(rev, []), default='path')
1484 return scmutil.match(wctx, fcache.get(rev, []), default='path')
1452
1485
1453 return filematcher
1486 return filematcher
1454
1487
1455 def _makegraphlogrevset(repo, pats, opts, revs):
1488 def _makegraphlogrevset(repo, pats, opts, revs):
1456 """Return (expr, filematcher) where expr is a revset string built
1489 """Return (expr, filematcher) where expr is a revset string built
1457 from log options and file patterns or None. If --stat or --patch
1490 from log options and file patterns or None. If --stat or --patch
1458 are not passed filematcher is None. Otherwise it is a callable
1491 are not passed filematcher is None. Otherwise it is a callable
1459 taking a revision number and returning a match objects filtering
1492 taking a revision number and returning a match objects filtering
1460 the files to be detailed when displaying the revision.
1493 the files to be detailed when displaying the revision.
1461 """
1494 """
1462 opt2revset = {
1495 opt2revset = {
1463 'no_merges': ('not merge()', None),
1496 'no_merges': ('not merge()', None),
1464 'only_merges': ('merge()', None),
1497 'only_merges': ('merge()', None),
1465 '_ancestors': ('ancestors(%(val)s)', None),
1498 '_ancestors': ('ancestors(%(val)s)', None),
1466 '_fancestors': ('_firstancestors(%(val)s)', None),
1499 '_fancestors': ('_firstancestors(%(val)s)', None),
1467 '_descendants': ('descendants(%(val)s)', None),
1500 '_descendants': ('descendants(%(val)s)', None),
1468 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1501 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1469 '_matchfiles': ('_matchfiles(%(val)s)', None),
1502 '_matchfiles': ('_matchfiles(%(val)s)', None),
1470 'date': ('date(%(val)r)', None),
1503 'date': ('date(%(val)r)', None),
1471 'branch': ('branch(%(val)r)', ' or '),
1504 'branch': ('branch(%(val)r)', ' or '),
1472 '_patslog': ('filelog(%(val)r)', ' or '),
1505 '_patslog': ('filelog(%(val)r)', ' or '),
1473 '_patsfollow': ('follow(%(val)r)', ' or '),
1506 '_patsfollow': ('follow(%(val)r)', ' or '),
1474 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1507 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1475 'keyword': ('keyword(%(val)r)', ' or '),
1508 'keyword': ('keyword(%(val)r)', ' or '),
1476 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1509 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1477 'user': ('user(%(val)r)', ' or '),
1510 'user': ('user(%(val)r)', ' or '),
1478 }
1511 }
1479
1512
1480 opts = dict(opts)
1513 opts = dict(opts)
1481 # follow or not follow?
1514 # follow or not follow?
1482 follow = opts.get('follow') or opts.get('follow_first')
1515 follow = opts.get('follow') or opts.get('follow_first')
1483 followfirst = opts.get('follow_first') and 1 or 0
1516 followfirst = opts.get('follow_first') and 1 or 0
1484 # --follow with FILE behaviour depends on revs...
1517 # --follow with FILE behaviour depends on revs...
1485 startrev = revs[0]
1518 startrev = revs[0]
1486 followdescendants = (len(revs) > 1 and revs[0] < revs[1]) and 1 or 0
1519 followdescendants = (len(revs) > 1 and revs[0] < revs[1]) and 1 or 0
1487
1520
1488 # branch and only_branch are really aliases and must be handled at
1521 # branch and only_branch are really aliases and must be handled at
1489 # the same time
1522 # the same time
1490 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1523 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1491 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1524 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1492 # pats/include/exclude are passed to match.match() directly in
1525 # pats/include/exclude are passed to match.match() directly in
1493 # _matchfiles() revset but walkchangerevs() builds its matcher with
1526 # _matchfiles() revset but walkchangerevs() builds its matcher with
1494 # scmutil.match(). The difference is input pats are globbed on
1527 # scmutil.match(). The difference is input pats are globbed on
1495 # platforms without shell expansion (windows).
1528 # platforms without shell expansion (windows).
1496 pctx = repo[None]
1529 pctx = repo[None]
1497 match, pats = scmutil.matchandpats(pctx, pats, opts)
1530 match, pats = scmutil.matchandpats(pctx, pats, opts)
1498 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1531 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1499 if not slowpath:
1532 if not slowpath:
1500 for f in match.files():
1533 for f in match.files():
1501 if follow and f not in pctx:
1534 if follow and f not in pctx:
1502 raise util.Abort(_('cannot follow file not in parent '
1535 raise util.Abort(_('cannot follow file not in parent '
1503 'revision: "%s"') % f)
1536 'revision: "%s"') % f)
1504 filelog = repo.file(f)
1537 filelog = repo.file(f)
1505 if not filelog:
1538 if not filelog:
1506 # A zero count may be a directory or deleted file, so
1539 # A zero count may be a directory or deleted file, so
1507 # try to find matching entries on the slow path.
1540 # try to find matching entries on the slow path.
1508 if follow:
1541 if follow:
1509 raise util.Abort(
1542 raise util.Abort(
1510 _('cannot follow nonexistent file: "%s"') % f)
1543 _('cannot follow nonexistent file: "%s"') % f)
1511 slowpath = True
1544 slowpath = True
1512
1545
1513 # We decided to fall back to the slowpath because at least one
1546 # We decided to fall back to the slowpath because at least one
1514 # of the paths was not a file. Check to see if at least one of them
1547 # of the paths was not a file. Check to see if at least one of them
1515 # existed in history - in that case, we'll continue down the
1548 # existed in history - in that case, we'll continue down the
1516 # slowpath; otherwise, we can turn off the slowpath
1549 # slowpath; otherwise, we can turn off the slowpath
1517 if slowpath:
1550 if slowpath:
1518 for path in match.files():
1551 for path in match.files():
1519 if path == '.' or path in repo.store:
1552 if path == '.' or path in repo.store:
1520 break
1553 break
1521 else:
1554 else:
1522 slowpath = False
1555 slowpath = False
1523
1556
1524 if slowpath:
1557 if slowpath:
1525 # See walkchangerevs() slow path.
1558 # See walkchangerevs() slow path.
1526 #
1559 #
1527 if follow:
1560 if follow:
1528 raise util.Abort(_('can only follow copies/renames for explicit '
1561 raise util.Abort(_('can only follow copies/renames for explicit '
1529 'filenames'))
1562 'filenames'))
1530 # pats/include/exclude cannot be represented as separate
1563 # pats/include/exclude cannot be represented as separate
1531 # revset expressions as their filtering logic applies at file
1564 # revset expressions as their filtering logic applies at file
1532 # level. For instance "-I a -X a" matches a revision touching
1565 # level. For instance "-I a -X a" matches a revision touching
1533 # "a" and "b" while "file(a) and not file(b)" does
1566 # "a" and "b" while "file(a) and not file(b)" does
1534 # not. Besides, filesets are evaluated against the working
1567 # not. Besides, filesets are evaluated against the working
1535 # directory.
1568 # directory.
1536 matchargs = ['r:', 'd:relpath']
1569 matchargs = ['r:', 'd:relpath']
1537 for p in pats:
1570 for p in pats:
1538 matchargs.append('p:' + p)
1571 matchargs.append('p:' + p)
1539 for p in opts.get('include', []):
1572 for p in opts.get('include', []):
1540 matchargs.append('i:' + p)
1573 matchargs.append('i:' + p)
1541 for p in opts.get('exclude', []):
1574 for p in opts.get('exclude', []):
1542 matchargs.append('x:' + p)
1575 matchargs.append('x:' + p)
1543 matchargs = ','.join(('%r' % p) for p in matchargs)
1576 matchargs = ','.join(('%r' % p) for p in matchargs)
1544 opts['_matchfiles'] = matchargs
1577 opts['_matchfiles'] = matchargs
1545 else:
1578 else:
1546 if follow:
1579 if follow:
1547 fpats = ('_patsfollow', '_patsfollowfirst')
1580 fpats = ('_patsfollow', '_patsfollowfirst')
1548 fnopats = (('_ancestors', '_fancestors'),
1581 fnopats = (('_ancestors', '_fancestors'),
1549 ('_descendants', '_fdescendants'))
1582 ('_descendants', '_fdescendants'))
1550 if pats:
1583 if pats:
1551 # follow() revset interprets its file argument as a
1584 # follow() revset interprets its file argument as a
1552 # manifest entry, so use match.files(), not pats.
1585 # manifest entry, so use match.files(), not pats.
1553 opts[fpats[followfirst]] = list(match.files())
1586 opts[fpats[followfirst]] = list(match.files())
1554 else:
1587 else:
1555 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1588 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1556 else:
1589 else:
1557 opts['_patslog'] = list(pats)
1590 opts['_patslog'] = list(pats)
1558
1591
1559 filematcher = None
1592 filematcher = None
1560 if opts.get('patch') or opts.get('stat'):
1593 if opts.get('patch') or opts.get('stat'):
1561 if follow:
1594 if follow:
1562 filematcher = _makegraphfilematcher(repo, pats, followfirst)
1595 filematcher = _makegraphfilematcher(repo, pats, followfirst)
1563 else:
1596 else:
1564 filematcher = lambda rev: match
1597 filematcher = lambda rev: match
1565
1598
1566 expr = []
1599 expr = []
1567 for op, val in opts.iteritems():
1600 for op, val in opts.iteritems():
1568 if not val:
1601 if not val:
1569 continue
1602 continue
1570 if op not in opt2revset:
1603 if op not in opt2revset:
1571 continue
1604 continue
1572 revop, andor = opt2revset[op]
1605 revop, andor = opt2revset[op]
1573 if '%(val)' not in revop:
1606 if '%(val)' not in revop:
1574 expr.append(revop)
1607 expr.append(revop)
1575 else:
1608 else:
1576 if not isinstance(val, list):
1609 if not isinstance(val, list):
1577 e = revop % {'val': val}
1610 e = revop % {'val': val}
1578 else:
1611 else:
1579 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1612 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1580 expr.append(e)
1613 expr.append(e)
1581
1614
1582 if expr:
1615 if expr:
1583 expr = '(' + ' and '.join(expr) + ')'
1616 expr = '(' + ' and '.join(expr) + ')'
1584 else:
1617 else:
1585 expr = None
1618 expr = None
1586 return expr, filematcher
1619 return expr, filematcher
1587
1620
1588 def getgraphlogrevs(repo, pats, opts):
1621 def getgraphlogrevs(repo, pats, opts):
1589 """Return (revs, expr, filematcher) where revs is an iterable of
1622 """Return (revs, expr, filematcher) where revs is an iterable of
1590 revision numbers, expr is a revset string built from log options
1623 revision numbers, expr is a revset string built from log options
1591 and file patterns or None, and used to filter 'revs'. If --stat or
1624 and file patterns or None, and used to filter 'revs'. If --stat or
1592 --patch are not passed filematcher is None. Otherwise it is a
1625 --patch are not passed filematcher is None. Otherwise it is a
1593 callable taking a revision number and returning a match objects
1626 callable taking a revision number and returning a match objects
1594 filtering the files to be detailed when displaying the revision.
1627 filtering the files to be detailed when displaying the revision.
1595 """
1628 """
1596 if not len(repo):
1629 if not len(repo):
1597 return [], None, None
1630 return [], None, None
1598 limit = loglimit(opts)
1631 limit = loglimit(opts)
1599 # Default --rev value depends on --follow but --follow behaviour
1632 # Default --rev value depends on --follow but --follow behaviour
1600 # depends on revisions resolved from --rev...
1633 # depends on revisions resolved from --rev...
1601 follow = opts.get('follow') or opts.get('follow_first')
1634 follow = opts.get('follow') or opts.get('follow_first')
1602 possiblyunsorted = False # whether revs might need sorting
1635 possiblyunsorted = False # whether revs might need sorting
1603 if opts.get('rev'):
1636 if opts.get('rev'):
1604 revs = scmutil.revrange(repo, opts['rev'])
1637 revs = scmutil.revrange(repo, opts['rev'])
1605 # Don't sort here because _makegraphlogrevset might depend on the
1638 # Don't sort here because _makegraphlogrevset might depend on the
1606 # order of revs
1639 # order of revs
1607 possiblyunsorted = True
1640 possiblyunsorted = True
1608 else:
1641 else:
1609 if follow and len(repo) > 0:
1642 if follow and len(repo) > 0:
1610 revs = repo.revs('reverse(:.)')
1643 revs = repo.revs('reverse(:.)')
1611 else:
1644 else:
1612 revs = revset.baseset(repo.changelog)
1645 revs = revset.baseset(repo.changelog)
1613 revs.reverse()
1646 revs.reverse()
1614 if not revs:
1647 if not revs:
1615 return [], None, None
1648 return [], None, None
1616 revs = revset.baseset(revs)
1649 revs = revset.baseset(revs)
1617 expr, filematcher = _makegraphlogrevset(repo, pats, opts, revs)
1650 expr, filematcher = _makegraphlogrevset(repo, pats, opts, revs)
1618 if possiblyunsorted:
1651 if possiblyunsorted:
1619 revs.sort(reverse=True)
1652 revs.sort(reverse=True)
1620 if expr:
1653 if expr:
1621 # Revset matchers often operate faster on revisions in changelog
1654 # Revset matchers often operate faster on revisions in changelog
1622 # order, because most filters deal with the changelog.
1655 # order, because most filters deal with the changelog.
1623 revs.reverse()
1656 revs.reverse()
1624 matcher = revset.match(repo.ui, expr)
1657 matcher = revset.match(repo.ui, expr)
1625 # Revset matches can reorder revisions. "A or B" typically returns
1658 # Revset matches can reorder revisions. "A or B" typically returns
1626 # returns the revision matching A then the revision matching B. Sort
1659 # returns the revision matching A then the revision matching B. Sort
1627 # again to fix that.
1660 # again to fix that.
1628 revs = matcher(repo, revs)
1661 revs = matcher(repo, revs)
1629 revs.sort(reverse=True)
1662 revs.sort(reverse=True)
1630 if limit is not None:
1663 if limit is not None:
1631 revs = revs[:limit]
1664 revs = revs[:limit]
1632
1665
1633 return revs, expr, filematcher
1666 return revs, expr, filematcher
1634
1667
1635 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1668 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1636 filematcher=None):
1669 filematcher=None):
1637 seen, state = [], graphmod.asciistate()
1670 seen, state = [], graphmod.asciistate()
1638 for rev, type, ctx, parents in dag:
1671 for rev, type, ctx, parents in dag:
1639 char = 'o'
1672 char = 'o'
1640 if ctx.node() in showparents:
1673 if ctx.node() in showparents:
1641 char = '@'
1674 char = '@'
1642 elif ctx.obsolete():
1675 elif ctx.obsolete():
1643 char = 'x'
1676 char = 'x'
1644 copies = None
1677 copies = None
1645 if getrenamed and ctx.rev():
1678 if getrenamed and ctx.rev():
1646 copies = []
1679 copies = []
1647 for fn in ctx.files():
1680 for fn in ctx.files():
1648 rename = getrenamed(fn, ctx.rev())
1681 rename = getrenamed(fn, ctx.rev())
1649 if rename:
1682 if rename:
1650 copies.append((fn, rename[0]))
1683 copies.append((fn, rename[0]))
1651 revmatchfn = None
1684 revmatchfn = None
1652 if filematcher is not None:
1685 if filematcher is not None:
1653 revmatchfn = filematcher(ctx.rev())
1686 revmatchfn = filematcher(ctx.rev())
1654 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1687 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1655 lines = displayer.hunk.pop(rev).split('\n')
1688 lines = displayer.hunk.pop(rev).split('\n')
1656 if not lines[-1]:
1689 if not lines[-1]:
1657 del lines[-1]
1690 del lines[-1]
1658 displayer.flush(rev)
1691 displayer.flush(rev)
1659 edges = edgefn(type, char, lines, seen, rev, parents)
1692 edges = edgefn(type, char, lines, seen, rev, parents)
1660 for type, char, lines, coldata in edges:
1693 for type, char, lines, coldata in edges:
1661 graphmod.ascii(ui, state, type, char, lines, coldata)
1694 graphmod.ascii(ui, state, type, char, lines, coldata)
1662 displayer.close()
1695 displayer.close()
1663
1696
1664 def graphlog(ui, repo, *pats, **opts):
1697 def graphlog(ui, repo, *pats, **opts):
1665 # Parameters are identical to log command ones
1698 # Parameters are identical to log command ones
1666 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1699 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1667 revdag = graphmod.dagwalker(repo, revs)
1700 revdag = graphmod.dagwalker(repo, revs)
1668
1701
1669 getrenamed = None
1702 getrenamed = None
1670 if opts.get('copies'):
1703 if opts.get('copies'):
1671 endrev = None
1704 endrev = None
1672 if opts.get('rev'):
1705 if opts.get('rev'):
1673 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
1706 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
1674 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1707 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1675 displayer = show_changeset(ui, repo, opts, buffered=True)
1708 displayer = show_changeset(ui, repo, opts, buffered=True)
1676 showparents = [ctx.node() for ctx in repo[None].parents()]
1709 showparents = [ctx.node() for ctx in repo[None].parents()]
1677 displaygraph(ui, revdag, displayer, showparents,
1710 displaygraph(ui, revdag, displayer, showparents,
1678 graphmod.asciiedges, getrenamed, filematcher)
1711 graphmod.asciiedges, getrenamed, filematcher)
1679
1712
1680 def checkunsupportedgraphflags(pats, opts):
1713 def checkunsupportedgraphflags(pats, opts):
1681 for op in ["newest_first"]:
1714 for op in ["newest_first"]:
1682 if op in opts and opts[op]:
1715 if op in opts and opts[op]:
1683 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1716 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1684 % op.replace("_", "-"))
1717 % op.replace("_", "-"))
1685
1718
1686 def graphrevs(repo, nodes, opts):
1719 def graphrevs(repo, nodes, opts):
1687 limit = loglimit(opts)
1720 limit = loglimit(opts)
1688 nodes.reverse()
1721 nodes.reverse()
1689 if limit is not None:
1722 if limit is not None:
1690 nodes = nodes[:limit]
1723 nodes = nodes[:limit]
1691 return graphmod.nodes(repo, nodes)
1724 return graphmod.nodes(repo, nodes)
1692
1725
1693 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1726 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1694 join = lambda f: os.path.join(prefix, f)
1727 join = lambda f: os.path.join(prefix, f)
1695 bad = []
1728 bad = []
1696 oldbad = match.bad
1729 oldbad = match.bad
1697 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1730 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1698 names = []
1731 names = []
1699 wctx = repo[None]
1732 wctx = repo[None]
1700 cca = None
1733 cca = None
1701 abort, warn = scmutil.checkportabilityalert(ui)
1734 abort, warn = scmutil.checkportabilityalert(ui)
1702 if abort or warn:
1735 if abort or warn:
1703 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1736 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1704 for f in repo.walk(match):
1737 for f in repo.walk(match):
1705 exact = match.exact(f)
1738 exact = match.exact(f)
1706 if exact or not explicitonly and f not in repo.dirstate:
1739 if exact or not explicitonly and f not in repo.dirstate:
1707 if cca:
1740 if cca:
1708 cca(f)
1741 cca(f)
1709 names.append(f)
1742 names.append(f)
1710 if ui.verbose or not exact:
1743 if ui.verbose or not exact:
1711 ui.status(_('adding %s\n') % match.rel(join(f)))
1744 ui.status(_('adding %s\n') % match.rel(join(f)))
1712
1745
1713 for subpath in sorted(wctx.substate):
1746 for subpath in sorted(wctx.substate):
1714 sub = wctx.sub(subpath)
1747 sub = wctx.sub(subpath)
1715 try:
1748 try:
1716 submatch = matchmod.narrowmatcher(subpath, match)
1749 submatch = matchmod.narrowmatcher(subpath, match)
1717 if listsubrepos:
1750 if listsubrepos:
1718 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1751 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1719 False))
1752 False))
1720 else:
1753 else:
1721 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1754 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1722 True))
1755 True))
1723 except error.LookupError:
1756 except error.LookupError:
1724 ui.status(_("skipping missing subrepository: %s\n")
1757 ui.status(_("skipping missing subrepository: %s\n")
1725 % join(subpath))
1758 % join(subpath))
1726
1759
1727 if not dryrun:
1760 if not dryrun:
1728 rejected = wctx.add(names, prefix)
1761 rejected = wctx.add(names, prefix)
1729 bad.extend(f for f in rejected if f in match.files())
1762 bad.extend(f for f in rejected if f in match.files())
1730 return bad
1763 return bad
1731
1764
1732 def forget(ui, repo, match, prefix, explicitonly):
1765 def forget(ui, repo, match, prefix, explicitonly):
1733 join = lambda f: os.path.join(prefix, f)
1766 join = lambda f: os.path.join(prefix, f)
1734 bad = []
1767 bad = []
1735 oldbad = match.bad
1768 oldbad = match.bad
1736 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1769 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1737 wctx = repo[None]
1770 wctx = repo[None]
1738 forgot = []
1771 forgot = []
1739 s = repo.status(match=match, clean=True)
1772 s = repo.status(match=match, clean=True)
1740 forget = sorted(s[0] + s[1] + s[3] + s[6])
1773 forget = sorted(s[0] + s[1] + s[3] + s[6])
1741 if explicitonly:
1774 if explicitonly:
1742 forget = [f for f in forget if match.exact(f)]
1775 forget = [f for f in forget if match.exact(f)]
1743
1776
1744 for subpath in sorted(wctx.substate):
1777 for subpath in sorted(wctx.substate):
1745 sub = wctx.sub(subpath)
1778 sub = wctx.sub(subpath)
1746 try:
1779 try:
1747 submatch = matchmod.narrowmatcher(subpath, match)
1780 submatch = matchmod.narrowmatcher(subpath, match)
1748 subbad, subforgot = sub.forget(ui, submatch, prefix)
1781 subbad, subforgot = sub.forget(ui, submatch, prefix)
1749 bad.extend([subpath + '/' + f for f in subbad])
1782 bad.extend([subpath + '/' + f for f in subbad])
1750 forgot.extend([subpath + '/' + f for f in subforgot])
1783 forgot.extend([subpath + '/' + f for f in subforgot])
1751 except error.LookupError:
1784 except error.LookupError:
1752 ui.status(_("skipping missing subrepository: %s\n")
1785 ui.status(_("skipping missing subrepository: %s\n")
1753 % join(subpath))
1786 % join(subpath))
1754
1787
1755 if not explicitonly:
1788 if not explicitonly:
1756 for f in match.files():
1789 for f in match.files():
1757 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1790 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1758 if f not in forgot:
1791 if f not in forgot:
1759 if os.path.exists(match.rel(join(f))):
1792 if os.path.exists(match.rel(join(f))):
1760 ui.warn(_('not removing %s: '
1793 ui.warn(_('not removing %s: '
1761 'file is already untracked\n')
1794 'file is already untracked\n')
1762 % match.rel(join(f)))
1795 % match.rel(join(f)))
1763 bad.append(f)
1796 bad.append(f)
1764
1797
1765 for f in forget:
1798 for f in forget:
1766 if ui.verbose or not match.exact(f):
1799 if ui.verbose or not match.exact(f):
1767 ui.status(_('removing %s\n') % match.rel(join(f)))
1800 ui.status(_('removing %s\n') % match.rel(join(f)))
1768
1801
1769 rejected = wctx.forget(forget, prefix)
1802 rejected = wctx.forget(forget, prefix)
1770 bad.extend(f for f in rejected if f in match.files())
1803 bad.extend(f for f in rejected if f in match.files())
1771 forgot.extend(forget)
1804 forgot.extend(forget)
1772 return bad, forgot
1805 return bad, forgot
1773
1806
1774 def duplicatecopies(repo, rev, fromrev):
1807 def duplicatecopies(repo, rev, fromrev):
1775 '''reproduce copies from fromrev to rev in the dirstate'''
1808 '''reproduce copies from fromrev to rev in the dirstate'''
1776 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
1809 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
1777 # copies.pathcopies returns backward renames, so dst might not
1810 # copies.pathcopies returns backward renames, so dst might not
1778 # actually be in the dirstate
1811 # actually be in the dirstate
1779 if repo.dirstate[dst] in "nma":
1812 if repo.dirstate[dst] in "nma":
1780 repo.dirstate.copy(src, dst)
1813 repo.dirstate.copy(src, dst)
1781
1814
1782 def commit(ui, repo, commitfunc, pats, opts):
1815 def commit(ui, repo, commitfunc, pats, opts):
1783 '''commit the specified files or all outstanding changes'''
1816 '''commit the specified files or all outstanding changes'''
1784 date = opts.get('date')
1817 date = opts.get('date')
1785 if date:
1818 if date:
1786 opts['date'] = util.parsedate(date)
1819 opts['date'] = util.parsedate(date)
1787 message = logmessage(ui, opts)
1820 message = logmessage(ui, opts)
1788
1821
1789 # extract addremove carefully -- this function can be called from a command
1822 # extract addremove carefully -- this function can be called from a command
1790 # that doesn't support addremove
1823 # that doesn't support addremove
1791 if opts.get('addremove'):
1824 if opts.get('addremove'):
1792 scmutil.addremove(repo, pats, opts)
1825 scmutil.addremove(repo, pats, opts)
1793
1826
1794 return commitfunc(ui, repo, message,
1827 return commitfunc(ui, repo, message,
1795 scmutil.match(repo[None], pats, opts), opts)
1828 scmutil.match(repo[None], pats, opts), opts)
1796
1829
1797 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1830 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1798 ui.note(_('amending changeset %s\n') % old)
1831 ui.note(_('amending changeset %s\n') % old)
1799 base = old.p1()
1832 base = old.p1()
1800
1833
1801 wlock = lock = newid = None
1834 wlock = lock = newid = None
1802 try:
1835 try:
1803 wlock = repo.wlock()
1836 wlock = repo.wlock()
1804 lock = repo.lock()
1837 lock = repo.lock()
1805 tr = repo.transaction('amend')
1838 tr = repo.transaction('amend')
1806 try:
1839 try:
1807 # See if we got a message from -m or -l, if not, open the editor
1840 # See if we got a message from -m or -l, if not, open the editor
1808 # with the message of the changeset to amend
1841 # with the message of the changeset to amend
1809 message = logmessage(ui, opts)
1842 message = logmessage(ui, opts)
1810 # ensure logfile does not conflict with later enforcement of the
1843 # ensure logfile does not conflict with later enforcement of the
1811 # message. potential logfile content has been processed by
1844 # message. potential logfile content has been processed by
1812 # `logmessage` anyway.
1845 # `logmessage` anyway.
1813 opts.pop('logfile')
1846 opts.pop('logfile')
1814 # First, do a regular commit to record all changes in the working
1847 # First, do a regular commit to record all changes in the working
1815 # directory (if there are any)
1848 # directory (if there are any)
1816 ui.callhooks = False
1849 ui.callhooks = False
1817 currentbookmark = repo._bookmarkcurrent
1850 currentbookmark = repo._bookmarkcurrent
1818 try:
1851 try:
1819 repo._bookmarkcurrent = None
1852 repo._bookmarkcurrent = None
1820 opts['message'] = 'temporary amend commit for %s' % old
1853 opts['message'] = 'temporary amend commit for %s' % old
1821 node = commit(ui, repo, commitfunc, pats, opts)
1854 node = commit(ui, repo, commitfunc, pats, opts)
1822 finally:
1855 finally:
1823 repo._bookmarkcurrent = currentbookmark
1856 repo._bookmarkcurrent = currentbookmark
1824 ui.callhooks = True
1857 ui.callhooks = True
1825 ctx = repo[node]
1858 ctx = repo[node]
1826
1859
1827 # Participating changesets:
1860 # Participating changesets:
1828 #
1861 #
1829 # node/ctx o - new (intermediate) commit that contains changes
1862 # node/ctx o - new (intermediate) commit that contains changes
1830 # | from working dir to go into amending commit
1863 # | from working dir to go into amending commit
1831 # | (or a workingctx if there were no changes)
1864 # | (or a workingctx if there were no changes)
1832 # |
1865 # |
1833 # old o - changeset to amend
1866 # old o - changeset to amend
1834 # |
1867 # |
1835 # base o - parent of amending changeset
1868 # base o - parent of amending changeset
1836
1869
1837 # Update extra dict from amended commit (e.g. to preserve graft
1870 # Update extra dict from amended commit (e.g. to preserve graft
1838 # source)
1871 # source)
1839 extra.update(old.extra())
1872 extra.update(old.extra())
1840
1873
1841 # Also update it from the intermediate commit or from the wctx
1874 # Also update it from the intermediate commit or from the wctx
1842 extra.update(ctx.extra())
1875 extra.update(ctx.extra())
1843
1876
1844 if len(old.parents()) > 1:
1877 if len(old.parents()) > 1:
1845 # ctx.files() isn't reliable for merges, so fall back to the
1878 # ctx.files() isn't reliable for merges, so fall back to the
1846 # slower repo.status() method
1879 # slower repo.status() method
1847 files = set([fn for st in repo.status(base, old)[:3]
1880 files = set([fn for st in repo.status(base, old)[:3]
1848 for fn in st])
1881 for fn in st])
1849 else:
1882 else:
1850 files = set(old.files())
1883 files = set(old.files())
1851
1884
1852 # Second, we use either the commit we just did, or if there were no
1885 # Second, we use either the commit we just did, or if there were no
1853 # changes the parent of the working directory as the version of the
1886 # changes the parent of the working directory as the version of the
1854 # files in the final amend commit
1887 # files in the final amend commit
1855 if node:
1888 if node:
1856 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
1889 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
1857
1890
1858 user = ctx.user()
1891 user = ctx.user()
1859 date = ctx.date()
1892 date = ctx.date()
1860 # Recompute copies (avoid recording a -> b -> a)
1893 # Recompute copies (avoid recording a -> b -> a)
1861 copied = copies.pathcopies(base, ctx)
1894 copied = copies.pathcopies(base, ctx)
1862
1895
1863 # Prune files which were reverted by the updates: if old
1896 # Prune files which were reverted by the updates: if old
1864 # introduced file X and our intermediate commit, node,
1897 # introduced file X and our intermediate commit, node,
1865 # renamed that file, then those two files are the same and
1898 # renamed that file, then those two files are the same and
1866 # we can discard X from our list of files. Likewise if X
1899 # we can discard X from our list of files. Likewise if X
1867 # was deleted, it's no longer relevant
1900 # was deleted, it's no longer relevant
1868 files.update(ctx.files())
1901 files.update(ctx.files())
1869
1902
1870 def samefile(f):
1903 def samefile(f):
1871 if f in ctx.manifest():
1904 if f in ctx.manifest():
1872 a = ctx.filectx(f)
1905 a = ctx.filectx(f)
1873 if f in base.manifest():
1906 if f in base.manifest():
1874 b = base.filectx(f)
1907 b = base.filectx(f)
1875 return (not a.cmp(b)
1908 return (not a.cmp(b)
1876 and a.flags() == b.flags())
1909 and a.flags() == b.flags())
1877 else:
1910 else:
1878 return False
1911 return False
1879 else:
1912 else:
1880 return f not in base.manifest()
1913 return f not in base.manifest()
1881 files = [f for f in files if not samefile(f)]
1914 files = [f for f in files if not samefile(f)]
1882
1915
1883 def filectxfn(repo, ctx_, path):
1916 def filectxfn(repo, ctx_, path):
1884 try:
1917 try:
1885 fctx = ctx[path]
1918 fctx = ctx[path]
1886 flags = fctx.flags()
1919 flags = fctx.flags()
1887 mctx = context.memfilectx(fctx.path(), fctx.data(),
1920 mctx = context.memfilectx(fctx.path(), fctx.data(),
1888 islink='l' in flags,
1921 islink='l' in flags,
1889 isexec='x' in flags,
1922 isexec='x' in flags,
1890 copied=copied.get(path))
1923 copied=copied.get(path))
1891 return mctx
1924 return mctx
1892 except KeyError:
1925 except KeyError:
1893 raise IOError
1926 raise IOError
1894 else:
1927 else:
1895 ui.note(_('copying changeset %s to %s\n') % (old, base))
1928 ui.note(_('copying changeset %s to %s\n') % (old, base))
1896
1929
1897 # Use version of files as in the old cset
1930 # Use version of files as in the old cset
1898 def filectxfn(repo, ctx_, path):
1931 def filectxfn(repo, ctx_, path):
1899 try:
1932 try:
1900 return old.filectx(path)
1933 return old.filectx(path)
1901 except KeyError:
1934 except KeyError:
1902 raise IOError
1935 raise IOError
1903
1936
1904 user = opts.get('user') or old.user()
1937 user = opts.get('user') or old.user()
1905 date = opts.get('date') or old.date()
1938 date = opts.get('date') or old.date()
1906 editmsg = False
1939 editmsg = False
1907 if not message:
1940 if not message:
1908 editmsg = True
1941 editmsg = True
1909 message = old.description()
1942 message = old.description()
1910
1943
1911 pureextra = extra.copy()
1944 pureextra = extra.copy()
1912 extra['amend_source'] = old.hex()
1945 extra['amend_source'] = old.hex()
1913
1946
1914 new = context.memctx(repo,
1947 new = context.memctx(repo,
1915 parents=[base.node(), old.p2().node()],
1948 parents=[base.node(), old.p2().node()],
1916 text=message,
1949 text=message,
1917 files=files,
1950 files=files,
1918 filectxfn=filectxfn,
1951 filectxfn=filectxfn,
1919 user=user,
1952 user=user,
1920 date=date,
1953 date=date,
1921 extra=extra)
1954 extra=extra)
1922 if editmsg:
1955 if editmsg:
1923 new._text = commitforceeditor(repo, new, [])
1956 new._text = commitforceeditor(repo, new, [])
1924
1957
1925 newdesc = changelog.stripdesc(new.description())
1958 newdesc = changelog.stripdesc(new.description())
1926 if ((not node)
1959 if ((not node)
1927 and newdesc == old.description()
1960 and newdesc == old.description()
1928 and user == old.user()
1961 and user == old.user()
1929 and date == old.date()
1962 and date == old.date()
1930 and pureextra == old.extra()):
1963 and pureextra == old.extra()):
1931 # nothing changed. continuing here would create a new node
1964 # nothing changed. continuing here would create a new node
1932 # anyway because of the amend_source noise.
1965 # anyway because of the amend_source noise.
1933 #
1966 #
1934 # This not what we expect from amend.
1967 # This not what we expect from amend.
1935 return old.node()
1968 return old.node()
1936
1969
1937 ph = repo.ui.config('phases', 'new-commit', phases.draft)
1970 ph = repo.ui.config('phases', 'new-commit', phases.draft)
1938 try:
1971 try:
1939 repo.ui.setconfig('phases', 'new-commit', old.phase())
1972 repo.ui.setconfig('phases', 'new-commit', old.phase())
1940 newid = repo.commitctx(new)
1973 newid = repo.commitctx(new)
1941 finally:
1974 finally:
1942 repo.ui.setconfig('phases', 'new-commit', ph)
1975 repo.ui.setconfig('phases', 'new-commit', ph)
1943 if newid != old.node():
1976 if newid != old.node():
1944 # Reroute the working copy parent to the new changeset
1977 # Reroute the working copy parent to the new changeset
1945 repo.setparents(newid, nullid)
1978 repo.setparents(newid, nullid)
1946
1979
1947 # Move bookmarks from old parent to amend commit
1980 # Move bookmarks from old parent to amend commit
1948 bms = repo.nodebookmarks(old.node())
1981 bms = repo.nodebookmarks(old.node())
1949 if bms:
1982 if bms:
1950 marks = repo._bookmarks
1983 marks = repo._bookmarks
1951 for bm in bms:
1984 for bm in bms:
1952 marks[bm] = newid
1985 marks[bm] = newid
1953 marks.write()
1986 marks.write()
1954 #commit the whole amend process
1987 #commit the whole amend process
1955 if obsolete._enabled and newid != old.node():
1988 if obsolete._enabled and newid != old.node():
1956 # mark the new changeset as successor of the rewritten one
1989 # mark the new changeset as successor of the rewritten one
1957 new = repo[newid]
1990 new = repo[newid]
1958 obs = [(old, (new,))]
1991 obs = [(old, (new,))]
1959 if node:
1992 if node:
1960 obs.append((ctx, ()))
1993 obs.append((ctx, ()))
1961
1994
1962 obsolete.createmarkers(repo, obs)
1995 obsolete.createmarkers(repo, obs)
1963 tr.close()
1996 tr.close()
1964 finally:
1997 finally:
1965 tr.release()
1998 tr.release()
1966 if (not obsolete._enabled) and newid != old.node():
1999 if (not obsolete._enabled) and newid != old.node():
1967 # Strip the intermediate commit (if there was one) and the amended
2000 # Strip the intermediate commit (if there was one) and the amended
1968 # commit
2001 # commit
1969 if node:
2002 if node:
1970 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2003 ui.note(_('stripping intermediate changeset %s\n') % ctx)
1971 ui.note(_('stripping amended changeset %s\n') % old)
2004 ui.note(_('stripping amended changeset %s\n') % old)
1972 repair.strip(ui, repo, old.node(), topic='amend-backup')
2005 repair.strip(ui, repo, old.node(), topic='amend-backup')
1973 finally:
2006 finally:
1974 if newid is None:
2007 if newid is None:
1975 repo.dirstate.invalidate()
2008 repo.dirstate.invalidate()
1976 lockmod.release(lock, wlock)
2009 lockmod.release(lock, wlock)
1977 return newid
2010 return newid
1978
2011
1979 def commiteditor(repo, ctx, subs):
2012 def commiteditor(repo, ctx, subs):
1980 if ctx.description():
2013 if ctx.description():
1981 return ctx.description()
2014 return ctx.description()
1982 return commitforceeditor(repo, ctx, subs)
2015 return commitforceeditor(repo, ctx, subs)
1983
2016
1984 def commitforceeditor(repo, ctx, subs):
2017 def commitforceeditor(repo, ctx, subs):
1985 edittext = []
2018 edittext = []
1986 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2019 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1987 if ctx.description():
2020 if ctx.description():
1988 edittext.append(ctx.description())
2021 edittext.append(ctx.description())
1989 edittext.append("")
2022 edittext.append("")
1990 edittext.append("") # Empty line between message and comments.
2023 edittext.append("") # Empty line between message and comments.
1991 edittext.append(_("HG: Enter commit message."
2024 edittext.append(_("HG: Enter commit message."
1992 " Lines beginning with 'HG:' are removed."))
2025 " Lines beginning with 'HG:' are removed."))
1993 edittext.append(_("HG: Leave message empty to abort commit."))
2026 edittext.append(_("HG: Leave message empty to abort commit."))
1994 edittext.append("HG: --")
2027 edittext.append("HG: --")
1995 edittext.append(_("HG: user: %s") % ctx.user())
2028 edittext.append(_("HG: user: %s") % ctx.user())
1996 if ctx.p2():
2029 if ctx.p2():
1997 edittext.append(_("HG: branch merge"))
2030 edittext.append(_("HG: branch merge"))
1998 if ctx.branch():
2031 if ctx.branch():
1999 edittext.append(_("HG: branch '%s'") % ctx.branch())
2032 edittext.append(_("HG: branch '%s'") % ctx.branch())
2000 if bookmarks.iscurrent(repo):
2033 if bookmarks.iscurrent(repo):
2001 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2034 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2002 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2035 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2003 edittext.extend([_("HG: added %s") % f for f in added])
2036 edittext.extend([_("HG: added %s") % f for f in added])
2004 edittext.extend([_("HG: changed %s") % f for f in modified])
2037 edittext.extend([_("HG: changed %s") % f for f in modified])
2005 edittext.extend([_("HG: removed %s") % f for f in removed])
2038 edittext.extend([_("HG: removed %s") % f for f in removed])
2006 if not added and not modified and not removed:
2039 if not added and not modified and not removed:
2007 edittext.append(_("HG: no files changed"))
2040 edittext.append(_("HG: no files changed"))
2008 edittext.append("")
2041 edittext.append("")
2009 # run editor in the repository root
2042 # run editor in the repository root
2010 olddir = os.getcwd()
2043 olddir = os.getcwd()
2011 os.chdir(repo.root)
2044 os.chdir(repo.root)
2012 text = repo.ui.edit("\n".join(edittext), ctx.user(), ctx.extra())
2045 text = repo.ui.edit("\n".join(edittext), ctx.user(), ctx.extra())
2013 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2046 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2014 os.chdir(olddir)
2047 os.chdir(olddir)
2015
2048
2016 if not text.strip():
2049 if not text.strip():
2017 raise util.Abort(_("empty commit message"))
2050 raise util.Abort(_("empty commit message"))
2018
2051
2019 return text
2052 return text
2020
2053
2021 def commitstatus(repo, node, branch, bheads=None, opts={}):
2054 def commitstatus(repo, node, branch, bheads=None, opts={}):
2022 ctx = repo[node]
2055 ctx = repo[node]
2023 parents = ctx.parents()
2056 parents = ctx.parents()
2024
2057
2025 if (not opts.get('amend') and bheads and node not in bheads and not
2058 if (not opts.get('amend') and bheads and node not in bheads and not
2026 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2059 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2027 repo.ui.status(_('created new head\n'))
2060 repo.ui.status(_('created new head\n'))
2028 # The message is not printed for initial roots. For the other
2061 # The message is not printed for initial roots. For the other
2029 # changesets, it is printed in the following situations:
2062 # changesets, it is printed in the following situations:
2030 #
2063 #
2031 # Par column: for the 2 parents with ...
2064 # Par column: for the 2 parents with ...
2032 # N: null or no parent
2065 # N: null or no parent
2033 # B: parent is on another named branch
2066 # B: parent is on another named branch
2034 # C: parent is a regular non head changeset
2067 # C: parent is a regular non head changeset
2035 # H: parent was a branch head of the current branch
2068 # H: parent was a branch head of the current branch
2036 # Msg column: whether we print "created new head" message
2069 # Msg column: whether we print "created new head" message
2037 # In the following, it is assumed that there already exists some
2070 # In the following, it is assumed that there already exists some
2038 # initial branch heads of the current branch, otherwise nothing is
2071 # initial branch heads of the current branch, otherwise nothing is
2039 # printed anyway.
2072 # printed anyway.
2040 #
2073 #
2041 # Par Msg Comment
2074 # Par Msg Comment
2042 # N N y additional topo root
2075 # N N y additional topo root
2043 #
2076 #
2044 # B N y additional branch root
2077 # B N y additional branch root
2045 # C N y additional topo head
2078 # C N y additional topo head
2046 # H N n usual case
2079 # H N n usual case
2047 #
2080 #
2048 # B B y weird additional branch root
2081 # B B y weird additional branch root
2049 # C B y branch merge
2082 # C B y branch merge
2050 # H B n merge with named branch
2083 # H B n merge with named branch
2051 #
2084 #
2052 # C C y additional head from merge
2085 # C C y additional head from merge
2053 # C H n merge with a head
2086 # C H n merge with a head
2054 #
2087 #
2055 # H H n head merge: head count decreases
2088 # H H n head merge: head count decreases
2056
2089
2057 if not opts.get('close_branch'):
2090 if not opts.get('close_branch'):
2058 for r in parents:
2091 for r in parents:
2059 if r.closesbranch() and r.branch() == branch:
2092 if r.closesbranch() and r.branch() == branch:
2060 repo.ui.status(_('reopening closed branch head %d\n') % r)
2093 repo.ui.status(_('reopening closed branch head %d\n') % r)
2061
2094
2062 if repo.ui.debugflag:
2095 if repo.ui.debugflag:
2063 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2096 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2064 elif repo.ui.verbose:
2097 elif repo.ui.verbose:
2065 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2098 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2066
2099
2067 def revert(ui, repo, ctx, parents, *pats, **opts):
2100 def revert(ui, repo, ctx, parents, *pats, **opts):
2068 parent, p2 = parents
2101 parent, p2 = parents
2069 node = ctx.node()
2102 node = ctx.node()
2070
2103
2071 mf = ctx.manifest()
2104 mf = ctx.manifest()
2072 if node == parent:
2105 if node == parent:
2073 pmf = mf
2106 pmf = mf
2074 else:
2107 else:
2075 pmf = None
2108 pmf = None
2076
2109
2077 # need all matching names in dirstate and manifest of target rev,
2110 # need all matching names in dirstate and manifest of target rev,
2078 # so have to walk both. do not print errors if files exist in one
2111 # so have to walk both. do not print errors if files exist in one
2079 # but not other.
2112 # but not other.
2080
2113
2081 names = {}
2114 names = {}
2082
2115
2083 wlock = repo.wlock()
2116 wlock = repo.wlock()
2084 try:
2117 try:
2085 # walk dirstate.
2118 # walk dirstate.
2086
2119
2087 m = scmutil.match(repo[None], pats, opts)
2120 m = scmutil.match(repo[None], pats, opts)
2088 m.bad = lambda x, y: False
2121 m.bad = lambda x, y: False
2089 for abs in repo.walk(m):
2122 for abs in repo.walk(m):
2090 names[abs] = m.rel(abs), m.exact(abs)
2123 names[abs] = m.rel(abs), m.exact(abs)
2091
2124
2092 # walk target manifest.
2125 # walk target manifest.
2093
2126
2094 def badfn(path, msg):
2127 def badfn(path, msg):
2095 if path in names:
2128 if path in names:
2096 return
2129 return
2097 if path in ctx.substate:
2130 if path in ctx.substate:
2098 return
2131 return
2099 path_ = path + '/'
2132 path_ = path + '/'
2100 for f in names:
2133 for f in names:
2101 if f.startswith(path_):
2134 if f.startswith(path_):
2102 return
2135 return
2103 ui.warn("%s: %s\n" % (m.rel(path), msg))
2136 ui.warn("%s: %s\n" % (m.rel(path), msg))
2104
2137
2105 m = scmutil.match(ctx, pats, opts)
2138 m = scmutil.match(ctx, pats, opts)
2106 m.bad = badfn
2139 m.bad = badfn
2107 for abs in ctx.walk(m):
2140 for abs in ctx.walk(m):
2108 if abs not in names:
2141 if abs not in names:
2109 names[abs] = m.rel(abs), m.exact(abs)
2142 names[abs] = m.rel(abs), m.exact(abs)
2110
2143
2111 # get the list of subrepos that must be reverted
2144 # get the list of subrepos that must be reverted
2112 targetsubs = sorted(s for s in ctx.substate if m(s))
2145 targetsubs = sorted(s for s in ctx.substate if m(s))
2113 m = scmutil.matchfiles(repo, names)
2146 m = scmutil.matchfiles(repo, names)
2114 changes = repo.status(match=m)[:4]
2147 changes = repo.status(match=m)[:4]
2115 modified, added, removed, deleted = map(set, changes)
2148 modified, added, removed, deleted = map(set, changes)
2116
2149
2117 # if f is a rename, also revert the source
2150 # if f is a rename, also revert the source
2118 cwd = repo.getcwd()
2151 cwd = repo.getcwd()
2119 for f in added:
2152 for f in added:
2120 src = repo.dirstate.copied(f)
2153 src = repo.dirstate.copied(f)
2121 if src and src not in names and repo.dirstate[src] == 'r':
2154 if src and src not in names and repo.dirstate[src] == 'r':
2122 removed.add(src)
2155 removed.add(src)
2123 names[src] = (repo.pathto(src, cwd), True)
2156 names[src] = (repo.pathto(src, cwd), True)
2124
2157
2125 def removeforget(abs):
2158 def removeforget(abs):
2126 if repo.dirstate[abs] == 'a':
2159 if repo.dirstate[abs] == 'a':
2127 return _('forgetting %s\n')
2160 return _('forgetting %s\n')
2128 return _('removing %s\n')
2161 return _('removing %s\n')
2129
2162
2130 revert = ([], _('reverting %s\n'))
2163 revert = ([], _('reverting %s\n'))
2131 add = ([], _('adding %s\n'))
2164 add = ([], _('adding %s\n'))
2132 remove = ([], removeforget)
2165 remove = ([], removeforget)
2133 undelete = ([], _('undeleting %s\n'))
2166 undelete = ([], _('undeleting %s\n'))
2134
2167
2135 disptable = (
2168 disptable = (
2136 # dispatch table:
2169 # dispatch table:
2137 # file state
2170 # file state
2138 # action if in target manifest
2171 # action if in target manifest
2139 # action if not in target manifest
2172 # action if not in target manifest
2140 # make backup if in target manifest
2173 # make backup if in target manifest
2141 # make backup if not in target manifest
2174 # make backup if not in target manifest
2142 (modified, revert, remove, True, True),
2175 (modified, revert, remove, True, True),
2143 (added, revert, remove, True, False),
2176 (added, revert, remove, True, False),
2144 (removed, undelete, None, True, False),
2177 (removed, undelete, None, True, False),
2145 (deleted, revert, remove, False, False),
2178 (deleted, revert, remove, False, False),
2146 )
2179 )
2147
2180
2148 for abs, (rel, exact) in sorted(names.items()):
2181 for abs, (rel, exact) in sorted(names.items()):
2149 mfentry = mf.get(abs)
2182 mfentry = mf.get(abs)
2150 target = repo.wjoin(abs)
2183 target = repo.wjoin(abs)
2151 def handle(xlist, dobackup):
2184 def handle(xlist, dobackup):
2152 xlist[0].append(abs)
2185 xlist[0].append(abs)
2153 if (dobackup and not opts.get('no_backup') and
2186 if (dobackup and not opts.get('no_backup') and
2154 os.path.lexists(target) and
2187 os.path.lexists(target) and
2155 abs in ctx and repo[None][abs].cmp(ctx[abs])):
2188 abs in ctx and repo[None][abs].cmp(ctx[abs])):
2156 bakname = "%s.orig" % rel
2189 bakname = "%s.orig" % rel
2157 ui.note(_('saving current version of %s as %s\n') %
2190 ui.note(_('saving current version of %s as %s\n') %
2158 (rel, bakname))
2191 (rel, bakname))
2159 if not opts.get('dry_run'):
2192 if not opts.get('dry_run'):
2160 util.rename(target, bakname)
2193 util.rename(target, bakname)
2161 if ui.verbose or not exact:
2194 if ui.verbose or not exact:
2162 msg = xlist[1]
2195 msg = xlist[1]
2163 if not isinstance(msg, basestring):
2196 if not isinstance(msg, basestring):
2164 msg = msg(abs)
2197 msg = msg(abs)
2165 ui.status(msg % rel)
2198 ui.status(msg % rel)
2166 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2199 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2167 if abs not in table:
2200 if abs not in table:
2168 continue
2201 continue
2169 # file has changed in dirstate
2202 # file has changed in dirstate
2170 if mfentry:
2203 if mfentry:
2171 handle(hitlist, backuphit)
2204 handle(hitlist, backuphit)
2172 elif misslist is not None:
2205 elif misslist is not None:
2173 handle(misslist, backupmiss)
2206 handle(misslist, backupmiss)
2174 break
2207 break
2175 else:
2208 else:
2176 if abs not in repo.dirstate:
2209 if abs not in repo.dirstate:
2177 if mfentry:
2210 if mfentry:
2178 handle(add, True)
2211 handle(add, True)
2179 elif exact:
2212 elif exact:
2180 ui.warn(_('file not managed: %s\n') % rel)
2213 ui.warn(_('file not managed: %s\n') % rel)
2181 continue
2214 continue
2182 # file has not changed in dirstate
2215 # file has not changed in dirstate
2183 if node == parent:
2216 if node == parent:
2184 if exact:
2217 if exact:
2185 ui.warn(_('no changes needed to %s\n') % rel)
2218 ui.warn(_('no changes needed to %s\n') % rel)
2186 continue
2219 continue
2187 if pmf is None:
2220 if pmf is None:
2188 # only need parent manifest in this unlikely case,
2221 # only need parent manifest in this unlikely case,
2189 # so do not read by default
2222 # so do not read by default
2190 pmf = repo[parent].manifest()
2223 pmf = repo[parent].manifest()
2191 if abs in pmf and mfentry:
2224 if abs in pmf and mfentry:
2192 # if version of file is same in parent and target
2225 # if version of file is same in parent and target
2193 # manifests, do nothing
2226 # manifests, do nothing
2194 if (pmf[abs] != mfentry or
2227 if (pmf[abs] != mfentry or
2195 pmf.flags(abs) != mf.flags(abs)):
2228 pmf.flags(abs) != mf.flags(abs)):
2196 handle(revert, False)
2229 handle(revert, False)
2197 else:
2230 else:
2198 handle(remove, False)
2231 handle(remove, False)
2199 if not opts.get('dry_run'):
2232 if not opts.get('dry_run'):
2200 _performrevert(repo, parents, ctx, revert, add, remove, undelete)
2233 _performrevert(repo, parents, ctx, revert, add, remove, undelete)
2201
2234
2202 if targetsubs:
2235 if targetsubs:
2203 # Revert the subrepos on the revert list
2236 # Revert the subrepos on the revert list
2204 for sub in targetsubs:
2237 for sub in targetsubs:
2205 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2238 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2206 finally:
2239 finally:
2207 wlock.release()
2240 wlock.release()
2208
2241
2209 def _performrevert(repo, parents, ctx, revert, add, remove, undelete):
2242 def _performrevert(repo, parents, ctx, revert, add, remove, undelete):
2210 """function that actually perform all the action computed for revert
2243 """function that actually perform all the action computed for revert
2211
2244
2212 This is an independent function to let extension to plug in and react to
2245 This is an independent function to let extension to plug in and react to
2213 the imminent revert.
2246 the imminent revert.
2214
2247
2215 Make sure you have the working directory locked when caling this function.
2248 Make sure you have the working directory locked when caling this function.
2216 """
2249 """
2217 parent, p2 = parents
2250 parent, p2 = parents
2218 node = ctx.node()
2251 node = ctx.node()
2219 def checkout(f):
2252 def checkout(f):
2220 fc = ctx[f]
2253 fc = ctx[f]
2221 repo.wwrite(f, fc.data(), fc.flags())
2254 repo.wwrite(f, fc.data(), fc.flags())
2222
2255
2223 audit_path = pathutil.pathauditor(repo.root)
2256 audit_path = pathutil.pathauditor(repo.root)
2224 for f in remove[0]:
2257 for f in remove[0]:
2225 if repo.dirstate[f] == 'a':
2258 if repo.dirstate[f] == 'a':
2226 repo.dirstate.drop(f)
2259 repo.dirstate.drop(f)
2227 continue
2260 continue
2228 audit_path(f)
2261 audit_path(f)
2229 try:
2262 try:
2230 util.unlinkpath(repo.wjoin(f))
2263 util.unlinkpath(repo.wjoin(f))
2231 except OSError:
2264 except OSError:
2232 pass
2265 pass
2233 repo.dirstate.remove(f)
2266 repo.dirstate.remove(f)
2234
2267
2235 normal = None
2268 normal = None
2236 if node == parent:
2269 if node == parent:
2237 # We're reverting to our parent. If possible, we'd like status
2270 # We're reverting to our parent. If possible, we'd like status
2238 # to report the file as clean. We have to use normallookup for
2271 # to report the file as clean. We have to use normallookup for
2239 # merges to avoid losing information about merged/dirty files.
2272 # merges to avoid losing information about merged/dirty files.
2240 if p2 != nullid:
2273 if p2 != nullid:
2241 normal = repo.dirstate.normallookup
2274 normal = repo.dirstate.normallookup
2242 else:
2275 else:
2243 normal = repo.dirstate.normal
2276 normal = repo.dirstate.normal
2244 for f in revert[0]:
2277 for f in revert[0]:
2245 checkout(f)
2278 checkout(f)
2246 if normal:
2279 if normal:
2247 normal(f)
2280 normal(f)
2248
2281
2249 for f in add[0]:
2282 for f in add[0]:
2250 checkout(f)
2283 checkout(f)
2251 repo.dirstate.add(f)
2284 repo.dirstate.add(f)
2252
2285
2253 normal = repo.dirstate.normallookup
2286 normal = repo.dirstate.normallookup
2254 if node == parent and p2 == nullid:
2287 if node == parent and p2 == nullid:
2255 normal = repo.dirstate.normal
2288 normal = repo.dirstate.normal
2256 for f in undelete[0]:
2289 for f in undelete[0]:
2257 checkout(f)
2290 checkout(f)
2258 normal(f)
2291 normal(f)
2259
2292
2260 copied = copies.pathcopies(repo[parent], ctx)
2293 copied = copies.pathcopies(repo[parent], ctx)
2261
2294
2262 for f in add[0] + undelete[0] + revert[0]:
2295 for f in add[0] + undelete[0] + revert[0]:
2263 if f in copied:
2296 if f in copied:
2264 repo.dirstate.copy(copied[f], f)
2297 repo.dirstate.copy(copied[f], f)
2265
2298
2266 def command(table):
2299 def command(table):
2267 '''returns a function object bound to table which can be used as
2300 '''returns a function object bound to table which can be used as
2268 a decorator for populating table as a command table'''
2301 a decorator for populating table as a command table'''
2269
2302
2270 def cmd(name, options=(), synopsis=None):
2303 def cmd(name, options=(), synopsis=None):
2271 def decorator(func):
2304 def decorator(func):
2272 if synopsis:
2305 if synopsis:
2273 table[name] = func, list(options), synopsis
2306 table[name] = func, list(options), synopsis
2274 else:
2307 else:
2275 table[name] = func, list(options)
2308 table[name] = func, list(options)
2276 return func
2309 return func
2277 return decorator
2310 return decorator
2278
2311
2279 return cmd
2312 return cmd
2280
2313
2281 # a list of (ui, repo) functions called by commands.summary
2314 # a list of (ui, repo) functions called by commands.summary
2282 summaryhooks = util.hooks()
2315 summaryhooks = util.hooks()
2283
2316
2284 # A list of state files kept by multistep operations like graft.
2317 # A list of state files kept by multistep operations like graft.
2285 # Since graft cannot be aborted, it is considered 'clearable' by update.
2318 # Since graft cannot be aborted, it is considered 'clearable' by update.
2286 # note: bisect is intentionally excluded
2319 # note: bisect is intentionally excluded
2287 # (state file, clearable, allowcommit, error, hint)
2320 # (state file, clearable, allowcommit, error, hint)
2288 unfinishedstates = [
2321 unfinishedstates = [
2289 ('graftstate', True, False, _('graft in progress'),
2322 ('graftstate', True, False, _('graft in progress'),
2290 _("use 'hg graft --continue' or 'hg update' to abort")),
2323 _("use 'hg graft --continue' or 'hg update' to abort")),
2291 ('updatestate', True, False, _('last update was interrupted'),
2324 ('updatestate', True, False, _('last update was interrupted'),
2292 _("use 'hg update' to get a consistent checkout"))
2325 _("use 'hg update' to get a consistent checkout"))
2293 ]
2326 ]
2294
2327
2295 def checkunfinished(repo, commit=False):
2328 def checkunfinished(repo, commit=False):
2296 '''Look for an unfinished multistep operation, like graft, and abort
2329 '''Look for an unfinished multistep operation, like graft, and abort
2297 if found. It's probably good to check this right before
2330 if found. It's probably good to check this right before
2298 bailifchanged().
2331 bailifchanged().
2299 '''
2332 '''
2300 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2333 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2301 if commit and allowcommit:
2334 if commit and allowcommit:
2302 continue
2335 continue
2303 if repo.vfs.exists(f):
2336 if repo.vfs.exists(f):
2304 raise util.Abort(msg, hint=hint)
2337 raise util.Abort(msg, hint=hint)
2305
2338
2306 def clearunfinished(repo):
2339 def clearunfinished(repo):
2307 '''Check for unfinished operations (as above), and clear the ones
2340 '''Check for unfinished operations (as above), and clear the ones
2308 that are clearable.
2341 that are clearable.
2309 '''
2342 '''
2310 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2343 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2311 if not clearable and repo.vfs.exists(f):
2344 if not clearable and repo.vfs.exists(f):
2312 raise util.Abort(msg, hint=hint)
2345 raise util.Abort(msg, hint=hint)
2313 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2346 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2314 if clearable and repo.vfs.exists(f):
2347 if clearable and repo.vfs.exists(f):
2315 util.unlink(repo.join(f))
2348 util.unlink(repo.join(f))
@@ -1,1685 +1,1708 b''
1 $ hg init a
1 $ hg init a
2 $ cd a
2 $ cd a
3 $ echo a > a
3 $ echo a > a
4 $ hg add a
4 $ hg add a
5 $ echo line 1 > b
5 $ echo line 1 > b
6 $ echo line 2 >> b
6 $ echo line 2 >> b
7 $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
7 $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
8
8
9 $ hg add b
9 $ hg add b
10 $ echo other 1 > c
10 $ echo other 1 > c
11 $ echo other 2 >> c
11 $ echo other 2 >> c
12 $ echo >> c
12 $ echo >> c
13 $ echo other 3 >> c
13 $ echo other 3 >> c
14 $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
14 $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
15
15
16 $ hg add c
16 $ hg add c
17 $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
17 $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
18 $ echo c >> c
18 $ echo c >> c
19 $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
19 $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
20
20
21 $ echo foo > .hg/branch
21 $ echo foo > .hg/branch
22 $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
22 $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
23
23
24 $ hg co -q 3
24 $ hg co -q 3
25 $ echo other 4 >> d
25 $ echo other 4 >> d
26 $ hg add d
26 $ hg add d
27 $ hg commit -m 'new head' -d '1500000 0' -u 'person'
27 $ hg commit -m 'new head' -d '1500000 0' -u 'person'
28
28
29 $ hg merge -q foo
29 $ hg merge -q foo
30 $ hg commit -m 'merge' -d '1500001 0' -u 'person'
30 $ hg commit -m 'merge' -d '1500001 0' -u 'person'
31
31
32 Second branch starting at nullrev:
32 Second branch starting at nullrev:
33
33
34 $ hg update null
34 $ hg update null
35 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
35 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
36 $ echo second > second
36 $ echo second > second
37 $ hg add second
37 $ hg add second
38 $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
38 $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
39 created new head
39 created new head
40
40
41 $ echo third > third
41 $ echo third > third
42 $ hg add third
42 $ hg add third
43 $ hg mv second fourth
43 $ hg mv second fourth
44 $ hg commit -m third -d "2020-01-01 10:01"
44 $ hg commit -m third -d "2020-01-01 10:01"
45
45
46 $ hg log --template '{join(file_copies, ",\n")}\n' -r .
46 $ hg log --template '{join(file_copies, ",\n")}\n' -r .
47 fourth (second)
47 fourth (second)
48 $ hg log -T '{file_copies % "{source} -> {name}\n"}' -r .
48 $ hg log -T '{file_copies % "{source} -> {name}\n"}' -r .
49 second -> fourth
49 second -> fourth
50
50
51 Quoting for ui.logtemplate
51 Quoting for ui.logtemplate
52
52
53 $ hg tip --config "ui.logtemplate={rev}\n"
53 $ hg tip --config "ui.logtemplate={rev}\n"
54 8
54 8
55 $ hg tip --config "ui.logtemplate='{rev}\n'"
55 $ hg tip --config "ui.logtemplate='{rev}\n'"
56 8
56 8
57 $ hg tip --config 'ui.logtemplate="{rev}\n"'
57 $ hg tip --config 'ui.logtemplate="{rev}\n"'
58 8
58 8
59
59
60 Make sure user/global hgrc does not affect tests
60 Make sure user/global hgrc does not affect tests
61
61
62 $ echo '[ui]' > .hg/hgrc
62 $ echo '[ui]' > .hg/hgrc
63 $ echo 'logtemplate =' >> .hg/hgrc
63 $ echo 'logtemplate =' >> .hg/hgrc
64 $ echo 'style =' >> .hg/hgrc
64 $ echo 'style =' >> .hg/hgrc
65
65
66 Add some simple styles to settings
67
68 $ echo '[templates]' >> .hg/hgrc
69 $ printf 'simple = "{rev}\\n"\n' >> .hg/hgrc
70 $ printf 'simple2 = {rev}\\n\n' >> .hg/hgrc
71
72 $ hg log -l1 -Tsimple
73 8
74 $ hg log -l1 -Tsimple2
75 8
76
77 Test templates and style maps in files:
78
79 $ echo "{rev}" > tmpl
80 $ hg log -l1 -T./tmpl
81 8
82 $ hg log -l1 -Tblah/blah
83 blah/blah (no-eol)
84
85 $ printf 'changeset = "{rev}\\n"\n' > map-simple
86 $ hg log -l1 -T./map-simple
87 8
88
66 Default style is like normal output:
89 Default style is like normal output:
67
90
68 $ hg log > log.out
91 $ hg log > log.out
69 $ hg log --style default > style.out
92 $ hg log --style default > style.out
70 $ cmp log.out style.out || diff -u log.out style.out
93 $ cmp log.out style.out || diff -u log.out style.out
71
94
72 $ hg log -v > log.out
95 $ hg log -v > log.out
73 $ hg log -v --style default > style.out
96 $ hg log -v --style default > style.out
74 $ cmp log.out style.out || diff -u log.out style.out
97 $ cmp log.out style.out || diff -u log.out style.out
75
98
76 $ hg log --debug > log.out
99 $ hg log --debug > log.out
77 $ hg log --debug --style default > style.out
100 $ hg log --debug --style default > style.out
78 $ cmp log.out style.out || diff -u log.out style.out
101 $ cmp log.out style.out || diff -u log.out style.out
79
102
80 Revision with no copies (used to print a traceback):
103 Revision with no copies (used to print a traceback):
81
104
82 $ hg tip -v --template '\n'
105 $ hg tip -v --template '\n'
83
106
84
107
85 Compact style works:
108 Compact style works:
86
109
87 $ hg log --style compact
110 $ hg log -Tcompact
88 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
111 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
89 third
112 third
90
113
91 7:-1 29114dbae42b 1970-01-12 13:46 +0000 user
114 7:-1 29114dbae42b 1970-01-12 13:46 +0000 user
92 second
115 second
93
116
94 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
117 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
95 merge
118 merge
96
119
97 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
120 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
98 new head
121 new head
99
122
100 4 bbe44766e73d 1970-01-17 04:53 +0000 person
123 4 bbe44766e73d 1970-01-17 04:53 +0000 person
101 new branch
124 new branch
102
125
103 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
126 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
104 no user, no domain
127 no user, no domain
105
128
106 2 97054abb4ab8 1970-01-14 21:20 +0000 other
129 2 97054abb4ab8 1970-01-14 21:20 +0000 other
107 no person
130 no person
108
131
109 1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
132 1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
110 other 1
133 other 1
111
134
112 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
135 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
113 line 1
136 line 1
114
137
115
138
116 $ hg log -v --style compact
139 $ hg log -v --style compact
117 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
140 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
118 third
141 third
119
142
120 7:-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
143 7:-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
121 second
144 second
122
145
123 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
146 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
124 merge
147 merge
125
148
126 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
149 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
127 new head
150 new head
128
151
129 4 bbe44766e73d 1970-01-17 04:53 +0000 person
152 4 bbe44766e73d 1970-01-17 04:53 +0000 person
130 new branch
153 new branch
131
154
132 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
155 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
133 no user, no domain
156 no user, no domain
134
157
135 2 97054abb4ab8 1970-01-14 21:20 +0000 other@place
158 2 97054abb4ab8 1970-01-14 21:20 +0000 other@place
136 no person
159 no person
137
160
138 1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
161 1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
139 other 1
162 other 1
140 other 2
163 other 2
141
164
142 other 3
165 other 3
143
166
144 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
167 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
145 line 1
168 line 1
146 line 2
169 line 2
147
170
148
171
149 $ hg log --debug --style compact
172 $ hg log --debug --style compact
150 8[tip]:7,-1 95c24699272e 2020-01-01 10:01 +0000 test
173 8[tip]:7,-1 95c24699272e 2020-01-01 10:01 +0000 test
151 third
174 third
152
175
153 7:-1,-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
176 7:-1,-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
154 second
177 second
155
178
156 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
179 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
157 merge
180 merge
158
181
159 5:3,-1 13207e5a10d9 1970-01-18 08:40 +0000 person
182 5:3,-1 13207e5a10d9 1970-01-18 08:40 +0000 person
160 new head
183 new head
161
184
162 4:3,-1 bbe44766e73d 1970-01-17 04:53 +0000 person
185 4:3,-1 bbe44766e73d 1970-01-17 04:53 +0000 person
163 new branch
186 new branch
164
187
165 3:2,-1 10e46f2dcbf4 1970-01-16 01:06 +0000 person
188 3:2,-1 10e46f2dcbf4 1970-01-16 01:06 +0000 person
166 no user, no domain
189 no user, no domain
167
190
168 2:1,-1 97054abb4ab8 1970-01-14 21:20 +0000 other@place
191 2:1,-1 97054abb4ab8 1970-01-14 21:20 +0000 other@place
169 no person
192 no person
170
193
171 1:0,-1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
194 1:0,-1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
172 other 1
195 other 1
173 other 2
196 other 2
174
197
175 other 3
198 other 3
176
199
177 0:-1,-1 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
200 0:-1,-1 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
178 line 1
201 line 1
179 line 2
202 line 2
180
203
181
204
182 Test xml styles:
205 Test xml styles:
183
206
184 $ hg log --style xml
207 $ hg log --style xml
185 <?xml version="1.0"?>
208 <?xml version="1.0"?>
186 <log>
209 <log>
187 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
210 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
188 <tag>tip</tag>
211 <tag>tip</tag>
189 <author email="test">test</author>
212 <author email="test">test</author>
190 <date>2020-01-01T10:01:00+00:00</date>
213 <date>2020-01-01T10:01:00+00:00</date>
191 <msg xml:space="preserve">third</msg>
214 <msg xml:space="preserve">third</msg>
192 </logentry>
215 </logentry>
193 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
216 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
194 <parent revision="-1" node="0000000000000000000000000000000000000000" />
217 <parent revision="-1" node="0000000000000000000000000000000000000000" />
195 <author email="user@hostname">User Name</author>
218 <author email="user@hostname">User Name</author>
196 <date>1970-01-12T13:46:40+00:00</date>
219 <date>1970-01-12T13:46:40+00:00</date>
197 <msg xml:space="preserve">second</msg>
220 <msg xml:space="preserve">second</msg>
198 </logentry>
221 </logentry>
199 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
222 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
200 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
223 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
201 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
224 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
202 <author email="person">person</author>
225 <author email="person">person</author>
203 <date>1970-01-18T08:40:01+00:00</date>
226 <date>1970-01-18T08:40:01+00:00</date>
204 <msg xml:space="preserve">merge</msg>
227 <msg xml:space="preserve">merge</msg>
205 </logentry>
228 </logentry>
206 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
229 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
207 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
230 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
208 <author email="person">person</author>
231 <author email="person">person</author>
209 <date>1970-01-18T08:40:00+00:00</date>
232 <date>1970-01-18T08:40:00+00:00</date>
210 <msg xml:space="preserve">new head</msg>
233 <msg xml:space="preserve">new head</msg>
211 </logentry>
234 </logentry>
212 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
235 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
213 <branch>foo</branch>
236 <branch>foo</branch>
214 <author email="person">person</author>
237 <author email="person">person</author>
215 <date>1970-01-17T04:53:20+00:00</date>
238 <date>1970-01-17T04:53:20+00:00</date>
216 <msg xml:space="preserve">new branch</msg>
239 <msg xml:space="preserve">new branch</msg>
217 </logentry>
240 </logentry>
218 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
241 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
219 <author email="person">person</author>
242 <author email="person">person</author>
220 <date>1970-01-16T01:06:40+00:00</date>
243 <date>1970-01-16T01:06:40+00:00</date>
221 <msg xml:space="preserve">no user, no domain</msg>
244 <msg xml:space="preserve">no user, no domain</msg>
222 </logentry>
245 </logentry>
223 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
246 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
224 <author email="other@place">other</author>
247 <author email="other@place">other</author>
225 <date>1970-01-14T21:20:00+00:00</date>
248 <date>1970-01-14T21:20:00+00:00</date>
226 <msg xml:space="preserve">no person</msg>
249 <msg xml:space="preserve">no person</msg>
227 </logentry>
250 </logentry>
228 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
251 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
229 <author email="other@place">A. N. Other</author>
252 <author email="other@place">A. N. Other</author>
230 <date>1970-01-13T17:33:20+00:00</date>
253 <date>1970-01-13T17:33:20+00:00</date>
231 <msg xml:space="preserve">other 1
254 <msg xml:space="preserve">other 1
232 other 2
255 other 2
233
256
234 other 3</msg>
257 other 3</msg>
235 </logentry>
258 </logentry>
236 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
259 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
237 <author email="user@hostname">User Name</author>
260 <author email="user@hostname">User Name</author>
238 <date>1970-01-12T13:46:40+00:00</date>
261 <date>1970-01-12T13:46:40+00:00</date>
239 <msg xml:space="preserve">line 1
262 <msg xml:space="preserve">line 1
240 line 2</msg>
263 line 2</msg>
241 </logentry>
264 </logentry>
242 </log>
265 </log>
243
266
244 $ hg log -v --style xml
267 $ hg log -v --style xml
245 <?xml version="1.0"?>
268 <?xml version="1.0"?>
246 <log>
269 <log>
247 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
270 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
248 <tag>tip</tag>
271 <tag>tip</tag>
249 <author email="test">test</author>
272 <author email="test">test</author>
250 <date>2020-01-01T10:01:00+00:00</date>
273 <date>2020-01-01T10:01:00+00:00</date>
251 <msg xml:space="preserve">third</msg>
274 <msg xml:space="preserve">third</msg>
252 <paths>
275 <paths>
253 <path action="A">fourth</path>
276 <path action="A">fourth</path>
254 <path action="A">third</path>
277 <path action="A">third</path>
255 <path action="R">second</path>
278 <path action="R">second</path>
256 </paths>
279 </paths>
257 <copies>
280 <copies>
258 <copy source="second">fourth</copy>
281 <copy source="second">fourth</copy>
259 </copies>
282 </copies>
260 </logentry>
283 </logentry>
261 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
284 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
262 <parent revision="-1" node="0000000000000000000000000000000000000000" />
285 <parent revision="-1" node="0000000000000000000000000000000000000000" />
263 <author email="user@hostname">User Name</author>
286 <author email="user@hostname">User Name</author>
264 <date>1970-01-12T13:46:40+00:00</date>
287 <date>1970-01-12T13:46:40+00:00</date>
265 <msg xml:space="preserve">second</msg>
288 <msg xml:space="preserve">second</msg>
266 <paths>
289 <paths>
267 <path action="A">second</path>
290 <path action="A">second</path>
268 </paths>
291 </paths>
269 </logentry>
292 </logentry>
270 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
293 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
271 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
294 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
272 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
295 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
273 <author email="person">person</author>
296 <author email="person">person</author>
274 <date>1970-01-18T08:40:01+00:00</date>
297 <date>1970-01-18T08:40:01+00:00</date>
275 <msg xml:space="preserve">merge</msg>
298 <msg xml:space="preserve">merge</msg>
276 <paths>
299 <paths>
277 </paths>
300 </paths>
278 </logentry>
301 </logentry>
279 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
302 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
280 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
303 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
281 <author email="person">person</author>
304 <author email="person">person</author>
282 <date>1970-01-18T08:40:00+00:00</date>
305 <date>1970-01-18T08:40:00+00:00</date>
283 <msg xml:space="preserve">new head</msg>
306 <msg xml:space="preserve">new head</msg>
284 <paths>
307 <paths>
285 <path action="A">d</path>
308 <path action="A">d</path>
286 </paths>
309 </paths>
287 </logentry>
310 </logentry>
288 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
311 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
289 <branch>foo</branch>
312 <branch>foo</branch>
290 <author email="person">person</author>
313 <author email="person">person</author>
291 <date>1970-01-17T04:53:20+00:00</date>
314 <date>1970-01-17T04:53:20+00:00</date>
292 <msg xml:space="preserve">new branch</msg>
315 <msg xml:space="preserve">new branch</msg>
293 <paths>
316 <paths>
294 </paths>
317 </paths>
295 </logentry>
318 </logentry>
296 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
319 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
297 <author email="person">person</author>
320 <author email="person">person</author>
298 <date>1970-01-16T01:06:40+00:00</date>
321 <date>1970-01-16T01:06:40+00:00</date>
299 <msg xml:space="preserve">no user, no domain</msg>
322 <msg xml:space="preserve">no user, no domain</msg>
300 <paths>
323 <paths>
301 <path action="M">c</path>
324 <path action="M">c</path>
302 </paths>
325 </paths>
303 </logentry>
326 </logentry>
304 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
327 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
305 <author email="other@place">other</author>
328 <author email="other@place">other</author>
306 <date>1970-01-14T21:20:00+00:00</date>
329 <date>1970-01-14T21:20:00+00:00</date>
307 <msg xml:space="preserve">no person</msg>
330 <msg xml:space="preserve">no person</msg>
308 <paths>
331 <paths>
309 <path action="A">c</path>
332 <path action="A">c</path>
310 </paths>
333 </paths>
311 </logentry>
334 </logentry>
312 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
335 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
313 <author email="other@place">A. N. Other</author>
336 <author email="other@place">A. N. Other</author>
314 <date>1970-01-13T17:33:20+00:00</date>
337 <date>1970-01-13T17:33:20+00:00</date>
315 <msg xml:space="preserve">other 1
338 <msg xml:space="preserve">other 1
316 other 2
339 other 2
317
340
318 other 3</msg>
341 other 3</msg>
319 <paths>
342 <paths>
320 <path action="A">b</path>
343 <path action="A">b</path>
321 </paths>
344 </paths>
322 </logentry>
345 </logentry>
323 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
346 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
324 <author email="user@hostname">User Name</author>
347 <author email="user@hostname">User Name</author>
325 <date>1970-01-12T13:46:40+00:00</date>
348 <date>1970-01-12T13:46:40+00:00</date>
326 <msg xml:space="preserve">line 1
349 <msg xml:space="preserve">line 1
327 line 2</msg>
350 line 2</msg>
328 <paths>
351 <paths>
329 <path action="A">a</path>
352 <path action="A">a</path>
330 </paths>
353 </paths>
331 </logentry>
354 </logentry>
332 </log>
355 </log>
333
356
334 $ hg log --debug --style xml
357 $ hg log --debug --style xml
335 <?xml version="1.0"?>
358 <?xml version="1.0"?>
336 <log>
359 <log>
337 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
360 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
338 <tag>tip</tag>
361 <tag>tip</tag>
339 <parent revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453" />
362 <parent revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453" />
340 <parent revision="-1" node="0000000000000000000000000000000000000000" />
363 <parent revision="-1" node="0000000000000000000000000000000000000000" />
341 <author email="test">test</author>
364 <author email="test">test</author>
342 <date>2020-01-01T10:01:00+00:00</date>
365 <date>2020-01-01T10:01:00+00:00</date>
343 <msg xml:space="preserve">third</msg>
366 <msg xml:space="preserve">third</msg>
344 <paths>
367 <paths>
345 <path action="A">fourth</path>
368 <path action="A">fourth</path>
346 <path action="A">third</path>
369 <path action="A">third</path>
347 <path action="R">second</path>
370 <path action="R">second</path>
348 </paths>
371 </paths>
349 <copies>
372 <copies>
350 <copy source="second">fourth</copy>
373 <copy source="second">fourth</copy>
351 </copies>
374 </copies>
352 <extra key="branch">default</extra>
375 <extra key="branch">default</extra>
353 </logentry>
376 </logentry>
354 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
377 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
355 <parent revision="-1" node="0000000000000000000000000000000000000000" />
378 <parent revision="-1" node="0000000000000000000000000000000000000000" />
356 <parent revision="-1" node="0000000000000000000000000000000000000000" />
379 <parent revision="-1" node="0000000000000000000000000000000000000000" />
357 <author email="user@hostname">User Name</author>
380 <author email="user@hostname">User Name</author>
358 <date>1970-01-12T13:46:40+00:00</date>
381 <date>1970-01-12T13:46:40+00:00</date>
359 <msg xml:space="preserve">second</msg>
382 <msg xml:space="preserve">second</msg>
360 <paths>
383 <paths>
361 <path action="A">second</path>
384 <path action="A">second</path>
362 </paths>
385 </paths>
363 <extra key="branch">default</extra>
386 <extra key="branch">default</extra>
364 </logentry>
387 </logentry>
365 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
388 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
366 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
389 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
367 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
390 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
368 <author email="person">person</author>
391 <author email="person">person</author>
369 <date>1970-01-18T08:40:01+00:00</date>
392 <date>1970-01-18T08:40:01+00:00</date>
370 <msg xml:space="preserve">merge</msg>
393 <msg xml:space="preserve">merge</msg>
371 <paths>
394 <paths>
372 </paths>
395 </paths>
373 <extra key="branch">default</extra>
396 <extra key="branch">default</extra>
374 </logentry>
397 </logentry>
375 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
398 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
376 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
399 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
377 <parent revision="-1" node="0000000000000000000000000000000000000000" />
400 <parent revision="-1" node="0000000000000000000000000000000000000000" />
378 <author email="person">person</author>
401 <author email="person">person</author>
379 <date>1970-01-18T08:40:00+00:00</date>
402 <date>1970-01-18T08:40:00+00:00</date>
380 <msg xml:space="preserve">new head</msg>
403 <msg xml:space="preserve">new head</msg>
381 <paths>
404 <paths>
382 <path action="A">d</path>
405 <path action="A">d</path>
383 </paths>
406 </paths>
384 <extra key="branch">default</extra>
407 <extra key="branch">default</extra>
385 </logentry>
408 </logentry>
386 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
409 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
387 <branch>foo</branch>
410 <branch>foo</branch>
388 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
411 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
389 <parent revision="-1" node="0000000000000000000000000000000000000000" />
412 <parent revision="-1" node="0000000000000000000000000000000000000000" />
390 <author email="person">person</author>
413 <author email="person">person</author>
391 <date>1970-01-17T04:53:20+00:00</date>
414 <date>1970-01-17T04:53:20+00:00</date>
392 <msg xml:space="preserve">new branch</msg>
415 <msg xml:space="preserve">new branch</msg>
393 <paths>
416 <paths>
394 </paths>
417 </paths>
395 <extra key="branch">foo</extra>
418 <extra key="branch">foo</extra>
396 </logentry>
419 </logentry>
397 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
420 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
398 <parent revision="2" node="97054abb4ab824450e9164180baf491ae0078465" />
421 <parent revision="2" node="97054abb4ab824450e9164180baf491ae0078465" />
399 <parent revision="-1" node="0000000000000000000000000000000000000000" />
422 <parent revision="-1" node="0000000000000000000000000000000000000000" />
400 <author email="person">person</author>
423 <author email="person">person</author>
401 <date>1970-01-16T01:06:40+00:00</date>
424 <date>1970-01-16T01:06:40+00:00</date>
402 <msg xml:space="preserve">no user, no domain</msg>
425 <msg xml:space="preserve">no user, no domain</msg>
403 <paths>
426 <paths>
404 <path action="M">c</path>
427 <path action="M">c</path>
405 </paths>
428 </paths>
406 <extra key="branch">default</extra>
429 <extra key="branch">default</extra>
407 </logentry>
430 </logentry>
408 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
431 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
409 <parent revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965" />
432 <parent revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965" />
410 <parent revision="-1" node="0000000000000000000000000000000000000000" />
433 <parent revision="-1" node="0000000000000000000000000000000000000000" />
411 <author email="other@place">other</author>
434 <author email="other@place">other</author>
412 <date>1970-01-14T21:20:00+00:00</date>
435 <date>1970-01-14T21:20:00+00:00</date>
413 <msg xml:space="preserve">no person</msg>
436 <msg xml:space="preserve">no person</msg>
414 <paths>
437 <paths>
415 <path action="A">c</path>
438 <path action="A">c</path>
416 </paths>
439 </paths>
417 <extra key="branch">default</extra>
440 <extra key="branch">default</extra>
418 </logentry>
441 </logentry>
419 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
442 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
420 <parent revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f" />
443 <parent revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f" />
421 <parent revision="-1" node="0000000000000000000000000000000000000000" />
444 <parent revision="-1" node="0000000000000000000000000000000000000000" />
422 <author email="other@place">A. N. Other</author>
445 <author email="other@place">A. N. Other</author>
423 <date>1970-01-13T17:33:20+00:00</date>
446 <date>1970-01-13T17:33:20+00:00</date>
424 <msg xml:space="preserve">other 1
447 <msg xml:space="preserve">other 1
425 other 2
448 other 2
426
449
427 other 3</msg>
450 other 3</msg>
428 <paths>
451 <paths>
429 <path action="A">b</path>
452 <path action="A">b</path>
430 </paths>
453 </paths>
431 <extra key="branch">default</extra>
454 <extra key="branch">default</extra>
432 </logentry>
455 </logentry>
433 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
456 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
434 <parent revision="-1" node="0000000000000000000000000000000000000000" />
457 <parent revision="-1" node="0000000000000000000000000000000000000000" />
435 <parent revision="-1" node="0000000000000000000000000000000000000000" />
458 <parent revision="-1" node="0000000000000000000000000000000000000000" />
436 <author email="user@hostname">User Name</author>
459 <author email="user@hostname">User Name</author>
437 <date>1970-01-12T13:46:40+00:00</date>
460 <date>1970-01-12T13:46:40+00:00</date>
438 <msg xml:space="preserve">line 1
461 <msg xml:space="preserve">line 1
439 line 2</msg>
462 line 2</msg>
440 <paths>
463 <paths>
441 <path action="A">a</path>
464 <path action="A">a</path>
442 </paths>
465 </paths>
443 <extra key="branch">default</extra>
466 <extra key="branch">default</extra>
444 </logentry>
467 </logentry>
445 </log>
468 </log>
446
469
447
470
448 Error if style not readable:
471 Error if style not readable:
449
472
450 #if unix-permissions no-root
473 #if unix-permissions no-root
451 $ touch q
474 $ touch q
452 $ chmod 0 q
475 $ chmod 0 q
453 $ hg log --style ./q
476 $ hg log --style ./q
454 abort: Permission denied: ./q
477 abort: Permission denied: ./q
455 [255]
478 [255]
456 #endif
479 #endif
457
480
458 Error if no style:
481 Error if no style:
459
482
460 $ hg log --style notexist
483 $ hg log --style notexist
461 abort: style 'notexist' not found
484 abort: style 'notexist' not found
462 (available styles: bisect, changelog, compact, default, phases, xml)
485 (available styles: bisect, changelog, compact, default, phases, xml)
463 [255]
486 [255]
464
487
465 Error if style missing key:
488 Error if style missing key:
466
489
467 $ echo 'q = q' > t
490 $ echo 'q = q' > t
468 $ hg log --style ./t
491 $ hg log --style ./t
469 abort: "changeset" not in template map
492 abort: "changeset" not in template map
470 [255]
493 [255]
471
494
472 Error if style missing value:
495 Error if style missing value:
473
496
474 $ echo 'changeset =' > t
497 $ echo 'changeset =' > t
475 $ hg log --style t
498 $ hg log --style t
476 abort: t:1: missing value
499 abort: t:1: missing value
477 [255]
500 [255]
478
501
479 Error if include fails:
502 Error if include fails:
480
503
481 $ echo 'changeset = q' >> t
504 $ echo 'changeset = q' >> t
482 #if unix-permissions no-root
505 #if unix-permissions no-root
483 $ hg log --style ./t
506 $ hg log --style ./t
484 abort: template file ./q: Permission denied
507 abort: template file ./q: Permission denied
485 [255]
508 [255]
486 $ rm q
509 $ rm q
487 #endif
510 #endif
488
511
489 Include works:
512 Include works:
490
513
491 $ echo '{rev}' > q
514 $ echo '{rev}' > q
492 $ hg log --style ./t
515 $ hg log --style ./t
493 8
516 8
494 7
517 7
495 6
518 6
496 5
519 5
497 4
520 4
498 3
521 3
499 2
522 2
500 1
523 1
501 0
524 0
502
525
503 Missing non-standard names give no error (backward compatibility):
526 Missing non-standard names give no error (backward compatibility):
504
527
505 $ echo "changeset = '{c}'" > t
528 $ echo "changeset = '{c}'" > t
506 $ hg log --style ./t
529 $ hg log --style ./t
507
530
508 Defining non-standard name works:
531 Defining non-standard name works:
509
532
510 $ cat <<EOF > t
533 $ cat <<EOF > t
511 > changeset = '{c}'
534 > changeset = '{c}'
512 > c = q
535 > c = q
513 > EOF
536 > EOF
514 $ hg log --style ./t
537 $ hg log --style ./t
515 8
538 8
516 7
539 7
517 6
540 6
518 5
541 5
519 4
542 4
520 3
543 3
521 2
544 2
522 1
545 1
523 0
546 0
524
547
525 ui.style works:
548 ui.style works:
526
549
527 $ echo '[ui]' > .hg/hgrc
550 $ echo '[ui]' > .hg/hgrc
528 $ echo 'style = t' >> .hg/hgrc
551 $ echo 'style = t' >> .hg/hgrc
529 $ hg log
552 $ hg log
530 8
553 8
531 7
554 7
532 6
555 6
533 5
556 5
534 4
557 4
535 3
558 3
536 2
559 2
537 1
560 1
538 0
561 0
539
562
540
563
541 Issue338:
564 Issue338:
542
565
543 $ hg log --style=changelog > changelog
566 $ hg log --style=changelog > changelog
544
567
545 $ cat changelog
568 $ cat changelog
546 2020-01-01 test <test>
569 2020-01-01 test <test>
547
570
548 * fourth, second, third:
571 * fourth, second, third:
549 third
572 third
550 [95c24699272e] [tip]
573 [95c24699272e] [tip]
551
574
552 1970-01-12 User Name <user@hostname>
575 1970-01-12 User Name <user@hostname>
553
576
554 * second:
577 * second:
555 second
578 second
556 [29114dbae42b]
579 [29114dbae42b]
557
580
558 1970-01-18 person <person>
581 1970-01-18 person <person>
559
582
560 * merge
583 * merge
561 [d41e714fe50d]
584 [d41e714fe50d]
562
585
563 * d:
586 * d:
564 new head
587 new head
565 [13207e5a10d9]
588 [13207e5a10d9]
566
589
567 1970-01-17 person <person>
590 1970-01-17 person <person>
568
591
569 * new branch
592 * new branch
570 [bbe44766e73d] <foo>
593 [bbe44766e73d] <foo>
571
594
572 1970-01-16 person <person>
595 1970-01-16 person <person>
573
596
574 * c:
597 * c:
575 no user, no domain
598 no user, no domain
576 [10e46f2dcbf4]
599 [10e46f2dcbf4]
577
600
578 1970-01-14 other <other@place>
601 1970-01-14 other <other@place>
579
602
580 * c:
603 * c:
581 no person
604 no person
582 [97054abb4ab8]
605 [97054abb4ab8]
583
606
584 1970-01-13 A. N. Other <other@place>
607 1970-01-13 A. N. Other <other@place>
585
608
586 * b:
609 * b:
587 other 1 other 2
610 other 1 other 2
588
611
589 other 3
612 other 3
590 [b608e9d1a3f0]
613 [b608e9d1a3f0]
591
614
592 1970-01-12 User Name <user@hostname>
615 1970-01-12 User Name <user@hostname>
593
616
594 * a:
617 * a:
595 line 1 line 2
618 line 1 line 2
596 [1e4e1b8f71e0]
619 [1e4e1b8f71e0]
597
620
598
621
599 Issue2130: xml output for 'hg heads' is malformed
622 Issue2130: xml output for 'hg heads' is malformed
600
623
601 $ hg heads --style changelog
624 $ hg heads --style changelog
602 2020-01-01 test <test>
625 2020-01-01 test <test>
603
626
604 * fourth, second, third:
627 * fourth, second, third:
605 third
628 third
606 [95c24699272e] [tip]
629 [95c24699272e] [tip]
607
630
608 1970-01-18 person <person>
631 1970-01-18 person <person>
609
632
610 * merge
633 * merge
611 [d41e714fe50d]
634 [d41e714fe50d]
612
635
613 1970-01-17 person <person>
636 1970-01-17 person <person>
614
637
615 * new branch
638 * new branch
616 [bbe44766e73d] <foo>
639 [bbe44766e73d] <foo>
617
640
618
641
619 Keys work:
642 Keys work:
620
643
621 $ for key in author branch branches date desc file_adds file_dels file_mods \
644 $ for key in author branch branches date desc file_adds file_dels file_mods \
622 > file_copies file_copies_switch files \
645 > file_copies file_copies_switch files \
623 > manifest node parents rev tags diffstat extras \
646 > manifest node parents rev tags diffstat extras \
624 > p1rev p2rev p1node p2node; do
647 > p1rev p2rev p1node p2node; do
625 > for mode in '' --verbose --debug; do
648 > for mode in '' --verbose --debug; do
626 > hg log $mode --template "$key$mode: {$key}\n"
649 > hg log $mode --template "$key$mode: {$key}\n"
627 > done
650 > done
628 > done
651 > done
629 author: test
652 author: test
630 author: User Name <user@hostname>
653 author: User Name <user@hostname>
631 author: person
654 author: person
632 author: person
655 author: person
633 author: person
656 author: person
634 author: person
657 author: person
635 author: other@place
658 author: other@place
636 author: A. N. Other <other@place>
659 author: A. N. Other <other@place>
637 author: User Name <user@hostname>
660 author: User Name <user@hostname>
638 author--verbose: test
661 author--verbose: test
639 author--verbose: User Name <user@hostname>
662 author--verbose: User Name <user@hostname>
640 author--verbose: person
663 author--verbose: person
641 author--verbose: person
664 author--verbose: person
642 author--verbose: person
665 author--verbose: person
643 author--verbose: person
666 author--verbose: person
644 author--verbose: other@place
667 author--verbose: other@place
645 author--verbose: A. N. Other <other@place>
668 author--verbose: A. N. Other <other@place>
646 author--verbose: User Name <user@hostname>
669 author--verbose: User Name <user@hostname>
647 author--debug: test
670 author--debug: test
648 author--debug: User Name <user@hostname>
671 author--debug: User Name <user@hostname>
649 author--debug: person
672 author--debug: person
650 author--debug: person
673 author--debug: person
651 author--debug: person
674 author--debug: person
652 author--debug: person
675 author--debug: person
653 author--debug: other@place
676 author--debug: other@place
654 author--debug: A. N. Other <other@place>
677 author--debug: A. N. Other <other@place>
655 author--debug: User Name <user@hostname>
678 author--debug: User Name <user@hostname>
656 branch: default
679 branch: default
657 branch: default
680 branch: default
658 branch: default
681 branch: default
659 branch: default
682 branch: default
660 branch: foo
683 branch: foo
661 branch: default
684 branch: default
662 branch: default
685 branch: default
663 branch: default
686 branch: default
664 branch: default
687 branch: default
665 branch--verbose: default
688 branch--verbose: default
666 branch--verbose: default
689 branch--verbose: default
667 branch--verbose: default
690 branch--verbose: default
668 branch--verbose: default
691 branch--verbose: default
669 branch--verbose: foo
692 branch--verbose: foo
670 branch--verbose: default
693 branch--verbose: default
671 branch--verbose: default
694 branch--verbose: default
672 branch--verbose: default
695 branch--verbose: default
673 branch--verbose: default
696 branch--verbose: default
674 branch--debug: default
697 branch--debug: default
675 branch--debug: default
698 branch--debug: default
676 branch--debug: default
699 branch--debug: default
677 branch--debug: default
700 branch--debug: default
678 branch--debug: foo
701 branch--debug: foo
679 branch--debug: default
702 branch--debug: default
680 branch--debug: default
703 branch--debug: default
681 branch--debug: default
704 branch--debug: default
682 branch--debug: default
705 branch--debug: default
683 branches:
706 branches:
684 branches:
707 branches:
685 branches:
708 branches:
686 branches:
709 branches:
687 branches: foo
710 branches: foo
688 branches:
711 branches:
689 branches:
712 branches:
690 branches:
713 branches:
691 branches:
714 branches:
692 branches--verbose:
715 branches--verbose:
693 branches--verbose:
716 branches--verbose:
694 branches--verbose:
717 branches--verbose:
695 branches--verbose:
718 branches--verbose:
696 branches--verbose: foo
719 branches--verbose: foo
697 branches--verbose:
720 branches--verbose:
698 branches--verbose:
721 branches--verbose:
699 branches--verbose:
722 branches--verbose:
700 branches--verbose:
723 branches--verbose:
701 branches--debug:
724 branches--debug:
702 branches--debug:
725 branches--debug:
703 branches--debug:
726 branches--debug:
704 branches--debug:
727 branches--debug:
705 branches--debug: foo
728 branches--debug: foo
706 branches--debug:
729 branches--debug:
707 branches--debug:
730 branches--debug:
708 branches--debug:
731 branches--debug:
709 branches--debug:
732 branches--debug:
710 date: 1577872860.00
733 date: 1577872860.00
711 date: 1000000.00
734 date: 1000000.00
712 date: 1500001.00
735 date: 1500001.00
713 date: 1500000.00
736 date: 1500000.00
714 date: 1400000.00
737 date: 1400000.00
715 date: 1300000.00
738 date: 1300000.00
716 date: 1200000.00
739 date: 1200000.00
717 date: 1100000.00
740 date: 1100000.00
718 date: 1000000.00
741 date: 1000000.00
719 date--verbose: 1577872860.00
742 date--verbose: 1577872860.00
720 date--verbose: 1000000.00
743 date--verbose: 1000000.00
721 date--verbose: 1500001.00
744 date--verbose: 1500001.00
722 date--verbose: 1500000.00
745 date--verbose: 1500000.00
723 date--verbose: 1400000.00
746 date--verbose: 1400000.00
724 date--verbose: 1300000.00
747 date--verbose: 1300000.00
725 date--verbose: 1200000.00
748 date--verbose: 1200000.00
726 date--verbose: 1100000.00
749 date--verbose: 1100000.00
727 date--verbose: 1000000.00
750 date--verbose: 1000000.00
728 date--debug: 1577872860.00
751 date--debug: 1577872860.00
729 date--debug: 1000000.00
752 date--debug: 1000000.00
730 date--debug: 1500001.00
753 date--debug: 1500001.00
731 date--debug: 1500000.00
754 date--debug: 1500000.00
732 date--debug: 1400000.00
755 date--debug: 1400000.00
733 date--debug: 1300000.00
756 date--debug: 1300000.00
734 date--debug: 1200000.00
757 date--debug: 1200000.00
735 date--debug: 1100000.00
758 date--debug: 1100000.00
736 date--debug: 1000000.00
759 date--debug: 1000000.00
737 desc: third
760 desc: third
738 desc: second
761 desc: second
739 desc: merge
762 desc: merge
740 desc: new head
763 desc: new head
741 desc: new branch
764 desc: new branch
742 desc: no user, no domain
765 desc: no user, no domain
743 desc: no person
766 desc: no person
744 desc: other 1
767 desc: other 1
745 other 2
768 other 2
746
769
747 other 3
770 other 3
748 desc: line 1
771 desc: line 1
749 line 2
772 line 2
750 desc--verbose: third
773 desc--verbose: third
751 desc--verbose: second
774 desc--verbose: second
752 desc--verbose: merge
775 desc--verbose: merge
753 desc--verbose: new head
776 desc--verbose: new head
754 desc--verbose: new branch
777 desc--verbose: new branch
755 desc--verbose: no user, no domain
778 desc--verbose: no user, no domain
756 desc--verbose: no person
779 desc--verbose: no person
757 desc--verbose: other 1
780 desc--verbose: other 1
758 other 2
781 other 2
759
782
760 other 3
783 other 3
761 desc--verbose: line 1
784 desc--verbose: line 1
762 line 2
785 line 2
763 desc--debug: third
786 desc--debug: third
764 desc--debug: second
787 desc--debug: second
765 desc--debug: merge
788 desc--debug: merge
766 desc--debug: new head
789 desc--debug: new head
767 desc--debug: new branch
790 desc--debug: new branch
768 desc--debug: no user, no domain
791 desc--debug: no user, no domain
769 desc--debug: no person
792 desc--debug: no person
770 desc--debug: other 1
793 desc--debug: other 1
771 other 2
794 other 2
772
795
773 other 3
796 other 3
774 desc--debug: line 1
797 desc--debug: line 1
775 line 2
798 line 2
776 file_adds: fourth third
799 file_adds: fourth third
777 file_adds: second
800 file_adds: second
778 file_adds:
801 file_adds:
779 file_adds: d
802 file_adds: d
780 file_adds:
803 file_adds:
781 file_adds:
804 file_adds:
782 file_adds: c
805 file_adds: c
783 file_adds: b
806 file_adds: b
784 file_adds: a
807 file_adds: a
785 file_adds--verbose: fourth third
808 file_adds--verbose: fourth third
786 file_adds--verbose: second
809 file_adds--verbose: second
787 file_adds--verbose:
810 file_adds--verbose:
788 file_adds--verbose: d
811 file_adds--verbose: d
789 file_adds--verbose:
812 file_adds--verbose:
790 file_adds--verbose:
813 file_adds--verbose:
791 file_adds--verbose: c
814 file_adds--verbose: c
792 file_adds--verbose: b
815 file_adds--verbose: b
793 file_adds--verbose: a
816 file_adds--verbose: a
794 file_adds--debug: fourth third
817 file_adds--debug: fourth third
795 file_adds--debug: second
818 file_adds--debug: second
796 file_adds--debug:
819 file_adds--debug:
797 file_adds--debug: d
820 file_adds--debug: d
798 file_adds--debug:
821 file_adds--debug:
799 file_adds--debug:
822 file_adds--debug:
800 file_adds--debug: c
823 file_adds--debug: c
801 file_adds--debug: b
824 file_adds--debug: b
802 file_adds--debug: a
825 file_adds--debug: a
803 file_dels: second
826 file_dels: second
804 file_dels:
827 file_dels:
805 file_dels:
828 file_dels:
806 file_dels:
829 file_dels:
807 file_dels:
830 file_dels:
808 file_dels:
831 file_dels:
809 file_dels:
832 file_dels:
810 file_dels:
833 file_dels:
811 file_dels:
834 file_dels:
812 file_dels--verbose: second
835 file_dels--verbose: second
813 file_dels--verbose:
836 file_dels--verbose:
814 file_dels--verbose:
837 file_dels--verbose:
815 file_dels--verbose:
838 file_dels--verbose:
816 file_dels--verbose:
839 file_dels--verbose:
817 file_dels--verbose:
840 file_dels--verbose:
818 file_dels--verbose:
841 file_dels--verbose:
819 file_dels--verbose:
842 file_dels--verbose:
820 file_dels--verbose:
843 file_dels--verbose:
821 file_dels--debug: second
844 file_dels--debug: second
822 file_dels--debug:
845 file_dels--debug:
823 file_dels--debug:
846 file_dels--debug:
824 file_dels--debug:
847 file_dels--debug:
825 file_dels--debug:
848 file_dels--debug:
826 file_dels--debug:
849 file_dels--debug:
827 file_dels--debug:
850 file_dels--debug:
828 file_dels--debug:
851 file_dels--debug:
829 file_dels--debug:
852 file_dels--debug:
830 file_mods:
853 file_mods:
831 file_mods:
854 file_mods:
832 file_mods:
855 file_mods:
833 file_mods:
856 file_mods:
834 file_mods:
857 file_mods:
835 file_mods: c
858 file_mods: c
836 file_mods:
859 file_mods:
837 file_mods:
860 file_mods:
838 file_mods:
861 file_mods:
839 file_mods--verbose:
862 file_mods--verbose:
840 file_mods--verbose:
863 file_mods--verbose:
841 file_mods--verbose:
864 file_mods--verbose:
842 file_mods--verbose:
865 file_mods--verbose:
843 file_mods--verbose:
866 file_mods--verbose:
844 file_mods--verbose: c
867 file_mods--verbose: c
845 file_mods--verbose:
868 file_mods--verbose:
846 file_mods--verbose:
869 file_mods--verbose:
847 file_mods--verbose:
870 file_mods--verbose:
848 file_mods--debug:
871 file_mods--debug:
849 file_mods--debug:
872 file_mods--debug:
850 file_mods--debug:
873 file_mods--debug:
851 file_mods--debug:
874 file_mods--debug:
852 file_mods--debug:
875 file_mods--debug:
853 file_mods--debug: c
876 file_mods--debug: c
854 file_mods--debug:
877 file_mods--debug:
855 file_mods--debug:
878 file_mods--debug:
856 file_mods--debug:
879 file_mods--debug:
857 file_copies: fourth (second)
880 file_copies: fourth (second)
858 file_copies:
881 file_copies:
859 file_copies:
882 file_copies:
860 file_copies:
883 file_copies:
861 file_copies:
884 file_copies:
862 file_copies:
885 file_copies:
863 file_copies:
886 file_copies:
864 file_copies:
887 file_copies:
865 file_copies:
888 file_copies:
866 file_copies--verbose: fourth (second)
889 file_copies--verbose: fourth (second)
867 file_copies--verbose:
890 file_copies--verbose:
868 file_copies--verbose:
891 file_copies--verbose:
869 file_copies--verbose:
892 file_copies--verbose:
870 file_copies--verbose:
893 file_copies--verbose:
871 file_copies--verbose:
894 file_copies--verbose:
872 file_copies--verbose:
895 file_copies--verbose:
873 file_copies--verbose:
896 file_copies--verbose:
874 file_copies--verbose:
897 file_copies--verbose:
875 file_copies--debug: fourth (second)
898 file_copies--debug: fourth (second)
876 file_copies--debug:
899 file_copies--debug:
877 file_copies--debug:
900 file_copies--debug:
878 file_copies--debug:
901 file_copies--debug:
879 file_copies--debug:
902 file_copies--debug:
880 file_copies--debug:
903 file_copies--debug:
881 file_copies--debug:
904 file_copies--debug:
882 file_copies--debug:
905 file_copies--debug:
883 file_copies--debug:
906 file_copies--debug:
884 file_copies_switch:
907 file_copies_switch:
885 file_copies_switch:
908 file_copies_switch:
886 file_copies_switch:
909 file_copies_switch:
887 file_copies_switch:
910 file_copies_switch:
888 file_copies_switch:
911 file_copies_switch:
889 file_copies_switch:
912 file_copies_switch:
890 file_copies_switch:
913 file_copies_switch:
891 file_copies_switch:
914 file_copies_switch:
892 file_copies_switch:
915 file_copies_switch:
893 file_copies_switch--verbose:
916 file_copies_switch--verbose:
894 file_copies_switch--verbose:
917 file_copies_switch--verbose:
895 file_copies_switch--verbose:
918 file_copies_switch--verbose:
896 file_copies_switch--verbose:
919 file_copies_switch--verbose:
897 file_copies_switch--verbose:
920 file_copies_switch--verbose:
898 file_copies_switch--verbose:
921 file_copies_switch--verbose:
899 file_copies_switch--verbose:
922 file_copies_switch--verbose:
900 file_copies_switch--verbose:
923 file_copies_switch--verbose:
901 file_copies_switch--verbose:
924 file_copies_switch--verbose:
902 file_copies_switch--debug:
925 file_copies_switch--debug:
903 file_copies_switch--debug:
926 file_copies_switch--debug:
904 file_copies_switch--debug:
927 file_copies_switch--debug:
905 file_copies_switch--debug:
928 file_copies_switch--debug:
906 file_copies_switch--debug:
929 file_copies_switch--debug:
907 file_copies_switch--debug:
930 file_copies_switch--debug:
908 file_copies_switch--debug:
931 file_copies_switch--debug:
909 file_copies_switch--debug:
932 file_copies_switch--debug:
910 file_copies_switch--debug:
933 file_copies_switch--debug:
911 files: fourth second third
934 files: fourth second third
912 files: second
935 files: second
913 files:
936 files:
914 files: d
937 files: d
915 files:
938 files:
916 files: c
939 files: c
917 files: c
940 files: c
918 files: b
941 files: b
919 files: a
942 files: a
920 files--verbose: fourth second third
943 files--verbose: fourth second third
921 files--verbose: second
944 files--verbose: second
922 files--verbose:
945 files--verbose:
923 files--verbose: d
946 files--verbose: d
924 files--verbose:
947 files--verbose:
925 files--verbose: c
948 files--verbose: c
926 files--verbose: c
949 files--verbose: c
927 files--verbose: b
950 files--verbose: b
928 files--verbose: a
951 files--verbose: a
929 files--debug: fourth second third
952 files--debug: fourth second third
930 files--debug: second
953 files--debug: second
931 files--debug:
954 files--debug:
932 files--debug: d
955 files--debug: d
933 files--debug:
956 files--debug:
934 files--debug: c
957 files--debug: c
935 files--debug: c
958 files--debug: c
936 files--debug: b
959 files--debug: b
937 files--debug: a
960 files--debug: a
938 manifest: 6:94961b75a2da
961 manifest: 6:94961b75a2da
939 manifest: 5:f2dbc354b94e
962 manifest: 5:f2dbc354b94e
940 manifest: 4:4dc3def4f9b4
963 manifest: 4:4dc3def4f9b4
941 manifest: 4:4dc3def4f9b4
964 manifest: 4:4dc3def4f9b4
942 manifest: 3:cb5a1327723b
965 manifest: 3:cb5a1327723b
943 manifest: 3:cb5a1327723b
966 manifest: 3:cb5a1327723b
944 manifest: 2:6e0e82995c35
967 manifest: 2:6e0e82995c35
945 manifest: 1:4e8d705b1e53
968 manifest: 1:4e8d705b1e53
946 manifest: 0:a0c8bcbbb45c
969 manifest: 0:a0c8bcbbb45c
947 manifest--verbose: 6:94961b75a2da
970 manifest--verbose: 6:94961b75a2da
948 manifest--verbose: 5:f2dbc354b94e
971 manifest--verbose: 5:f2dbc354b94e
949 manifest--verbose: 4:4dc3def4f9b4
972 manifest--verbose: 4:4dc3def4f9b4
950 manifest--verbose: 4:4dc3def4f9b4
973 manifest--verbose: 4:4dc3def4f9b4
951 manifest--verbose: 3:cb5a1327723b
974 manifest--verbose: 3:cb5a1327723b
952 manifest--verbose: 3:cb5a1327723b
975 manifest--verbose: 3:cb5a1327723b
953 manifest--verbose: 2:6e0e82995c35
976 manifest--verbose: 2:6e0e82995c35
954 manifest--verbose: 1:4e8d705b1e53
977 manifest--verbose: 1:4e8d705b1e53
955 manifest--verbose: 0:a0c8bcbbb45c
978 manifest--verbose: 0:a0c8bcbbb45c
956 manifest--debug: 6:94961b75a2da554b4df6fb599e5bfc7d48de0c64
979 manifest--debug: 6:94961b75a2da554b4df6fb599e5bfc7d48de0c64
957 manifest--debug: 5:f2dbc354b94e5ec0b4f10680ee0cee816101d0bf
980 manifest--debug: 5:f2dbc354b94e5ec0b4f10680ee0cee816101d0bf
958 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
981 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
959 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
982 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
960 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
983 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
961 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
984 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
962 manifest--debug: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
985 manifest--debug: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
963 manifest--debug: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
986 manifest--debug: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
964 manifest--debug: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
987 manifest--debug: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
965 node: 95c24699272ef57d062b8bccc32c878bf841784a
988 node: 95c24699272ef57d062b8bccc32c878bf841784a
966 node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
989 node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
967 node: d41e714fe50d9e4a5f11b4d595d543481b5f980b
990 node: d41e714fe50d9e4a5f11b4d595d543481b5f980b
968 node: 13207e5a10d9fd28ec424934298e176197f2c67f
991 node: 13207e5a10d9fd28ec424934298e176197f2c67f
969 node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
992 node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
970 node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
993 node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
971 node: 97054abb4ab824450e9164180baf491ae0078465
994 node: 97054abb4ab824450e9164180baf491ae0078465
972 node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
995 node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
973 node: 1e4e1b8f71e05681d422154f5421e385fec3454f
996 node: 1e4e1b8f71e05681d422154f5421e385fec3454f
974 node--verbose: 95c24699272ef57d062b8bccc32c878bf841784a
997 node--verbose: 95c24699272ef57d062b8bccc32c878bf841784a
975 node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
998 node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
976 node--verbose: d41e714fe50d9e4a5f11b4d595d543481b5f980b
999 node--verbose: d41e714fe50d9e4a5f11b4d595d543481b5f980b
977 node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
1000 node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
978 node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1001 node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
979 node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1002 node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
980 node--verbose: 97054abb4ab824450e9164180baf491ae0078465
1003 node--verbose: 97054abb4ab824450e9164180baf491ae0078465
981 node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1004 node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
982 node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
1005 node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
983 node--debug: 95c24699272ef57d062b8bccc32c878bf841784a
1006 node--debug: 95c24699272ef57d062b8bccc32c878bf841784a
984 node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1007 node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
985 node--debug: d41e714fe50d9e4a5f11b4d595d543481b5f980b
1008 node--debug: d41e714fe50d9e4a5f11b4d595d543481b5f980b
986 node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
1009 node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
987 node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1010 node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
988 node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1011 node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
989 node--debug: 97054abb4ab824450e9164180baf491ae0078465
1012 node--debug: 97054abb4ab824450e9164180baf491ae0078465
990 node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1013 node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
991 node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
1014 node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
992 parents:
1015 parents:
993 parents: -1:000000000000
1016 parents: -1:000000000000
994 parents: 5:13207e5a10d9 4:bbe44766e73d
1017 parents: 5:13207e5a10d9 4:bbe44766e73d
995 parents: 3:10e46f2dcbf4
1018 parents: 3:10e46f2dcbf4
996 parents:
1019 parents:
997 parents:
1020 parents:
998 parents:
1021 parents:
999 parents:
1022 parents:
1000 parents:
1023 parents:
1001 parents--verbose:
1024 parents--verbose:
1002 parents--verbose: -1:000000000000
1025 parents--verbose: -1:000000000000
1003 parents--verbose: 5:13207e5a10d9 4:bbe44766e73d
1026 parents--verbose: 5:13207e5a10d9 4:bbe44766e73d
1004 parents--verbose: 3:10e46f2dcbf4
1027 parents--verbose: 3:10e46f2dcbf4
1005 parents--verbose:
1028 parents--verbose:
1006 parents--verbose:
1029 parents--verbose:
1007 parents--verbose:
1030 parents--verbose:
1008 parents--verbose:
1031 parents--verbose:
1009 parents--verbose:
1032 parents--verbose:
1010 parents--debug: 7:29114dbae42b9f078cf2714dbe3a86bba8ec7453 -1:0000000000000000000000000000000000000000
1033 parents--debug: 7:29114dbae42b9f078cf2714dbe3a86bba8ec7453 -1:0000000000000000000000000000000000000000
1011 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
1034 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
1012 parents--debug: 5:13207e5a10d9fd28ec424934298e176197f2c67f 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
1035 parents--debug: 5:13207e5a10d9fd28ec424934298e176197f2c67f 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
1013 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
1036 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
1014 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
1037 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
1015 parents--debug: 2:97054abb4ab824450e9164180baf491ae0078465 -1:0000000000000000000000000000000000000000
1038 parents--debug: 2:97054abb4ab824450e9164180baf491ae0078465 -1:0000000000000000000000000000000000000000
1016 parents--debug: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965 -1:0000000000000000000000000000000000000000
1039 parents--debug: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965 -1:0000000000000000000000000000000000000000
1017 parents--debug: 0:1e4e1b8f71e05681d422154f5421e385fec3454f -1:0000000000000000000000000000000000000000
1040 parents--debug: 0:1e4e1b8f71e05681d422154f5421e385fec3454f -1:0000000000000000000000000000000000000000
1018 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
1041 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
1019 rev: 8
1042 rev: 8
1020 rev: 7
1043 rev: 7
1021 rev: 6
1044 rev: 6
1022 rev: 5
1045 rev: 5
1023 rev: 4
1046 rev: 4
1024 rev: 3
1047 rev: 3
1025 rev: 2
1048 rev: 2
1026 rev: 1
1049 rev: 1
1027 rev: 0
1050 rev: 0
1028 rev--verbose: 8
1051 rev--verbose: 8
1029 rev--verbose: 7
1052 rev--verbose: 7
1030 rev--verbose: 6
1053 rev--verbose: 6
1031 rev--verbose: 5
1054 rev--verbose: 5
1032 rev--verbose: 4
1055 rev--verbose: 4
1033 rev--verbose: 3
1056 rev--verbose: 3
1034 rev--verbose: 2
1057 rev--verbose: 2
1035 rev--verbose: 1
1058 rev--verbose: 1
1036 rev--verbose: 0
1059 rev--verbose: 0
1037 rev--debug: 8
1060 rev--debug: 8
1038 rev--debug: 7
1061 rev--debug: 7
1039 rev--debug: 6
1062 rev--debug: 6
1040 rev--debug: 5
1063 rev--debug: 5
1041 rev--debug: 4
1064 rev--debug: 4
1042 rev--debug: 3
1065 rev--debug: 3
1043 rev--debug: 2
1066 rev--debug: 2
1044 rev--debug: 1
1067 rev--debug: 1
1045 rev--debug: 0
1068 rev--debug: 0
1046 tags: tip
1069 tags: tip
1047 tags:
1070 tags:
1048 tags:
1071 tags:
1049 tags:
1072 tags:
1050 tags:
1073 tags:
1051 tags:
1074 tags:
1052 tags:
1075 tags:
1053 tags:
1076 tags:
1054 tags:
1077 tags:
1055 tags--verbose: tip
1078 tags--verbose: tip
1056 tags--verbose:
1079 tags--verbose:
1057 tags--verbose:
1080 tags--verbose:
1058 tags--verbose:
1081 tags--verbose:
1059 tags--verbose:
1082 tags--verbose:
1060 tags--verbose:
1083 tags--verbose:
1061 tags--verbose:
1084 tags--verbose:
1062 tags--verbose:
1085 tags--verbose:
1063 tags--verbose:
1086 tags--verbose:
1064 tags--debug: tip
1087 tags--debug: tip
1065 tags--debug:
1088 tags--debug:
1066 tags--debug:
1089 tags--debug:
1067 tags--debug:
1090 tags--debug:
1068 tags--debug:
1091 tags--debug:
1069 tags--debug:
1092 tags--debug:
1070 tags--debug:
1093 tags--debug:
1071 tags--debug:
1094 tags--debug:
1072 tags--debug:
1095 tags--debug:
1073 diffstat: 3: +2/-1
1096 diffstat: 3: +2/-1
1074 diffstat: 1: +1/-0
1097 diffstat: 1: +1/-0
1075 diffstat: 0: +0/-0
1098 diffstat: 0: +0/-0
1076 diffstat: 1: +1/-0
1099 diffstat: 1: +1/-0
1077 diffstat: 0: +0/-0
1100 diffstat: 0: +0/-0
1078 diffstat: 1: +1/-0
1101 diffstat: 1: +1/-0
1079 diffstat: 1: +4/-0
1102 diffstat: 1: +4/-0
1080 diffstat: 1: +2/-0
1103 diffstat: 1: +2/-0
1081 diffstat: 1: +1/-0
1104 diffstat: 1: +1/-0
1082 diffstat--verbose: 3: +2/-1
1105 diffstat--verbose: 3: +2/-1
1083 diffstat--verbose: 1: +1/-0
1106 diffstat--verbose: 1: +1/-0
1084 diffstat--verbose: 0: +0/-0
1107 diffstat--verbose: 0: +0/-0
1085 diffstat--verbose: 1: +1/-0
1108 diffstat--verbose: 1: +1/-0
1086 diffstat--verbose: 0: +0/-0
1109 diffstat--verbose: 0: +0/-0
1087 diffstat--verbose: 1: +1/-0
1110 diffstat--verbose: 1: +1/-0
1088 diffstat--verbose: 1: +4/-0
1111 diffstat--verbose: 1: +4/-0
1089 diffstat--verbose: 1: +2/-0
1112 diffstat--verbose: 1: +2/-0
1090 diffstat--verbose: 1: +1/-0
1113 diffstat--verbose: 1: +1/-0
1091 diffstat--debug: 3: +2/-1
1114 diffstat--debug: 3: +2/-1
1092 diffstat--debug: 1: +1/-0
1115 diffstat--debug: 1: +1/-0
1093 diffstat--debug: 0: +0/-0
1116 diffstat--debug: 0: +0/-0
1094 diffstat--debug: 1: +1/-0
1117 diffstat--debug: 1: +1/-0
1095 diffstat--debug: 0: +0/-0
1118 diffstat--debug: 0: +0/-0
1096 diffstat--debug: 1: +1/-0
1119 diffstat--debug: 1: +1/-0
1097 diffstat--debug: 1: +4/-0
1120 diffstat--debug: 1: +4/-0
1098 diffstat--debug: 1: +2/-0
1121 diffstat--debug: 1: +2/-0
1099 diffstat--debug: 1: +1/-0
1122 diffstat--debug: 1: +1/-0
1100 extras: branch=default
1123 extras: branch=default
1101 extras: branch=default
1124 extras: branch=default
1102 extras: branch=default
1125 extras: branch=default
1103 extras: branch=default
1126 extras: branch=default
1104 extras: branch=foo
1127 extras: branch=foo
1105 extras: branch=default
1128 extras: branch=default
1106 extras: branch=default
1129 extras: branch=default
1107 extras: branch=default
1130 extras: branch=default
1108 extras: branch=default
1131 extras: branch=default
1109 extras--verbose: branch=default
1132 extras--verbose: branch=default
1110 extras--verbose: branch=default
1133 extras--verbose: branch=default
1111 extras--verbose: branch=default
1134 extras--verbose: branch=default
1112 extras--verbose: branch=default
1135 extras--verbose: branch=default
1113 extras--verbose: branch=foo
1136 extras--verbose: branch=foo
1114 extras--verbose: branch=default
1137 extras--verbose: branch=default
1115 extras--verbose: branch=default
1138 extras--verbose: branch=default
1116 extras--verbose: branch=default
1139 extras--verbose: branch=default
1117 extras--verbose: branch=default
1140 extras--verbose: branch=default
1118 extras--debug: branch=default
1141 extras--debug: branch=default
1119 extras--debug: branch=default
1142 extras--debug: branch=default
1120 extras--debug: branch=default
1143 extras--debug: branch=default
1121 extras--debug: branch=default
1144 extras--debug: branch=default
1122 extras--debug: branch=foo
1145 extras--debug: branch=foo
1123 extras--debug: branch=default
1146 extras--debug: branch=default
1124 extras--debug: branch=default
1147 extras--debug: branch=default
1125 extras--debug: branch=default
1148 extras--debug: branch=default
1126 extras--debug: branch=default
1149 extras--debug: branch=default
1127 p1rev: 7
1150 p1rev: 7
1128 p1rev: -1
1151 p1rev: -1
1129 p1rev: 5
1152 p1rev: 5
1130 p1rev: 3
1153 p1rev: 3
1131 p1rev: 3
1154 p1rev: 3
1132 p1rev: 2
1155 p1rev: 2
1133 p1rev: 1
1156 p1rev: 1
1134 p1rev: 0
1157 p1rev: 0
1135 p1rev: -1
1158 p1rev: -1
1136 p1rev--verbose: 7
1159 p1rev--verbose: 7
1137 p1rev--verbose: -1
1160 p1rev--verbose: -1
1138 p1rev--verbose: 5
1161 p1rev--verbose: 5
1139 p1rev--verbose: 3
1162 p1rev--verbose: 3
1140 p1rev--verbose: 3
1163 p1rev--verbose: 3
1141 p1rev--verbose: 2
1164 p1rev--verbose: 2
1142 p1rev--verbose: 1
1165 p1rev--verbose: 1
1143 p1rev--verbose: 0
1166 p1rev--verbose: 0
1144 p1rev--verbose: -1
1167 p1rev--verbose: -1
1145 p1rev--debug: 7
1168 p1rev--debug: 7
1146 p1rev--debug: -1
1169 p1rev--debug: -1
1147 p1rev--debug: 5
1170 p1rev--debug: 5
1148 p1rev--debug: 3
1171 p1rev--debug: 3
1149 p1rev--debug: 3
1172 p1rev--debug: 3
1150 p1rev--debug: 2
1173 p1rev--debug: 2
1151 p1rev--debug: 1
1174 p1rev--debug: 1
1152 p1rev--debug: 0
1175 p1rev--debug: 0
1153 p1rev--debug: -1
1176 p1rev--debug: -1
1154 p2rev: -1
1177 p2rev: -1
1155 p2rev: -1
1178 p2rev: -1
1156 p2rev: 4
1179 p2rev: 4
1157 p2rev: -1
1180 p2rev: -1
1158 p2rev: -1
1181 p2rev: -1
1159 p2rev: -1
1182 p2rev: -1
1160 p2rev: -1
1183 p2rev: -1
1161 p2rev: -1
1184 p2rev: -1
1162 p2rev: -1
1185 p2rev: -1
1163 p2rev--verbose: -1
1186 p2rev--verbose: -1
1164 p2rev--verbose: -1
1187 p2rev--verbose: -1
1165 p2rev--verbose: 4
1188 p2rev--verbose: 4
1166 p2rev--verbose: -1
1189 p2rev--verbose: -1
1167 p2rev--verbose: -1
1190 p2rev--verbose: -1
1168 p2rev--verbose: -1
1191 p2rev--verbose: -1
1169 p2rev--verbose: -1
1192 p2rev--verbose: -1
1170 p2rev--verbose: -1
1193 p2rev--verbose: -1
1171 p2rev--verbose: -1
1194 p2rev--verbose: -1
1172 p2rev--debug: -1
1195 p2rev--debug: -1
1173 p2rev--debug: -1
1196 p2rev--debug: -1
1174 p2rev--debug: 4
1197 p2rev--debug: 4
1175 p2rev--debug: -1
1198 p2rev--debug: -1
1176 p2rev--debug: -1
1199 p2rev--debug: -1
1177 p2rev--debug: -1
1200 p2rev--debug: -1
1178 p2rev--debug: -1
1201 p2rev--debug: -1
1179 p2rev--debug: -1
1202 p2rev--debug: -1
1180 p2rev--debug: -1
1203 p2rev--debug: -1
1181 p1node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1204 p1node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1182 p1node: 0000000000000000000000000000000000000000
1205 p1node: 0000000000000000000000000000000000000000
1183 p1node: 13207e5a10d9fd28ec424934298e176197f2c67f
1206 p1node: 13207e5a10d9fd28ec424934298e176197f2c67f
1184 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1207 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1185 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1208 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1186 p1node: 97054abb4ab824450e9164180baf491ae0078465
1209 p1node: 97054abb4ab824450e9164180baf491ae0078465
1187 p1node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1210 p1node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1188 p1node: 1e4e1b8f71e05681d422154f5421e385fec3454f
1211 p1node: 1e4e1b8f71e05681d422154f5421e385fec3454f
1189 p1node: 0000000000000000000000000000000000000000
1212 p1node: 0000000000000000000000000000000000000000
1190 p1node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1213 p1node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1191 p1node--verbose: 0000000000000000000000000000000000000000
1214 p1node--verbose: 0000000000000000000000000000000000000000
1192 p1node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
1215 p1node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
1193 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1216 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1194 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1217 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1195 p1node--verbose: 97054abb4ab824450e9164180baf491ae0078465
1218 p1node--verbose: 97054abb4ab824450e9164180baf491ae0078465
1196 p1node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1219 p1node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1197 p1node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
1220 p1node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
1198 p1node--verbose: 0000000000000000000000000000000000000000
1221 p1node--verbose: 0000000000000000000000000000000000000000
1199 p1node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1222 p1node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1200 p1node--debug: 0000000000000000000000000000000000000000
1223 p1node--debug: 0000000000000000000000000000000000000000
1201 p1node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
1224 p1node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
1202 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1225 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1203 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1226 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1204 p1node--debug: 97054abb4ab824450e9164180baf491ae0078465
1227 p1node--debug: 97054abb4ab824450e9164180baf491ae0078465
1205 p1node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1228 p1node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1206 p1node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
1229 p1node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
1207 p1node--debug: 0000000000000000000000000000000000000000
1230 p1node--debug: 0000000000000000000000000000000000000000
1208 p2node: 0000000000000000000000000000000000000000
1231 p2node: 0000000000000000000000000000000000000000
1209 p2node: 0000000000000000000000000000000000000000
1232 p2node: 0000000000000000000000000000000000000000
1210 p2node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1233 p2node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1211 p2node: 0000000000000000000000000000000000000000
1234 p2node: 0000000000000000000000000000000000000000
1212 p2node: 0000000000000000000000000000000000000000
1235 p2node: 0000000000000000000000000000000000000000
1213 p2node: 0000000000000000000000000000000000000000
1236 p2node: 0000000000000000000000000000000000000000
1214 p2node: 0000000000000000000000000000000000000000
1237 p2node: 0000000000000000000000000000000000000000
1215 p2node: 0000000000000000000000000000000000000000
1238 p2node: 0000000000000000000000000000000000000000
1216 p2node: 0000000000000000000000000000000000000000
1239 p2node: 0000000000000000000000000000000000000000
1217 p2node--verbose: 0000000000000000000000000000000000000000
1240 p2node--verbose: 0000000000000000000000000000000000000000
1218 p2node--verbose: 0000000000000000000000000000000000000000
1241 p2node--verbose: 0000000000000000000000000000000000000000
1219 p2node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1242 p2node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1220 p2node--verbose: 0000000000000000000000000000000000000000
1243 p2node--verbose: 0000000000000000000000000000000000000000
1221 p2node--verbose: 0000000000000000000000000000000000000000
1244 p2node--verbose: 0000000000000000000000000000000000000000
1222 p2node--verbose: 0000000000000000000000000000000000000000
1245 p2node--verbose: 0000000000000000000000000000000000000000
1223 p2node--verbose: 0000000000000000000000000000000000000000
1246 p2node--verbose: 0000000000000000000000000000000000000000
1224 p2node--verbose: 0000000000000000000000000000000000000000
1247 p2node--verbose: 0000000000000000000000000000000000000000
1225 p2node--verbose: 0000000000000000000000000000000000000000
1248 p2node--verbose: 0000000000000000000000000000000000000000
1226 p2node--debug: 0000000000000000000000000000000000000000
1249 p2node--debug: 0000000000000000000000000000000000000000
1227 p2node--debug: 0000000000000000000000000000000000000000
1250 p2node--debug: 0000000000000000000000000000000000000000
1228 p2node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1251 p2node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1229 p2node--debug: 0000000000000000000000000000000000000000
1252 p2node--debug: 0000000000000000000000000000000000000000
1230 p2node--debug: 0000000000000000000000000000000000000000
1253 p2node--debug: 0000000000000000000000000000000000000000
1231 p2node--debug: 0000000000000000000000000000000000000000
1254 p2node--debug: 0000000000000000000000000000000000000000
1232 p2node--debug: 0000000000000000000000000000000000000000
1255 p2node--debug: 0000000000000000000000000000000000000000
1233 p2node--debug: 0000000000000000000000000000000000000000
1256 p2node--debug: 0000000000000000000000000000000000000000
1234 p2node--debug: 0000000000000000000000000000000000000000
1257 p2node--debug: 0000000000000000000000000000000000000000
1235
1258
1236 Filters work:
1259 Filters work:
1237
1260
1238 $ hg log --template '{author|domain}\n'
1261 $ hg log --template '{author|domain}\n'
1239
1262
1240 hostname
1263 hostname
1241
1264
1242
1265
1243
1266
1244
1267
1245 place
1268 place
1246 place
1269 place
1247 hostname
1270 hostname
1248
1271
1249 $ hg log --template '{author|person}\n'
1272 $ hg log --template '{author|person}\n'
1250 test
1273 test
1251 User Name
1274 User Name
1252 person
1275 person
1253 person
1276 person
1254 person
1277 person
1255 person
1278 person
1256 other
1279 other
1257 A. N. Other
1280 A. N. Other
1258 User Name
1281 User Name
1259
1282
1260 $ hg log --template '{author|user}\n'
1283 $ hg log --template '{author|user}\n'
1261 test
1284 test
1262 user
1285 user
1263 person
1286 person
1264 person
1287 person
1265 person
1288 person
1266 person
1289 person
1267 other
1290 other
1268 other
1291 other
1269 user
1292 user
1270
1293
1271 $ hg log --template '{date|date}\n'
1294 $ hg log --template '{date|date}\n'
1272 Wed Jan 01 10:01:00 2020 +0000
1295 Wed Jan 01 10:01:00 2020 +0000
1273 Mon Jan 12 13:46:40 1970 +0000
1296 Mon Jan 12 13:46:40 1970 +0000
1274 Sun Jan 18 08:40:01 1970 +0000
1297 Sun Jan 18 08:40:01 1970 +0000
1275 Sun Jan 18 08:40:00 1970 +0000
1298 Sun Jan 18 08:40:00 1970 +0000
1276 Sat Jan 17 04:53:20 1970 +0000
1299 Sat Jan 17 04:53:20 1970 +0000
1277 Fri Jan 16 01:06:40 1970 +0000
1300 Fri Jan 16 01:06:40 1970 +0000
1278 Wed Jan 14 21:20:00 1970 +0000
1301 Wed Jan 14 21:20:00 1970 +0000
1279 Tue Jan 13 17:33:20 1970 +0000
1302 Tue Jan 13 17:33:20 1970 +0000
1280 Mon Jan 12 13:46:40 1970 +0000
1303 Mon Jan 12 13:46:40 1970 +0000
1281
1304
1282 $ hg log --template '{date|isodate}\n'
1305 $ hg log --template '{date|isodate}\n'
1283 2020-01-01 10:01 +0000
1306 2020-01-01 10:01 +0000
1284 1970-01-12 13:46 +0000
1307 1970-01-12 13:46 +0000
1285 1970-01-18 08:40 +0000
1308 1970-01-18 08:40 +0000
1286 1970-01-18 08:40 +0000
1309 1970-01-18 08:40 +0000
1287 1970-01-17 04:53 +0000
1310 1970-01-17 04:53 +0000
1288 1970-01-16 01:06 +0000
1311 1970-01-16 01:06 +0000
1289 1970-01-14 21:20 +0000
1312 1970-01-14 21:20 +0000
1290 1970-01-13 17:33 +0000
1313 1970-01-13 17:33 +0000
1291 1970-01-12 13:46 +0000
1314 1970-01-12 13:46 +0000
1292
1315
1293 $ hg log --template '{date|isodatesec}\n'
1316 $ hg log --template '{date|isodatesec}\n'
1294 2020-01-01 10:01:00 +0000
1317 2020-01-01 10:01:00 +0000
1295 1970-01-12 13:46:40 +0000
1318 1970-01-12 13:46:40 +0000
1296 1970-01-18 08:40:01 +0000
1319 1970-01-18 08:40:01 +0000
1297 1970-01-18 08:40:00 +0000
1320 1970-01-18 08:40:00 +0000
1298 1970-01-17 04:53:20 +0000
1321 1970-01-17 04:53:20 +0000
1299 1970-01-16 01:06:40 +0000
1322 1970-01-16 01:06:40 +0000
1300 1970-01-14 21:20:00 +0000
1323 1970-01-14 21:20:00 +0000
1301 1970-01-13 17:33:20 +0000
1324 1970-01-13 17:33:20 +0000
1302 1970-01-12 13:46:40 +0000
1325 1970-01-12 13:46:40 +0000
1303
1326
1304 $ hg log --template '{date|rfc822date}\n'
1327 $ hg log --template '{date|rfc822date}\n'
1305 Wed, 01 Jan 2020 10:01:00 +0000
1328 Wed, 01 Jan 2020 10:01:00 +0000
1306 Mon, 12 Jan 1970 13:46:40 +0000
1329 Mon, 12 Jan 1970 13:46:40 +0000
1307 Sun, 18 Jan 1970 08:40:01 +0000
1330 Sun, 18 Jan 1970 08:40:01 +0000
1308 Sun, 18 Jan 1970 08:40:00 +0000
1331 Sun, 18 Jan 1970 08:40:00 +0000
1309 Sat, 17 Jan 1970 04:53:20 +0000
1332 Sat, 17 Jan 1970 04:53:20 +0000
1310 Fri, 16 Jan 1970 01:06:40 +0000
1333 Fri, 16 Jan 1970 01:06:40 +0000
1311 Wed, 14 Jan 1970 21:20:00 +0000
1334 Wed, 14 Jan 1970 21:20:00 +0000
1312 Tue, 13 Jan 1970 17:33:20 +0000
1335 Tue, 13 Jan 1970 17:33:20 +0000
1313 Mon, 12 Jan 1970 13:46:40 +0000
1336 Mon, 12 Jan 1970 13:46:40 +0000
1314
1337
1315 $ hg log --template '{desc|firstline}\n'
1338 $ hg log --template '{desc|firstline}\n'
1316 third
1339 third
1317 second
1340 second
1318 merge
1341 merge
1319 new head
1342 new head
1320 new branch
1343 new branch
1321 no user, no domain
1344 no user, no domain
1322 no person
1345 no person
1323 other 1
1346 other 1
1324 line 1
1347 line 1
1325
1348
1326 $ hg log --template '{node|short}\n'
1349 $ hg log --template '{node|short}\n'
1327 95c24699272e
1350 95c24699272e
1328 29114dbae42b
1351 29114dbae42b
1329 d41e714fe50d
1352 d41e714fe50d
1330 13207e5a10d9
1353 13207e5a10d9
1331 bbe44766e73d
1354 bbe44766e73d
1332 10e46f2dcbf4
1355 10e46f2dcbf4
1333 97054abb4ab8
1356 97054abb4ab8
1334 b608e9d1a3f0
1357 b608e9d1a3f0
1335 1e4e1b8f71e0
1358 1e4e1b8f71e0
1336
1359
1337 $ hg log --template '<changeset author="{author|xmlescape}"/>\n'
1360 $ hg log --template '<changeset author="{author|xmlescape}"/>\n'
1338 <changeset author="test"/>
1361 <changeset author="test"/>
1339 <changeset author="User Name &lt;user@hostname&gt;"/>
1362 <changeset author="User Name &lt;user@hostname&gt;"/>
1340 <changeset author="person"/>
1363 <changeset author="person"/>
1341 <changeset author="person"/>
1364 <changeset author="person"/>
1342 <changeset author="person"/>
1365 <changeset author="person"/>
1343 <changeset author="person"/>
1366 <changeset author="person"/>
1344 <changeset author="other@place"/>
1367 <changeset author="other@place"/>
1345 <changeset author="A. N. Other &lt;other@place&gt;"/>
1368 <changeset author="A. N. Other &lt;other@place&gt;"/>
1346 <changeset author="User Name &lt;user@hostname&gt;"/>
1369 <changeset author="User Name &lt;user@hostname&gt;"/>
1347
1370
1348 $ hg log --template '{rev}: {children}\n'
1371 $ hg log --template '{rev}: {children}\n'
1349 8:
1372 8:
1350 7: 8:95c24699272e
1373 7: 8:95c24699272e
1351 6:
1374 6:
1352 5: 6:d41e714fe50d
1375 5: 6:d41e714fe50d
1353 4: 6:d41e714fe50d
1376 4: 6:d41e714fe50d
1354 3: 4:bbe44766e73d 5:13207e5a10d9
1377 3: 4:bbe44766e73d 5:13207e5a10d9
1355 2: 3:10e46f2dcbf4
1378 2: 3:10e46f2dcbf4
1356 1: 2:97054abb4ab8
1379 1: 2:97054abb4ab8
1357 0: 1:b608e9d1a3f0
1380 0: 1:b608e9d1a3f0
1358
1381
1359 Formatnode filter works:
1382 Formatnode filter works:
1360
1383
1361 $ hg -q log -r 0 --template '{node|formatnode}\n'
1384 $ hg -q log -r 0 --template '{node|formatnode}\n'
1362 1e4e1b8f71e0
1385 1e4e1b8f71e0
1363
1386
1364 $ hg log -r 0 --template '{node|formatnode}\n'
1387 $ hg log -r 0 --template '{node|formatnode}\n'
1365 1e4e1b8f71e0
1388 1e4e1b8f71e0
1366
1389
1367 $ hg -v log -r 0 --template '{node|formatnode}\n'
1390 $ hg -v log -r 0 --template '{node|formatnode}\n'
1368 1e4e1b8f71e0
1391 1e4e1b8f71e0
1369
1392
1370 $ hg --debug log -r 0 --template '{node|formatnode}\n'
1393 $ hg --debug log -r 0 --template '{node|formatnode}\n'
1371 1e4e1b8f71e05681d422154f5421e385fec3454f
1394 1e4e1b8f71e05681d422154f5421e385fec3454f
1372
1395
1373 Age filter:
1396 Age filter:
1374
1397
1375 $ hg log --template '{date|age}\n' > /dev/null || exit 1
1398 $ hg log --template '{date|age}\n' > /dev/null || exit 1
1376
1399
1377 >>> from datetime import datetime, timedelta
1400 >>> from datetime import datetime, timedelta
1378 >>> fp = open('a', 'w')
1401 >>> fp = open('a', 'w')
1379 >>> n = datetime.now() + timedelta(366 * 7)
1402 >>> n = datetime.now() + timedelta(366 * 7)
1380 >>> fp.write('%d-%d-%d 00:00' % (n.year, n.month, n.day))
1403 >>> fp.write('%d-%d-%d 00:00' % (n.year, n.month, n.day))
1381 >>> fp.close()
1404 >>> fp.close()
1382 $ hg add a
1405 $ hg add a
1383 $ hg commit -m future -d "`cat a`"
1406 $ hg commit -m future -d "`cat a`"
1384
1407
1385 $ hg log -l1 --template '{date|age}\n'
1408 $ hg log -l1 --template '{date|age}\n'
1386 7 years from now
1409 7 years from now
1387
1410
1388 Error on syntax:
1411 Error on syntax:
1389
1412
1390 $ echo 'x = "f' >> t
1413 $ echo 'x = "f' >> t
1391 $ hg log
1414 $ hg log
1392 abort: t:3: unmatched quotes
1415 abort: t:3: unmatched quotes
1393 [255]
1416 [255]
1394
1417
1395 Behind the scenes, this will throw TypeError
1418 Behind the scenes, this will throw TypeError
1396
1419
1397 $ hg log -l 3 --template '{date|obfuscate}\n'
1420 $ hg log -l 3 --template '{date|obfuscate}\n'
1398 abort: template filter 'obfuscate' is not compatible with keyword 'date'
1421 abort: template filter 'obfuscate' is not compatible with keyword 'date'
1399 [255]
1422 [255]
1400
1423
1401 Behind the scenes, this will throw a ValueError
1424 Behind the scenes, this will throw a ValueError
1402
1425
1403 $ hg log -l 3 --template 'line: {desc|shortdate}\n'
1426 $ hg log -l 3 --template 'line: {desc|shortdate}\n'
1404 abort: template filter 'shortdate' is not compatible with keyword 'desc'
1427 abort: template filter 'shortdate' is not compatible with keyword 'desc'
1405 [255]
1428 [255]
1406
1429
1407 Behind the scenes, this will throw AttributeError
1430 Behind the scenes, this will throw AttributeError
1408
1431
1409 $ hg log -l 3 --template 'line: {date|escape}\n'
1432 $ hg log -l 3 --template 'line: {date|escape}\n'
1410 abort: template filter 'escape' is not compatible with keyword 'date'
1433 abort: template filter 'escape' is not compatible with keyword 'date'
1411 [255]
1434 [255]
1412
1435
1413 Behind the scenes, this will throw ValueError
1436 Behind the scenes, this will throw ValueError
1414
1437
1415 $ hg tip --template '{author|email|date}\n'
1438 $ hg tip --template '{author|email|date}\n'
1416 abort: template filter 'datefilter' is not compatible with keyword 'author'
1439 abort: template filter 'datefilter' is not compatible with keyword 'author'
1417 [255]
1440 [255]
1418
1441
1419 $ cd ..
1442 $ cd ..
1420
1443
1421
1444
1422 latesttag:
1445 latesttag:
1423
1446
1424 $ hg init latesttag
1447 $ hg init latesttag
1425 $ cd latesttag
1448 $ cd latesttag
1426
1449
1427 $ echo a > file
1450 $ echo a > file
1428 $ hg ci -Am a -d '0 0'
1451 $ hg ci -Am a -d '0 0'
1429 adding file
1452 adding file
1430
1453
1431 $ echo b >> file
1454 $ echo b >> file
1432 $ hg ci -m b -d '1 0'
1455 $ hg ci -m b -d '1 0'
1433
1456
1434 $ echo c >> head1
1457 $ echo c >> head1
1435 $ hg ci -Am h1c -d '2 0'
1458 $ hg ci -Am h1c -d '2 0'
1436 adding head1
1459 adding head1
1437
1460
1438 $ hg update -q 1
1461 $ hg update -q 1
1439 $ echo d >> head2
1462 $ echo d >> head2
1440 $ hg ci -Am h2d -d '3 0'
1463 $ hg ci -Am h2d -d '3 0'
1441 adding head2
1464 adding head2
1442 created new head
1465 created new head
1443
1466
1444 $ echo e >> head2
1467 $ echo e >> head2
1445 $ hg ci -m h2e -d '4 0'
1468 $ hg ci -m h2e -d '4 0'
1446
1469
1447 $ hg merge -q
1470 $ hg merge -q
1448 $ hg ci -m merge -d '5 -3600'
1471 $ hg ci -m merge -d '5 -3600'
1449
1472
1450 No tag set:
1473 No tag set:
1451
1474
1452 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
1475 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
1453 5: null+5
1476 5: null+5
1454 4: null+4
1477 4: null+4
1455 3: null+3
1478 3: null+3
1456 2: null+3
1479 2: null+3
1457 1: null+2
1480 1: null+2
1458 0: null+1
1481 0: null+1
1459
1482
1460 One common tag: longuest path wins:
1483 One common tag: longuest path wins:
1461
1484
1462 $ hg tag -r 1 -m t1 -d '6 0' t1
1485 $ hg tag -r 1 -m t1 -d '6 0' t1
1463 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
1486 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
1464 6: t1+4
1487 6: t1+4
1465 5: t1+3
1488 5: t1+3
1466 4: t1+2
1489 4: t1+2
1467 3: t1+1
1490 3: t1+1
1468 2: t1+1
1491 2: t1+1
1469 1: t1+0
1492 1: t1+0
1470 0: null+1
1493 0: null+1
1471
1494
1472 One ancestor tag: more recent wins:
1495 One ancestor tag: more recent wins:
1473
1496
1474 $ hg tag -r 2 -m t2 -d '7 0' t2
1497 $ hg tag -r 2 -m t2 -d '7 0' t2
1475 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
1498 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
1476 7: t2+3
1499 7: t2+3
1477 6: t2+2
1500 6: t2+2
1478 5: t2+1
1501 5: t2+1
1479 4: t1+2
1502 4: t1+2
1480 3: t1+1
1503 3: t1+1
1481 2: t2+0
1504 2: t2+0
1482 1: t1+0
1505 1: t1+0
1483 0: null+1
1506 0: null+1
1484
1507
1485 Two branch tags: more recent wins:
1508 Two branch tags: more recent wins:
1486
1509
1487 $ hg tag -r 3 -m t3 -d '8 0' t3
1510 $ hg tag -r 3 -m t3 -d '8 0' t3
1488 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
1511 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
1489 8: t3+5
1512 8: t3+5
1490 7: t3+4
1513 7: t3+4
1491 6: t3+3
1514 6: t3+3
1492 5: t3+2
1515 5: t3+2
1493 4: t3+1
1516 4: t3+1
1494 3: t3+0
1517 3: t3+0
1495 2: t2+0
1518 2: t2+0
1496 1: t1+0
1519 1: t1+0
1497 0: null+1
1520 0: null+1
1498
1521
1499 Merged tag overrides:
1522 Merged tag overrides:
1500
1523
1501 $ hg tag -r 5 -m t5 -d '9 0' t5
1524 $ hg tag -r 5 -m t5 -d '9 0' t5
1502 $ hg tag -r 3 -m at3 -d '10 0' at3
1525 $ hg tag -r 3 -m at3 -d '10 0' at3
1503 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
1526 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
1504 10: t5+5
1527 10: t5+5
1505 9: t5+4
1528 9: t5+4
1506 8: t5+3
1529 8: t5+3
1507 7: t5+2
1530 7: t5+2
1508 6: t5+1
1531 6: t5+1
1509 5: t5+0
1532 5: t5+0
1510 4: at3:t3+1
1533 4: at3:t3+1
1511 3: at3:t3+0
1534 3: at3:t3+0
1512 2: t2+0
1535 2: t2+0
1513 1: t1+0
1536 1: t1+0
1514 0: null+1
1537 0: null+1
1515
1538
1516 $ cd ..
1539 $ cd ..
1517
1540
1518
1541
1519 Style path expansion: issue1948 - ui.style option doesn't work on OSX
1542 Style path expansion: issue1948 - ui.style option doesn't work on OSX
1520 if it is a relative path
1543 if it is a relative path
1521
1544
1522 $ mkdir -p home/styles
1545 $ mkdir -p home/styles
1523
1546
1524 $ cat > home/styles/teststyle <<EOF
1547 $ cat > home/styles/teststyle <<EOF
1525 > changeset = 'test {rev}:{node|short}\n'
1548 > changeset = 'test {rev}:{node|short}\n'
1526 > EOF
1549 > EOF
1527
1550
1528 $ HOME=`pwd`/home; export HOME
1551 $ HOME=`pwd`/home; export HOME
1529
1552
1530 $ cat > latesttag/.hg/hgrc <<EOF
1553 $ cat > latesttag/.hg/hgrc <<EOF
1531 > [ui]
1554 > [ui]
1532 > style = ~/styles/teststyle
1555 > style = ~/styles/teststyle
1533 > EOF
1556 > EOF
1534
1557
1535 $ hg -R latesttag tip
1558 $ hg -R latesttag tip
1536 test 10:9b4a630e5f5f
1559 test 10:9b4a630e5f5f
1537
1560
1538 Test recursive showlist template (issue1989):
1561 Test recursive showlist template (issue1989):
1539
1562
1540 $ cat > style1989 <<EOF
1563 $ cat > style1989 <<EOF
1541 > changeset = '{file_mods}{manifest}{extras}'
1564 > changeset = '{file_mods}{manifest}{extras}'
1542 > file_mod = 'M|{author|person}\n'
1565 > file_mod = 'M|{author|person}\n'
1543 > manifest = '{rev},{author}\n'
1566 > manifest = '{rev},{author}\n'
1544 > extra = '{key}: {author}\n'
1567 > extra = '{key}: {author}\n'
1545 > EOF
1568 > EOF
1546
1569
1547 $ hg -R latesttag log -r tip --style=style1989
1570 $ hg -R latesttag log -r tip --style=style1989
1548 M|test
1571 M|test
1549 10,test
1572 10,test
1550 branch: test
1573 branch: test
1551
1574
1552 Test new-style inline templating:
1575 Test new-style inline templating:
1553
1576
1554 $ hg log -R latesttag -r tip --template 'modified files: {file_mods % " {file}\n"}\n'
1577 $ hg log -R latesttag -r tip --template 'modified files: {file_mods % " {file}\n"}\n'
1555 modified files: .hgtags
1578 modified files: .hgtags
1556
1579
1557 Test the sub function of templating for expansion:
1580 Test the sub function of templating for expansion:
1558
1581
1559 $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
1582 $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
1560 xx
1583 xx
1561
1584
1562 Test the strip function with chars specified:
1585 Test the strip function with chars specified:
1563
1586
1564 $ hg log -R latesttag --template '{desc}\n'
1587 $ hg log -R latesttag --template '{desc}\n'
1565 at3
1588 at3
1566 t5
1589 t5
1567 t3
1590 t3
1568 t2
1591 t2
1569 t1
1592 t1
1570 merge
1593 merge
1571 h2e
1594 h2e
1572 h2d
1595 h2d
1573 h1c
1596 h1c
1574 b
1597 b
1575 a
1598 a
1576
1599
1577 $ hg log -R latesttag --template '{strip(desc, "te")}\n'
1600 $ hg log -R latesttag --template '{strip(desc, "te")}\n'
1578 at3
1601 at3
1579 5
1602 5
1580 3
1603 3
1581 2
1604 2
1582 1
1605 1
1583 merg
1606 merg
1584 h2
1607 h2
1585 h2d
1608 h2d
1586 h1c
1609 h1c
1587 b
1610 b
1588 a
1611 a
1589
1612
1590 Test date format:
1613 Test date format:
1591
1614
1592 $ hg log -R latesttag --template 'date: {date(date, "%y %m %d %S %z")}\n'
1615 $ hg log -R latesttag --template 'date: {date(date, "%y %m %d %S %z")}\n'
1593 date: 70 01 01 10 +0000
1616 date: 70 01 01 10 +0000
1594 date: 70 01 01 09 +0000
1617 date: 70 01 01 09 +0000
1595 date: 70 01 01 08 +0000
1618 date: 70 01 01 08 +0000
1596 date: 70 01 01 07 +0000
1619 date: 70 01 01 07 +0000
1597 date: 70 01 01 06 +0000
1620 date: 70 01 01 06 +0000
1598 date: 70 01 01 05 +0100
1621 date: 70 01 01 05 +0100
1599 date: 70 01 01 04 +0000
1622 date: 70 01 01 04 +0000
1600 date: 70 01 01 03 +0000
1623 date: 70 01 01 03 +0000
1601 date: 70 01 01 02 +0000
1624 date: 70 01 01 02 +0000
1602 date: 70 01 01 01 +0000
1625 date: 70 01 01 01 +0000
1603 date: 70 01 01 00 +0000
1626 date: 70 01 01 00 +0000
1604
1627
1605 Test string escaping:
1628 Test string escaping:
1606
1629
1607 $ hg log -R latesttag -r 0 --template '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
1630 $ hg log -R latesttag -r 0 --template '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
1608 >
1631 >
1609 <>\n<[>
1632 <>\n<[>
1610 <>\n<]>
1633 <>\n<]>
1611 <>\n<
1634 <>\n<
1612
1635
1613 Test recursive evaluation:
1636 Test recursive evaluation:
1614
1637
1615 $ hg init r
1638 $ hg init r
1616 $ cd r
1639 $ cd r
1617 $ echo a > a
1640 $ echo a > a
1618 $ hg ci -Am '{rev}'
1641 $ hg ci -Am '{rev}'
1619 adding a
1642 adding a
1620 $ hg log -r 0 --template '{if(rev, desc)}\n'
1643 $ hg log -r 0 --template '{if(rev, desc)}\n'
1621 {rev}
1644 {rev}
1622 $ hg log -r 0 --template '{if(rev, "{author} {rev}")}\n'
1645 $ hg log -r 0 --template '{if(rev, "{author} {rev}")}\n'
1623 test 0
1646 test 0
1624
1647
1625 Test branches inside if statement:
1648 Test branches inside if statement:
1626
1649
1627 $ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
1650 $ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
1628 no
1651 no
1629
1652
1630 Test shortest(node) function:
1653 Test shortest(node) function:
1631
1654
1632 $ echo b > b
1655 $ echo b > b
1633 $ hg ci -qAm b
1656 $ hg ci -qAm b
1634 $ hg log --template '{shortest(node)}\n'
1657 $ hg log --template '{shortest(node)}\n'
1635 d97c
1658 d97c
1636 f776
1659 f776
1637 $ hg log --template '{shortest(node, 10)}\n'
1660 $ hg log --template '{shortest(node, 10)}\n'
1638 d97c383ae3
1661 d97c383ae3
1639 f7769ec2ab
1662 f7769ec2ab
1640
1663
1641 Test pad function
1664 Test pad function
1642
1665
1643 $ hg log --template '{pad(rev, 20)} {author|user}\n'
1666 $ hg log --template '{pad(rev, 20)} {author|user}\n'
1644 1 test
1667 1 test
1645 0 test
1668 0 test
1646
1669
1647 $ hg log --template '{pad(rev, 20, " ", True)} {author|user}\n'
1670 $ hg log --template '{pad(rev, 20, " ", True)} {author|user}\n'
1648 1 test
1671 1 test
1649 0 test
1672 0 test
1650
1673
1651 $ hg log --template '{pad(rev, 20, "-", False)} {author|user}\n'
1674 $ hg log --template '{pad(rev, 20, "-", False)} {author|user}\n'
1652 1------------------- test
1675 1------------------- test
1653 0------------------- test
1676 0------------------- test
1654
1677
1655 Test ifcontains function
1678 Test ifcontains function
1656
1679
1657 $ hg log --template '{rev} {ifcontains("a", file_adds, "added a", "did not add a")}\n'
1680 $ hg log --template '{rev} {ifcontains("a", file_adds, "added a", "did not add a")}\n'
1658 1 did not add a
1681 1 did not add a
1659 0 added a
1682 0 added a
1660
1683
1661 Test revset function
1684 Test revset function
1662
1685
1663 $ hg log --template '{rev} {ifcontains(rev, revset("."), "current rev", "not current rev")}\n'
1686 $ hg log --template '{rev} {ifcontains(rev, revset("."), "current rev", "not current rev")}\n'
1664 1 current rev
1687 1 current rev
1665 0 not current rev
1688 0 not current rev
1666
1689
1667 $ hg log --template '{rev} Parents: {revset("parents(%s)", rev)}\n'
1690 $ hg log --template '{rev} Parents: {revset("parents(%s)", rev)}\n'
1668 1 Parents: 0
1691 1 Parents: 0
1669 0 Parents:
1692 0 Parents:
1670
1693
1671 $ hg log --template 'Rev: {rev}\n{revset("::%s", rev) % "Ancestor: {revision}\n"}\n'
1694 $ hg log --template 'Rev: {rev}\n{revset("::%s", rev) % "Ancestor: {revision}\n"}\n'
1672 Rev: 1
1695 Rev: 1
1673 Ancestor: 0
1696 Ancestor: 0
1674 Ancestor: 1
1697 Ancestor: 1
1675
1698
1676 Rev: 0
1699 Rev: 0
1677 Ancestor: 0
1700 Ancestor: 0
1678
1701
1679 Test current bookmark templating
1702 Test current bookmark templating
1680
1703
1681 $ hg book foo
1704 $ hg book foo
1682 $ hg book bar
1705 $ hg book bar
1683 $ hg log --template "{rev} {bookmarks % '{bookmark}{ifeq(bookmark, current, \"*\")} '}\n"
1706 $ hg log --template "{rev} {bookmarks % '{bookmark}{ifeq(bookmark, current, \"*\")} '}\n"
1684 1 bar* foo
1707 1 bar* foo
1685 0
1708 0
General Comments 0
You need to be logged in to leave comments. Login now