##// END OF EJS Templates
obsolete: compute unstable changeset...
Pierre-Yves David -
r17171:9c750c3e default
parent child Browse files
Show More
@@ -1,1284 +1,1299 b''
1 # context.py - changeset and file context objects for mercurial
1 # context.py - changeset and file context objects for mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 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 nullid, nullrev, short, hex, bin
8 from node import nullid, nullrev, short, hex, bin
9 from i18n import _
9 from i18n import _
10 import ancestor, mdiff, error, util, scmutil, subrepo, patch, encoding, phases
10 import ancestor, mdiff, error, util, scmutil, subrepo, patch, encoding, phases
11 import copies
11 import copies
12 import match as matchmod
12 import match as matchmod
13 import os, errno, stat
13 import os, errno, stat
14
14
15 propertycache = util.propertycache
15 propertycache = util.propertycache
16
16
17 class changectx(object):
17 class changectx(object):
18 """A changecontext object makes access to data related to a particular
18 """A changecontext object makes access to data related to a particular
19 changeset convenient."""
19 changeset convenient."""
20 def __init__(self, repo, changeid=''):
20 def __init__(self, repo, changeid=''):
21 """changeid is a revision number, node, or tag"""
21 """changeid is a revision number, node, or tag"""
22 if changeid == '':
22 if changeid == '':
23 changeid = '.'
23 changeid = '.'
24 self._repo = repo
24 self._repo = repo
25
25
26 if isinstance(changeid, int):
26 if isinstance(changeid, int):
27 self._rev = changeid
27 self._rev = changeid
28 self._node = repo.changelog.node(changeid)
28 self._node = repo.changelog.node(changeid)
29 return
29 return
30 if isinstance(changeid, long):
30 if isinstance(changeid, long):
31 changeid = str(changeid)
31 changeid = str(changeid)
32 if changeid == '.':
32 if changeid == '.':
33 self._node = repo.dirstate.p1()
33 self._node = repo.dirstate.p1()
34 self._rev = repo.changelog.rev(self._node)
34 self._rev = repo.changelog.rev(self._node)
35 return
35 return
36 if changeid == 'null':
36 if changeid == 'null':
37 self._node = nullid
37 self._node = nullid
38 self._rev = nullrev
38 self._rev = nullrev
39 return
39 return
40 if changeid == 'tip':
40 if changeid == 'tip':
41 self._rev = len(repo.changelog) - 1
41 self._rev = len(repo.changelog) - 1
42 self._node = repo.changelog.node(self._rev)
42 self._node = repo.changelog.node(self._rev)
43 return
43 return
44 if len(changeid) == 20:
44 if len(changeid) == 20:
45 try:
45 try:
46 self._node = changeid
46 self._node = changeid
47 self._rev = repo.changelog.rev(changeid)
47 self._rev = repo.changelog.rev(changeid)
48 return
48 return
49 except LookupError:
49 except LookupError:
50 pass
50 pass
51
51
52 try:
52 try:
53 r = int(changeid)
53 r = int(changeid)
54 if str(r) != changeid:
54 if str(r) != changeid:
55 raise ValueError
55 raise ValueError
56 l = len(repo.changelog)
56 l = len(repo.changelog)
57 if r < 0:
57 if r < 0:
58 r += l
58 r += l
59 if r < 0 or r >= l:
59 if r < 0 or r >= l:
60 raise ValueError
60 raise ValueError
61 self._rev = r
61 self._rev = r
62 self._node = repo.changelog.node(r)
62 self._node = repo.changelog.node(r)
63 return
63 return
64 except (ValueError, OverflowError):
64 except (ValueError, OverflowError):
65 pass
65 pass
66
66
67 if len(changeid) == 40:
67 if len(changeid) == 40:
68 try:
68 try:
69 self._node = bin(changeid)
69 self._node = bin(changeid)
70 self._rev = repo.changelog.rev(self._node)
70 self._rev = repo.changelog.rev(self._node)
71 return
71 return
72 except (TypeError, LookupError):
72 except (TypeError, LookupError):
73 pass
73 pass
74
74
75 if changeid in repo._bookmarks:
75 if changeid in repo._bookmarks:
76 self._node = repo._bookmarks[changeid]
76 self._node = repo._bookmarks[changeid]
77 self._rev = repo.changelog.rev(self._node)
77 self._rev = repo.changelog.rev(self._node)
78 return
78 return
79 if changeid in repo._tagscache.tags:
79 if changeid in repo._tagscache.tags:
80 self._node = repo._tagscache.tags[changeid]
80 self._node = repo._tagscache.tags[changeid]
81 self._rev = repo.changelog.rev(self._node)
81 self._rev = repo.changelog.rev(self._node)
82 return
82 return
83 try:
83 try:
84 self._node = repo.branchtip(changeid)
84 self._node = repo.branchtip(changeid)
85 self._rev = repo.changelog.rev(self._node)
85 self._rev = repo.changelog.rev(self._node)
86 return
86 return
87 except error.RepoLookupError:
87 except error.RepoLookupError:
88 pass
88 pass
89
89
90 self._node = repo.changelog._partialmatch(changeid)
90 self._node = repo.changelog._partialmatch(changeid)
91 if self._node is not None:
91 if self._node is not None:
92 self._rev = repo.changelog.rev(self._node)
92 self._rev = repo.changelog.rev(self._node)
93 return
93 return
94
94
95 # lookup failed
95 # lookup failed
96 # check if it might have come from damaged dirstate
96 # check if it might have come from damaged dirstate
97 if changeid in repo.dirstate.parents():
97 if changeid in repo.dirstate.parents():
98 raise error.Abort(_("working directory has unknown parent '%s'!")
98 raise error.Abort(_("working directory has unknown parent '%s'!")
99 % short(changeid))
99 % short(changeid))
100 try:
100 try:
101 if len(changeid) == 20:
101 if len(changeid) == 20:
102 changeid = hex(changeid)
102 changeid = hex(changeid)
103 except TypeError:
103 except TypeError:
104 pass
104 pass
105 raise error.RepoLookupError(
105 raise error.RepoLookupError(
106 _("unknown revision '%s'") % changeid)
106 _("unknown revision '%s'") % changeid)
107
107
108 def __str__(self):
108 def __str__(self):
109 return short(self.node())
109 return short(self.node())
110
110
111 def __int__(self):
111 def __int__(self):
112 return self.rev()
112 return self.rev()
113
113
114 def __repr__(self):
114 def __repr__(self):
115 return "<changectx %s>" % str(self)
115 return "<changectx %s>" % str(self)
116
116
117 def __hash__(self):
117 def __hash__(self):
118 try:
118 try:
119 return hash(self._rev)
119 return hash(self._rev)
120 except AttributeError:
120 except AttributeError:
121 return id(self)
121 return id(self)
122
122
123 def __eq__(self, other):
123 def __eq__(self, other):
124 try:
124 try:
125 return self._rev == other._rev
125 return self._rev == other._rev
126 except AttributeError:
126 except AttributeError:
127 return False
127 return False
128
128
129 def __ne__(self, other):
129 def __ne__(self, other):
130 return not (self == other)
130 return not (self == other)
131
131
132 def __nonzero__(self):
132 def __nonzero__(self):
133 return self._rev != nullrev
133 return self._rev != nullrev
134
134
135 @propertycache
135 @propertycache
136 def _changeset(self):
136 def _changeset(self):
137 return self._repo.changelog.read(self.rev())
137 return self._repo.changelog.read(self.rev())
138
138
139 @propertycache
139 @propertycache
140 def _manifest(self):
140 def _manifest(self):
141 return self._repo.manifest.read(self._changeset[0])
141 return self._repo.manifest.read(self._changeset[0])
142
142
143 @propertycache
143 @propertycache
144 def _manifestdelta(self):
144 def _manifestdelta(self):
145 return self._repo.manifest.readdelta(self._changeset[0])
145 return self._repo.manifest.readdelta(self._changeset[0])
146
146
147 @propertycache
147 @propertycache
148 def _parents(self):
148 def _parents(self):
149 p = self._repo.changelog.parentrevs(self._rev)
149 p = self._repo.changelog.parentrevs(self._rev)
150 if p[1] == nullrev:
150 if p[1] == nullrev:
151 p = p[:-1]
151 p = p[:-1]
152 return [changectx(self._repo, x) for x in p]
152 return [changectx(self._repo, x) for x in p]
153
153
154 @propertycache
154 @propertycache
155 def substate(self):
155 def substate(self):
156 return subrepo.state(self, self._repo.ui)
156 return subrepo.state(self, self._repo.ui)
157
157
158 def __contains__(self, key):
158 def __contains__(self, key):
159 return key in self._manifest
159 return key in self._manifest
160
160
161 def __getitem__(self, key):
161 def __getitem__(self, key):
162 return self.filectx(key)
162 return self.filectx(key)
163
163
164 def __iter__(self):
164 def __iter__(self):
165 for f in sorted(self._manifest):
165 for f in sorted(self._manifest):
166 yield f
166 yield f
167
167
168 def changeset(self):
168 def changeset(self):
169 return self._changeset
169 return self._changeset
170 def manifest(self):
170 def manifest(self):
171 return self._manifest
171 return self._manifest
172 def manifestnode(self):
172 def manifestnode(self):
173 return self._changeset[0]
173 return self._changeset[0]
174
174
175 def rev(self):
175 def rev(self):
176 return self._rev
176 return self._rev
177 def node(self):
177 def node(self):
178 return self._node
178 return self._node
179 def hex(self):
179 def hex(self):
180 return hex(self._node)
180 return hex(self._node)
181 def user(self):
181 def user(self):
182 return self._changeset[1]
182 return self._changeset[1]
183 def date(self):
183 def date(self):
184 return self._changeset[2]
184 return self._changeset[2]
185 def files(self):
185 def files(self):
186 return self._changeset[3]
186 return self._changeset[3]
187 def description(self):
187 def description(self):
188 return self._changeset[4]
188 return self._changeset[4]
189 def branch(self):
189 def branch(self):
190 return encoding.tolocal(self._changeset[5].get("branch"))
190 return encoding.tolocal(self._changeset[5].get("branch"))
191 def closesbranch(self):
191 def closesbranch(self):
192 return 'close' in self._changeset[5]
192 return 'close' in self._changeset[5]
193 def extra(self):
193 def extra(self):
194 return self._changeset[5]
194 return self._changeset[5]
195 def tags(self):
195 def tags(self):
196 return self._repo.nodetags(self._node)
196 return self._repo.nodetags(self._node)
197 def bookmarks(self):
197 def bookmarks(self):
198 return self._repo.nodebookmarks(self._node)
198 return self._repo.nodebookmarks(self._node)
199 def phase(self):
199 def phase(self):
200 return self._repo._phasecache.phase(self._repo, self._rev)
200 return self._repo._phasecache.phase(self._repo, self._rev)
201 def phasestr(self):
201 def phasestr(self):
202 return phases.phasenames[self.phase()]
202 return phases.phasenames[self.phase()]
203 def mutable(self):
203 def mutable(self):
204 return self.phase() > phases.public
204 return self.phase() > phases.public
205 def hidden(self):
205 def hidden(self):
206 return self._rev in self._repo.changelog.hiddenrevs
206 return self._rev in self._repo.changelog.hiddenrevs
207
207
208 def parents(self):
208 def parents(self):
209 """return contexts for each parent changeset"""
209 """return contexts for each parent changeset"""
210 return self._parents
210 return self._parents
211
211
212 def p1(self):
212 def p1(self):
213 return self._parents[0]
213 return self._parents[0]
214
214
215 def p2(self):
215 def p2(self):
216 if len(self._parents) == 2:
216 if len(self._parents) == 2:
217 return self._parents[1]
217 return self._parents[1]
218 return changectx(self._repo, -1)
218 return changectx(self._repo, -1)
219
219
220 def children(self):
220 def children(self):
221 """return contexts for each child changeset"""
221 """return contexts for each child changeset"""
222 c = self._repo.changelog.children(self._node)
222 c = self._repo.changelog.children(self._node)
223 return [changectx(self._repo, x) for x in c]
223 return [changectx(self._repo, x) for x in c]
224
224
225 def ancestors(self):
225 def ancestors(self):
226 for a in self._repo.changelog.ancestors([self._rev]):
226 for a in self._repo.changelog.ancestors([self._rev]):
227 yield changectx(self._repo, a)
227 yield changectx(self._repo, a)
228
228
229 def descendants(self):
229 def descendants(self):
230 for d in self._repo.changelog.descendants([self._rev]):
230 for d in self._repo.changelog.descendants([self._rev]):
231 yield changectx(self._repo, d)
231 yield changectx(self._repo, d)
232
232
233 def obsolete(self):
233 def obsolete(self):
234 """True if the changeset is obsolete"""
234 """True if the changeset is obsolete"""
235 return (self.node() in self._repo.obsstore.precursors
235 return (self.node() in self._repo.obsstore.precursors
236 and self.phase() > phases.public)
236 and self.phase() > phases.public)
237
237
238 def unstable(self):
239 """True if the changeset is not obsolete but it's ancestor are"""
240 # We should just compute /(obsolete()::) - obsolete()/
241 # and keep it in a cache.
242 #
243 # But this naive implementation does not require cache
244 if self.phase() <= phases.public:
245 return False
246 if self.obsolete():
247 return False
248 for anc in self.ancestors():
249 if anc.obsolete():
250 return True
251 return False
252
238 def _fileinfo(self, path):
253 def _fileinfo(self, path):
239 if '_manifest' in self.__dict__:
254 if '_manifest' in self.__dict__:
240 try:
255 try:
241 return self._manifest[path], self._manifest.flags(path)
256 return self._manifest[path], self._manifest.flags(path)
242 except KeyError:
257 except KeyError:
243 raise error.LookupError(self._node, path,
258 raise error.LookupError(self._node, path,
244 _('not found in manifest'))
259 _('not found in manifest'))
245 if '_manifestdelta' in self.__dict__ or path in self.files():
260 if '_manifestdelta' in self.__dict__ or path in self.files():
246 if path in self._manifestdelta:
261 if path in self._manifestdelta:
247 return (self._manifestdelta[path],
262 return (self._manifestdelta[path],
248 self._manifestdelta.flags(path))
263 self._manifestdelta.flags(path))
249 node, flag = self._repo.manifest.find(self._changeset[0], path)
264 node, flag = self._repo.manifest.find(self._changeset[0], path)
250 if not node:
265 if not node:
251 raise error.LookupError(self._node, path,
266 raise error.LookupError(self._node, path,
252 _('not found in manifest'))
267 _('not found in manifest'))
253
268
254 return node, flag
269 return node, flag
255
270
256 def filenode(self, path):
271 def filenode(self, path):
257 return self._fileinfo(path)[0]
272 return self._fileinfo(path)[0]
258
273
259 def flags(self, path):
274 def flags(self, path):
260 try:
275 try:
261 return self._fileinfo(path)[1]
276 return self._fileinfo(path)[1]
262 except error.LookupError:
277 except error.LookupError:
263 return ''
278 return ''
264
279
265 def filectx(self, path, fileid=None, filelog=None):
280 def filectx(self, path, fileid=None, filelog=None):
266 """get a file context from this changeset"""
281 """get a file context from this changeset"""
267 if fileid is None:
282 if fileid is None:
268 fileid = self.filenode(path)
283 fileid = self.filenode(path)
269 return filectx(self._repo, path, fileid=fileid,
284 return filectx(self._repo, path, fileid=fileid,
270 changectx=self, filelog=filelog)
285 changectx=self, filelog=filelog)
271
286
272 def ancestor(self, c2):
287 def ancestor(self, c2):
273 """
288 """
274 return the ancestor context of self and c2
289 return the ancestor context of self and c2
275 """
290 """
276 # deal with workingctxs
291 # deal with workingctxs
277 n2 = c2._node
292 n2 = c2._node
278 if n2 is None:
293 if n2 is None:
279 n2 = c2._parents[0]._node
294 n2 = c2._parents[0]._node
280 n = self._repo.changelog.ancestor(self._node, n2)
295 n = self._repo.changelog.ancestor(self._node, n2)
281 return changectx(self._repo, n)
296 return changectx(self._repo, n)
282
297
283 def walk(self, match):
298 def walk(self, match):
284 fset = set(match.files())
299 fset = set(match.files())
285 # for dirstate.walk, files=['.'] means "walk the whole tree".
300 # for dirstate.walk, files=['.'] means "walk the whole tree".
286 # follow that here, too
301 # follow that here, too
287 fset.discard('.')
302 fset.discard('.')
288 for fn in self:
303 for fn in self:
289 if fn in fset:
304 if fn in fset:
290 # specified pattern is the exact name
305 # specified pattern is the exact name
291 fset.remove(fn)
306 fset.remove(fn)
292 if match(fn):
307 if match(fn):
293 yield fn
308 yield fn
294 for fn in sorted(fset):
309 for fn in sorted(fset):
295 if fn in self._dirs:
310 if fn in self._dirs:
296 # specified pattern is a directory
311 # specified pattern is a directory
297 continue
312 continue
298 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
313 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
299 yield fn
314 yield fn
300
315
301 def sub(self, path):
316 def sub(self, path):
302 return subrepo.subrepo(self, path)
317 return subrepo.subrepo(self, path)
303
318
304 def match(self, pats=[], include=None, exclude=None, default='glob'):
319 def match(self, pats=[], include=None, exclude=None, default='glob'):
305 r = self._repo
320 r = self._repo
306 return matchmod.match(r.root, r.getcwd(), pats,
321 return matchmod.match(r.root, r.getcwd(), pats,
307 include, exclude, default,
322 include, exclude, default,
308 auditor=r.auditor, ctx=self)
323 auditor=r.auditor, ctx=self)
309
324
310 def diff(self, ctx2=None, match=None, **opts):
325 def diff(self, ctx2=None, match=None, **opts):
311 """Returns a diff generator for the given contexts and matcher"""
326 """Returns a diff generator for the given contexts and matcher"""
312 if ctx2 is None:
327 if ctx2 is None:
313 ctx2 = self.p1()
328 ctx2 = self.p1()
314 if ctx2 is not None and not isinstance(ctx2, changectx):
329 if ctx2 is not None and not isinstance(ctx2, changectx):
315 ctx2 = self._repo[ctx2]
330 ctx2 = self._repo[ctx2]
316 diffopts = patch.diffopts(self._repo.ui, opts)
331 diffopts = patch.diffopts(self._repo.ui, opts)
317 return patch.diff(self._repo, ctx2.node(), self.node(),
332 return patch.diff(self._repo, ctx2.node(), self.node(),
318 match=match, opts=diffopts)
333 match=match, opts=diffopts)
319
334
320 @propertycache
335 @propertycache
321 def _dirs(self):
336 def _dirs(self):
322 dirs = set()
337 dirs = set()
323 for f in self._manifest:
338 for f in self._manifest:
324 pos = f.rfind('/')
339 pos = f.rfind('/')
325 while pos != -1:
340 while pos != -1:
326 f = f[:pos]
341 f = f[:pos]
327 if f in dirs:
342 if f in dirs:
328 break # dirs already contains this and above
343 break # dirs already contains this and above
329 dirs.add(f)
344 dirs.add(f)
330 pos = f.rfind('/')
345 pos = f.rfind('/')
331 return dirs
346 return dirs
332
347
333 def dirs(self):
348 def dirs(self):
334 return self._dirs
349 return self._dirs
335
350
336 class filectx(object):
351 class filectx(object):
337 """A filecontext object makes access to data related to a particular
352 """A filecontext object makes access to data related to a particular
338 filerevision convenient."""
353 filerevision convenient."""
339 def __init__(self, repo, path, changeid=None, fileid=None,
354 def __init__(self, repo, path, changeid=None, fileid=None,
340 filelog=None, changectx=None):
355 filelog=None, changectx=None):
341 """changeid can be a changeset revision, node, or tag.
356 """changeid can be a changeset revision, node, or tag.
342 fileid can be a file revision or node."""
357 fileid can be a file revision or node."""
343 self._repo = repo
358 self._repo = repo
344 self._path = path
359 self._path = path
345
360
346 assert (changeid is not None
361 assert (changeid is not None
347 or fileid is not None
362 or fileid is not None
348 or changectx is not None), \
363 or changectx is not None), \
349 ("bad args: changeid=%r, fileid=%r, changectx=%r"
364 ("bad args: changeid=%r, fileid=%r, changectx=%r"
350 % (changeid, fileid, changectx))
365 % (changeid, fileid, changectx))
351
366
352 if filelog:
367 if filelog:
353 self._filelog = filelog
368 self._filelog = filelog
354
369
355 if changeid is not None:
370 if changeid is not None:
356 self._changeid = changeid
371 self._changeid = changeid
357 if changectx is not None:
372 if changectx is not None:
358 self._changectx = changectx
373 self._changectx = changectx
359 if fileid is not None:
374 if fileid is not None:
360 self._fileid = fileid
375 self._fileid = fileid
361
376
362 @propertycache
377 @propertycache
363 def _changectx(self):
378 def _changectx(self):
364 return changectx(self._repo, self._changeid)
379 return changectx(self._repo, self._changeid)
365
380
366 @propertycache
381 @propertycache
367 def _filelog(self):
382 def _filelog(self):
368 return self._repo.file(self._path)
383 return self._repo.file(self._path)
369
384
370 @propertycache
385 @propertycache
371 def _changeid(self):
386 def _changeid(self):
372 if '_changectx' in self.__dict__:
387 if '_changectx' in self.__dict__:
373 return self._changectx.rev()
388 return self._changectx.rev()
374 else:
389 else:
375 return self._filelog.linkrev(self._filerev)
390 return self._filelog.linkrev(self._filerev)
376
391
377 @propertycache
392 @propertycache
378 def _filenode(self):
393 def _filenode(self):
379 if '_fileid' in self.__dict__:
394 if '_fileid' in self.__dict__:
380 return self._filelog.lookup(self._fileid)
395 return self._filelog.lookup(self._fileid)
381 else:
396 else:
382 return self._changectx.filenode(self._path)
397 return self._changectx.filenode(self._path)
383
398
384 @propertycache
399 @propertycache
385 def _filerev(self):
400 def _filerev(self):
386 return self._filelog.rev(self._filenode)
401 return self._filelog.rev(self._filenode)
387
402
388 @propertycache
403 @propertycache
389 def _repopath(self):
404 def _repopath(self):
390 return self._path
405 return self._path
391
406
392 def __nonzero__(self):
407 def __nonzero__(self):
393 try:
408 try:
394 self._filenode
409 self._filenode
395 return True
410 return True
396 except error.LookupError:
411 except error.LookupError:
397 # file is missing
412 # file is missing
398 return False
413 return False
399
414
400 def __str__(self):
415 def __str__(self):
401 return "%s@%s" % (self.path(), short(self.node()))
416 return "%s@%s" % (self.path(), short(self.node()))
402
417
403 def __repr__(self):
418 def __repr__(self):
404 return "<filectx %s>" % str(self)
419 return "<filectx %s>" % str(self)
405
420
406 def __hash__(self):
421 def __hash__(self):
407 try:
422 try:
408 return hash((self._path, self._filenode))
423 return hash((self._path, self._filenode))
409 except AttributeError:
424 except AttributeError:
410 return id(self)
425 return id(self)
411
426
412 def __eq__(self, other):
427 def __eq__(self, other):
413 try:
428 try:
414 return (self._path == other._path
429 return (self._path == other._path
415 and self._filenode == other._filenode)
430 and self._filenode == other._filenode)
416 except AttributeError:
431 except AttributeError:
417 return False
432 return False
418
433
419 def __ne__(self, other):
434 def __ne__(self, other):
420 return not (self == other)
435 return not (self == other)
421
436
422 def filectx(self, fileid):
437 def filectx(self, fileid):
423 '''opens an arbitrary revision of the file without
438 '''opens an arbitrary revision of the file without
424 opening a new filelog'''
439 opening a new filelog'''
425 return filectx(self._repo, self._path, fileid=fileid,
440 return filectx(self._repo, self._path, fileid=fileid,
426 filelog=self._filelog)
441 filelog=self._filelog)
427
442
428 def filerev(self):
443 def filerev(self):
429 return self._filerev
444 return self._filerev
430 def filenode(self):
445 def filenode(self):
431 return self._filenode
446 return self._filenode
432 def flags(self):
447 def flags(self):
433 return self._changectx.flags(self._path)
448 return self._changectx.flags(self._path)
434 def filelog(self):
449 def filelog(self):
435 return self._filelog
450 return self._filelog
436
451
437 def rev(self):
452 def rev(self):
438 if '_changectx' in self.__dict__:
453 if '_changectx' in self.__dict__:
439 return self._changectx.rev()
454 return self._changectx.rev()
440 if '_changeid' in self.__dict__:
455 if '_changeid' in self.__dict__:
441 return self._changectx.rev()
456 return self._changectx.rev()
442 return self._filelog.linkrev(self._filerev)
457 return self._filelog.linkrev(self._filerev)
443
458
444 def linkrev(self):
459 def linkrev(self):
445 return self._filelog.linkrev(self._filerev)
460 return self._filelog.linkrev(self._filerev)
446 def node(self):
461 def node(self):
447 return self._changectx.node()
462 return self._changectx.node()
448 def hex(self):
463 def hex(self):
449 return hex(self.node())
464 return hex(self.node())
450 def user(self):
465 def user(self):
451 return self._changectx.user()
466 return self._changectx.user()
452 def date(self):
467 def date(self):
453 return self._changectx.date()
468 return self._changectx.date()
454 def files(self):
469 def files(self):
455 return self._changectx.files()
470 return self._changectx.files()
456 def description(self):
471 def description(self):
457 return self._changectx.description()
472 return self._changectx.description()
458 def branch(self):
473 def branch(self):
459 return self._changectx.branch()
474 return self._changectx.branch()
460 def extra(self):
475 def extra(self):
461 return self._changectx.extra()
476 return self._changectx.extra()
462 def manifest(self):
477 def manifest(self):
463 return self._changectx.manifest()
478 return self._changectx.manifest()
464 def changectx(self):
479 def changectx(self):
465 return self._changectx
480 return self._changectx
466
481
467 def data(self):
482 def data(self):
468 return self._filelog.read(self._filenode)
483 return self._filelog.read(self._filenode)
469 def path(self):
484 def path(self):
470 return self._path
485 return self._path
471 def size(self):
486 def size(self):
472 return self._filelog.size(self._filerev)
487 return self._filelog.size(self._filerev)
473
488
474 def isbinary(self):
489 def isbinary(self):
475 try:
490 try:
476 return util.binary(self.data())
491 return util.binary(self.data())
477 except IOError:
492 except IOError:
478 return False
493 return False
479
494
480 def cmp(self, fctx):
495 def cmp(self, fctx):
481 """compare with other file context
496 """compare with other file context
482
497
483 returns True if different than fctx.
498 returns True if different than fctx.
484 """
499 """
485 if (fctx._filerev is None
500 if (fctx._filerev is None
486 and (self._repo._encodefilterpats
501 and (self._repo._encodefilterpats
487 # if file data starts with '\1\n', empty metadata block is
502 # if file data starts with '\1\n', empty metadata block is
488 # prepended, which adds 4 bytes to filelog.size().
503 # prepended, which adds 4 bytes to filelog.size().
489 or self.size() - 4 == fctx.size())
504 or self.size() - 4 == fctx.size())
490 or self.size() == fctx.size()):
505 or self.size() == fctx.size()):
491 return self._filelog.cmp(self._filenode, fctx.data())
506 return self._filelog.cmp(self._filenode, fctx.data())
492
507
493 return True
508 return True
494
509
495 def renamed(self):
510 def renamed(self):
496 """check if file was actually renamed in this changeset revision
511 """check if file was actually renamed in this changeset revision
497
512
498 If rename logged in file revision, we report copy for changeset only
513 If rename logged in file revision, we report copy for changeset only
499 if file revisions linkrev points back to the changeset in question
514 if file revisions linkrev points back to the changeset in question
500 or both changeset parents contain different file revisions.
515 or both changeset parents contain different file revisions.
501 """
516 """
502
517
503 renamed = self._filelog.renamed(self._filenode)
518 renamed = self._filelog.renamed(self._filenode)
504 if not renamed:
519 if not renamed:
505 return renamed
520 return renamed
506
521
507 if self.rev() == self.linkrev():
522 if self.rev() == self.linkrev():
508 return renamed
523 return renamed
509
524
510 name = self.path()
525 name = self.path()
511 fnode = self._filenode
526 fnode = self._filenode
512 for p in self._changectx.parents():
527 for p in self._changectx.parents():
513 try:
528 try:
514 if fnode == p.filenode(name):
529 if fnode == p.filenode(name):
515 return None
530 return None
516 except error.LookupError:
531 except error.LookupError:
517 pass
532 pass
518 return renamed
533 return renamed
519
534
520 def parents(self):
535 def parents(self):
521 p = self._path
536 p = self._path
522 fl = self._filelog
537 fl = self._filelog
523 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
538 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
524
539
525 r = self._filelog.renamed(self._filenode)
540 r = self._filelog.renamed(self._filenode)
526 if r:
541 if r:
527 pl[0] = (r[0], r[1], None)
542 pl[0] = (r[0], r[1], None)
528
543
529 return [filectx(self._repo, p, fileid=n, filelog=l)
544 return [filectx(self._repo, p, fileid=n, filelog=l)
530 for p, n, l in pl if n != nullid]
545 for p, n, l in pl if n != nullid]
531
546
532 def p1(self):
547 def p1(self):
533 return self.parents()[0]
548 return self.parents()[0]
534
549
535 def p2(self):
550 def p2(self):
536 p = self.parents()
551 p = self.parents()
537 if len(p) == 2:
552 if len(p) == 2:
538 return p[1]
553 return p[1]
539 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
554 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
540
555
541 def children(self):
556 def children(self):
542 # hard for renames
557 # hard for renames
543 c = self._filelog.children(self._filenode)
558 c = self._filelog.children(self._filenode)
544 return [filectx(self._repo, self._path, fileid=x,
559 return [filectx(self._repo, self._path, fileid=x,
545 filelog=self._filelog) for x in c]
560 filelog=self._filelog) for x in c]
546
561
547 def annotate(self, follow=False, linenumber=None, diffopts=None):
562 def annotate(self, follow=False, linenumber=None, diffopts=None):
548 '''returns a list of tuples of (ctx, line) for each line
563 '''returns a list of tuples of (ctx, line) for each line
549 in the file, where ctx is the filectx of the node where
564 in the file, where ctx is the filectx of the node where
550 that line was last changed.
565 that line was last changed.
551 This returns tuples of ((ctx, linenumber), line) for each line,
566 This returns tuples of ((ctx, linenumber), line) for each line,
552 if "linenumber" parameter is NOT "None".
567 if "linenumber" parameter is NOT "None".
553 In such tuples, linenumber means one at the first appearance
568 In such tuples, linenumber means one at the first appearance
554 in the managed file.
569 in the managed file.
555 To reduce annotation cost,
570 To reduce annotation cost,
556 this returns fixed value(False is used) as linenumber,
571 this returns fixed value(False is used) as linenumber,
557 if "linenumber" parameter is "False".'''
572 if "linenumber" parameter is "False".'''
558
573
559 def decorate_compat(text, rev):
574 def decorate_compat(text, rev):
560 return ([rev] * len(text.splitlines()), text)
575 return ([rev] * len(text.splitlines()), text)
561
576
562 def without_linenumber(text, rev):
577 def without_linenumber(text, rev):
563 return ([(rev, False)] * len(text.splitlines()), text)
578 return ([(rev, False)] * len(text.splitlines()), text)
564
579
565 def with_linenumber(text, rev):
580 def with_linenumber(text, rev):
566 size = len(text.splitlines())
581 size = len(text.splitlines())
567 return ([(rev, i) for i in xrange(1, size + 1)], text)
582 return ([(rev, i) for i in xrange(1, size + 1)], text)
568
583
569 decorate = (((linenumber is None) and decorate_compat) or
584 decorate = (((linenumber is None) and decorate_compat) or
570 (linenumber and with_linenumber) or
585 (linenumber and with_linenumber) or
571 without_linenumber)
586 without_linenumber)
572
587
573 def pair(parent, child):
588 def pair(parent, child):
574 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
589 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
575 refine=True)
590 refine=True)
576 for (a1, a2, b1, b2), t in blocks:
591 for (a1, a2, b1, b2), t in blocks:
577 # Changed blocks ('!') or blocks made only of blank lines ('~')
592 # Changed blocks ('!') or blocks made only of blank lines ('~')
578 # belong to the child.
593 # belong to the child.
579 if t == '=':
594 if t == '=':
580 child[0][b1:b2] = parent[0][a1:a2]
595 child[0][b1:b2] = parent[0][a1:a2]
581 return child
596 return child
582
597
583 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
598 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
584 def getctx(path, fileid):
599 def getctx(path, fileid):
585 log = path == self._path and self._filelog or getlog(path)
600 log = path == self._path and self._filelog or getlog(path)
586 return filectx(self._repo, path, fileid=fileid, filelog=log)
601 return filectx(self._repo, path, fileid=fileid, filelog=log)
587 getctx = util.lrucachefunc(getctx)
602 getctx = util.lrucachefunc(getctx)
588
603
589 def parents(f):
604 def parents(f):
590 # we want to reuse filectx objects as much as possible
605 # we want to reuse filectx objects as much as possible
591 p = f._path
606 p = f._path
592 if f._filerev is None: # working dir
607 if f._filerev is None: # working dir
593 pl = [(n.path(), n.filerev()) for n in f.parents()]
608 pl = [(n.path(), n.filerev()) for n in f.parents()]
594 else:
609 else:
595 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
610 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
596
611
597 if follow:
612 if follow:
598 r = f.renamed()
613 r = f.renamed()
599 if r:
614 if r:
600 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
615 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
601
616
602 return [getctx(p, n) for p, n in pl if n != nullrev]
617 return [getctx(p, n) for p, n in pl if n != nullrev]
603
618
604 # use linkrev to find the first changeset where self appeared
619 # use linkrev to find the first changeset where self appeared
605 if self.rev() != self.linkrev():
620 if self.rev() != self.linkrev():
606 base = self.filectx(self.filerev())
621 base = self.filectx(self.filerev())
607 else:
622 else:
608 base = self
623 base = self
609
624
610 # This algorithm would prefer to be recursive, but Python is a
625 # This algorithm would prefer to be recursive, but Python is a
611 # bit recursion-hostile. Instead we do an iterative
626 # bit recursion-hostile. Instead we do an iterative
612 # depth-first search.
627 # depth-first search.
613
628
614 visit = [base]
629 visit = [base]
615 hist = {}
630 hist = {}
616 pcache = {}
631 pcache = {}
617 needed = {base: 1}
632 needed = {base: 1}
618 while visit:
633 while visit:
619 f = visit[-1]
634 f = visit[-1]
620 if f not in pcache:
635 if f not in pcache:
621 pcache[f] = parents(f)
636 pcache[f] = parents(f)
622
637
623 ready = True
638 ready = True
624 pl = pcache[f]
639 pl = pcache[f]
625 for p in pl:
640 for p in pl:
626 if p not in hist:
641 if p not in hist:
627 ready = False
642 ready = False
628 visit.append(p)
643 visit.append(p)
629 needed[p] = needed.get(p, 0) + 1
644 needed[p] = needed.get(p, 0) + 1
630 if ready:
645 if ready:
631 visit.pop()
646 visit.pop()
632 curr = decorate(f.data(), f)
647 curr = decorate(f.data(), f)
633 for p in pl:
648 for p in pl:
634 curr = pair(hist[p], curr)
649 curr = pair(hist[p], curr)
635 if needed[p] == 1:
650 if needed[p] == 1:
636 del hist[p]
651 del hist[p]
637 else:
652 else:
638 needed[p] -= 1
653 needed[p] -= 1
639
654
640 hist[f] = curr
655 hist[f] = curr
641 pcache[f] = []
656 pcache[f] = []
642
657
643 return zip(hist[base][0], hist[base][1].splitlines(True))
658 return zip(hist[base][0], hist[base][1].splitlines(True))
644
659
645 def ancestor(self, fc2, actx):
660 def ancestor(self, fc2, actx):
646 """
661 """
647 find the common ancestor file context, if any, of self, and fc2
662 find the common ancestor file context, if any, of self, and fc2
648
663
649 actx must be the changectx of the common ancestor
664 actx must be the changectx of the common ancestor
650 of self's and fc2's respective changesets.
665 of self's and fc2's respective changesets.
651 """
666 """
652
667
653 # the easy case: no (relevant) renames
668 # the easy case: no (relevant) renames
654 if fc2.path() == self.path() and self.path() in actx:
669 if fc2.path() == self.path() and self.path() in actx:
655 return actx[self.path()]
670 return actx[self.path()]
656
671
657 # the next easiest cases: unambiguous predecessor (name trumps
672 # the next easiest cases: unambiguous predecessor (name trumps
658 # history)
673 # history)
659 if self.path() in actx and fc2.path() not in actx:
674 if self.path() in actx and fc2.path() not in actx:
660 return actx[self.path()]
675 return actx[self.path()]
661 if fc2.path() in actx and self.path() not in actx:
676 if fc2.path() in actx and self.path() not in actx:
662 return actx[fc2.path()]
677 return actx[fc2.path()]
663
678
664 # prime the ancestor cache for the working directory
679 # prime the ancestor cache for the working directory
665 acache = {}
680 acache = {}
666 for c in (self, fc2):
681 for c in (self, fc2):
667 if c._filerev is None:
682 if c._filerev is None:
668 pl = [(n.path(), n.filenode()) for n in c.parents()]
683 pl = [(n.path(), n.filenode()) for n in c.parents()]
669 acache[(c._path, None)] = pl
684 acache[(c._path, None)] = pl
670
685
671 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
686 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
672 def parents(vertex):
687 def parents(vertex):
673 if vertex in acache:
688 if vertex in acache:
674 return acache[vertex]
689 return acache[vertex]
675 f, n = vertex
690 f, n = vertex
676 if f not in flcache:
691 if f not in flcache:
677 flcache[f] = self._repo.file(f)
692 flcache[f] = self._repo.file(f)
678 fl = flcache[f]
693 fl = flcache[f]
679 pl = [(f, p) for p in fl.parents(n) if p != nullid]
694 pl = [(f, p) for p in fl.parents(n) if p != nullid]
680 re = fl.renamed(n)
695 re = fl.renamed(n)
681 if re:
696 if re:
682 pl.append(re)
697 pl.append(re)
683 acache[vertex] = pl
698 acache[vertex] = pl
684 return pl
699 return pl
685
700
686 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
701 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
687 v = ancestor.ancestor(a, b, parents)
702 v = ancestor.ancestor(a, b, parents)
688 if v:
703 if v:
689 f, n = v
704 f, n = v
690 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
705 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
691
706
692 return None
707 return None
693
708
694 def ancestors(self, followfirst=False):
709 def ancestors(self, followfirst=False):
695 visit = {}
710 visit = {}
696 c = self
711 c = self
697 cut = followfirst and 1 or None
712 cut = followfirst and 1 or None
698 while True:
713 while True:
699 for parent in c.parents()[:cut]:
714 for parent in c.parents()[:cut]:
700 visit[(parent.rev(), parent.node())] = parent
715 visit[(parent.rev(), parent.node())] = parent
701 if not visit:
716 if not visit:
702 break
717 break
703 c = visit.pop(max(visit))
718 c = visit.pop(max(visit))
704 yield c
719 yield c
705
720
706 def copies(self, c2):
721 def copies(self, c2):
707 if not util.safehasattr(self, "_copycache"):
722 if not util.safehasattr(self, "_copycache"):
708 self._copycache = {}
723 self._copycache = {}
709 sc2 = str(c2)
724 sc2 = str(c2)
710 if sc2 not in self._copycache:
725 if sc2 not in self._copycache:
711 self._copycache[sc2] = copies.pathcopies(c2)
726 self._copycache[sc2] = copies.pathcopies(c2)
712 return self._copycache[sc2]
727 return self._copycache[sc2]
713
728
714 class workingctx(changectx):
729 class workingctx(changectx):
715 """A workingctx object makes access to data related to
730 """A workingctx object makes access to data related to
716 the current working directory convenient.
731 the current working directory convenient.
717 date - any valid date string or (unixtime, offset), or None.
732 date - any valid date string or (unixtime, offset), or None.
718 user - username string, or None.
733 user - username string, or None.
719 extra - a dictionary of extra values, or None.
734 extra - a dictionary of extra values, or None.
720 changes - a list of file lists as returned by localrepo.status()
735 changes - a list of file lists as returned by localrepo.status()
721 or None to use the repository status.
736 or None to use the repository status.
722 """
737 """
723 def __init__(self, repo, text="", user=None, date=None, extra=None,
738 def __init__(self, repo, text="", user=None, date=None, extra=None,
724 changes=None):
739 changes=None):
725 self._repo = repo
740 self._repo = repo
726 self._rev = None
741 self._rev = None
727 self._node = None
742 self._node = None
728 self._text = text
743 self._text = text
729 if date:
744 if date:
730 self._date = util.parsedate(date)
745 self._date = util.parsedate(date)
731 if user:
746 if user:
732 self._user = user
747 self._user = user
733 if changes:
748 if changes:
734 self._status = list(changes[:4])
749 self._status = list(changes[:4])
735 self._unknown = changes[4]
750 self._unknown = changes[4]
736 self._ignored = changes[5]
751 self._ignored = changes[5]
737 self._clean = changes[6]
752 self._clean = changes[6]
738 else:
753 else:
739 self._unknown = None
754 self._unknown = None
740 self._ignored = None
755 self._ignored = None
741 self._clean = None
756 self._clean = None
742
757
743 self._extra = {}
758 self._extra = {}
744 if extra:
759 if extra:
745 self._extra = extra.copy()
760 self._extra = extra.copy()
746 if 'branch' not in self._extra:
761 if 'branch' not in self._extra:
747 try:
762 try:
748 branch = encoding.fromlocal(self._repo.dirstate.branch())
763 branch = encoding.fromlocal(self._repo.dirstate.branch())
749 except UnicodeDecodeError:
764 except UnicodeDecodeError:
750 raise util.Abort(_('branch name not in UTF-8!'))
765 raise util.Abort(_('branch name not in UTF-8!'))
751 self._extra['branch'] = branch
766 self._extra['branch'] = branch
752 if self._extra['branch'] == '':
767 if self._extra['branch'] == '':
753 self._extra['branch'] = 'default'
768 self._extra['branch'] = 'default'
754
769
755 def __str__(self):
770 def __str__(self):
756 return str(self._parents[0]) + "+"
771 return str(self._parents[0]) + "+"
757
772
758 def __repr__(self):
773 def __repr__(self):
759 return "<workingctx %s>" % str(self)
774 return "<workingctx %s>" % str(self)
760
775
761 def __nonzero__(self):
776 def __nonzero__(self):
762 return True
777 return True
763
778
764 def __contains__(self, key):
779 def __contains__(self, key):
765 return self._repo.dirstate[key] not in "?r"
780 return self._repo.dirstate[key] not in "?r"
766
781
767 def _buildflagfunc(self):
782 def _buildflagfunc(self):
768 # Create a fallback function for getting file flags when the
783 # Create a fallback function for getting file flags when the
769 # filesystem doesn't support them
784 # filesystem doesn't support them
770
785
771 copiesget = self._repo.dirstate.copies().get
786 copiesget = self._repo.dirstate.copies().get
772
787
773 if len(self._parents) < 2:
788 if len(self._parents) < 2:
774 # when we have one parent, it's easy: copy from parent
789 # when we have one parent, it's easy: copy from parent
775 man = self._parents[0].manifest()
790 man = self._parents[0].manifest()
776 def func(f):
791 def func(f):
777 f = copiesget(f, f)
792 f = copiesget(f, f)
778 return man.flags(f)
793 return man.flags(f)
779 else:
794 else:
780 # merges are tricky: we try to reconstruct the unstored
795 # merges are tricky: we try to reconstruct the unstored
781 # result from the merge (issue1802)
796 # result from the merge (issue1802)
782 p1, p2 = self._parents
797 p1, p2 = self._parents
783 pa = p1.ancestor(p2)
798 pa = p1.ancestor(p2)
784 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
799 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
785
800
786 def func(f):
801 def func(f):
787 f = copiesget(f, f) # may be wrong for merges with copies
802 f = copiesget(f, f) # may be wrong for merges with copies
788 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
803 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
789 if fl1 == fl2:
804 if fl1 == fl2:
790 return fl1
805 return fl1
791 if fl1 == fla:
806 if fl1 == fla:
792 return fl2
807 return fl2
793 if fl2 == fla:
808 if fl2 == fla:
794 return fl1
809 return fl1
795 return '' # punt for conflicts
810 return '' # punt for conflicts
796
811
797 return func
812 return func
798
813
799 @propertycache
814 @propertycache
800 def _flagfunc(self):
815 def _flagfunc(self):
801 return self._repo.dirstate.flagfunc(self._buildflagfunc)
816 return self._repo.dirstate.flagfunc(self._buildflagfunc)
802
817
803 @propertycache
818 @propertycache
804 def _manifest(self):
819 def _manifest(self):
805 """generate a manifest corresponding to the working directory"""
820 """generate a manifest corresponding to the working directory"""
806
821
807 man = self._parents[0].manifest().copy()
822 man = self._parents[0].manifest().copy()
808 if len(self._parents) > 1:
823 if len(self._parents) > 1:
809 man2 = self.p2().manifest()
824 man2 = self.p2().manifest()
810 def getman(f):
825 def getman(f):
811 if f in man:
826 if f in man:
812 return man
827 return man
813 return man2
828 return man2
814 else:
829 else:
815 getman = lambda f: man
830 getman = lambda f: man
816
831
817 copied = self._repo.dirstate.copies()
832 copied = self._repo.dirstate.copies()
818 ff = self._flagfunc
833 ff = self._flagfunc
819 modified, added, removed, deleted = self._status
834 modified, added, removed, deleted = self._status
820 for i, l in (("a", added), ("m", modified)):
835 for i, l in (("a", added), ("m", modified)):
821 for f in l:
836 for f in l:
822 orig = copied.get(f, f)
837 orig = copied.get(f, f)
823 man[f] = getman(orig).get(orig, nullid) + i
838 man[f] = getman(orig).get(orig, nullid) + i
824 try:
839 try:
825 man.set(f, ff(f))
840 man.set(f, ff(f))
826 except OSError:
841 except OSError:
827 pass
842 pass
828
843
829 for f in deleted + removed:
844 for f in deleted + removed:
830 if f in man:
845 if f in man:
831 del man[f]
846 del man[f]
832
847
833 return man
848 return man
834
849
835 def __iter__(self):
850 def __iter__(self):
836 d = self._repo.dirstate
851 d = self._repo.dirstate
837 for f in d:
852 for f in d:
838 if d[f] != 'r':
853 if d[f] != 'r':
839 yield f
854 yield f
840
855
841 @propertycache
856 @propertycache
842 def _status(self):
857 def _status(self):
843 return self._repo.status()[:4]
858 return self._repo.status()[:4]
844
859
845 @propertycache
860 @propertycache
846 def _user(self):
861 def _user(self):
847 return self._repo.ui.username()
862 return self._repo.ui.username()
848
863
849 @propertycache
864 @propertycache
850 def _date(self):
865 def _date(self):
851 return util.makedate()
866 return util.makedate()
852
867
853 @propertycache
868 @propertycache
854 def _parents(self):
869 def _parents(self):
855 p = self._repo.dirstate.parents()
870 p = self._repo.dirstate.parents()
856 if p[1] == nullid:
871 if p[1] == nullid:
857 p = p[:-1]
872 p = p[:-1]
858 self._parents = [changectx(self._repo, x) for x in p]
873 self._parents = [changectx(self._repo, x) for x in p]
859 return self._parents
874 return self._parents
860
875
861 def status(self, ignored=False, clean=False, unknown=False):
876 def status(self, ignored=False, clean=False, unknown=False):
862 """Explicit status query
877 """Explicit status query
863 Unless this method is used to query the working copy status, the
878 Unless this method is used to query the working copy status, the
864 _status property will implicitly read the status using its default
879 _status property will implicitly read the status using its default
865 arguments."""
880 arguments."""
866 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
881 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
867 self._unknown = self._ignored = self._clean = None
882 self._unknown = self._ignored = self._clean = None
868 if unknown:
883 if unknown:
869 self._unknown = stat[4]
884 self._unknown = stat[4]
870 if ignored:
885 if ignored:
871 self._ignored = stat[5]
886 self._ignored = stat[5]
872 if clean:
887 if clean:
873 self._clean = stat[6]
888 self._clean = stat[6]
874 self._status = stat[:4]
889 self._status = stat[:4]
875 return stat
890 return stat
876
891
877 def manifest(self):
892 def manifest(self):
878 return self._manifest
893 return self._manifest
879 def user(self):
894 def user(self):
880 return self._user or self._repo.ui.username()
895 return self._user or self._repo.ui.username()
881 def date(self):
896 def date(self):
882 return self._date
897 return self._date
883 def description(self):
898 def description(self):
884 return self._text
899 return self._text
885 def files(self):
900 def files(self):
886 return sorted(self._status[0] + self._status[1] + self._status[2])
901 return sorted(self._status[0] + self._status[1] + self._status[2])
887
902
888 def modified(self):
903 def modified(self):
889 return self._status[0]
904 return self._status[0]
890 def added(self):
905 def added(self):
891 return self._status[1]
906 return self._status[1]
892 def removed(self):
907 def removed(self):
893 return self._status[2]
908 return self._status[2]
894 def deleted(self):
909 def deleted(self):
895 return self._status[3]
910 return self._status[3]
896 def unknown(self):
911 def unknown(self):
897 assert self._unknown is not None # must call status first
912 assert self._unknown is not None # must call status first
898 return self._unknown
913 return self._unknown
899 def ignored(self):
914 def ignored(self):
900 assert self._ignored is not None # must call status first
915 assert self._ignored is not None # must call status first
901 return self._ignored
916 return self._ignored
902 def clean(self):
917 def clean(self):
903 assert self._clean is not None # must call status first
918 assert self._clean is not None # must call status first
904 return self._clean
919 return self._clean
905 def branch(self):
920 def branch(self):
906 return encoding.tolocal(self._extra['branch'])
921 return encoding.tolocal(self._extra['branch'])
907 def closesbranch(self):
922 def closesbranch(self):
908 return 'close' in self._extra
923 return 'close' in self._extra
909 def extra(self):
924 def extra(self):
910 return self._extra
925 return self._extra
911
926
912 def tags(self):
927 def tags(self):
913 t = []
928 t = []
914 for p in self.parents():
929 for p in self.parents():
915 t.extend(p.tags())
930 t.extend(p.tags())
916 return t
931 return t
917
932
918 def bookmarks(self):
933 def bookmarks(self):
919 b = []
934 b = []
920 for p in self.parents():
935 for p in self.parents():
921 b.extend(p.bookmarks())
936 b.extend(p.bookmarks())
922 return b
937 return b
923
938
924 def phase(self):
939 def phase(self):
925 phase = phases.draft # default phase to draft
940 phase = phases.draft # default phase to draft
926 for p in self.parents():
941 for p in self.parents():
927 phase = max(phase, p.phase())
942 phase = max(phase, p.phase())
928 return phase
943 return phase
929
944
930 def hidden(self):
945 def hidden(self):
931 return False
946 return False
932
947
933 def children(self):
948 def children(self):
934 return []
949 return []
935
950
936 def flags(self, path):
951 def flags(self, path):
937 if '_manifest' in self.__dict__:
952 if '_manifest' in self.__dict__:
938 try:
953 try:
939 return self._manifest.flags(path)
954 return self._manifest.flags(path)
940 except KeyError:
955 except KeyError:
941 return ''
956 return ''
942
957
943 try:
958 try:
944 return self._flagfunc(path)
959 return self._flagfunc(path)
945 except OSError:
960 except OSError:
946 return ''
961 return ''
947
962
948 def filectx(self, path, filelog=None):
963 def filectx(self, path, filelog=None):
949 """get a file context from the working directory"""
964 """get a file context from the working directory"""
950 return workingfilectx(self._repo, path, workingctx=self,
965 return workingfilectx(self._repo, path, workingctx=self,
951 filelog=filelog)
966 filelog=filelog)
952
967
953 def ancestor(self, c2):
968 def ancestor(self, c2):
954 """return the ancestor context of self and c2"""
969 """return the ancestor context of self and c2"""
955 return self._parents[0].ancestor(c2) # punt on two parents for now
970 return self._parents[0].ancestor(c2) # punt on two parents for now
956
971
957 def walk(self, match):
972 def walk(self, match):
958 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
973 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
959 True, False))
974 True, False))
960
975
961 def dirty(self, missing=False, merge=True, branch=True):
976 def dirty(self, missing=False, merge=True, branch=True):
962 "check whether a working directory is modified"
977 "check whether a working directory is modified"
963 # check subrepos first
978 # check subrepos first
964 for s in self.substate:
979 for s in self.substate:
965 if self.sub(s).dirty():
980 if self.sub(s).dirty():
966 return True
981 return True
967 # check current working dir
982 # check current working dir
968 return ((merge and self.p2()) or
983 return ((merge and self.p2()) or
969 (branch and self.branch() != self.p1().branch()) or
984 (branch and self.branch() != self.p1().branch()) or
970 self.modified() or self.added() or self.removed() or
985 self.modified() or self.added() or self.removed() or
971 (missing and self.deleted()))
986 (missing and self.deleted()))
972
987
973 def add(self, list, prefix=""):
988 def add(self, list, prefix=""):
974 join = lambda f: os.path.join(prefix, f)
989 join = lambda f: os.path.join(prefix, f)
975 wlock = self._repo.wlock()
990 wlock = self._repo.wlock()
976 ui, ds = self._repo.ui, self._repo.dirstate
991 ui, ds = self._repo.ui, self._repo.dirstate
977 try:
992 try:
978 rejected = []
993 rejected = []
979 for f in list:
994 for f in list:
980 scmutil.checkportable(ui, join(f))
995 scmutil.checkportable(ui, join(f))
981 p = self._repo.wjoin(f)
996 p = self._repo.wjoin(f)
982 try:
997 try:
983 st = os.lstat(p)
998 st = os.lstat(p)
984 except OSError:
999 except OSError:
985 ui.warn(_("%s does not exist!\n") % join(f))
1000 ui.warn(_("%s does not exist!\n") % join(f))
986 rejected.append(f)
1001 rejected.append(f)
987 continue
1002 continue
988 if st.st_size > 10000000:
1003 if st.st_size > 10000000:
989 ui.warn(_("%s: up to %d MB of RAM may be required "
1004 ui.warn(_("%s: up to %d MB of RAM may be required "
990 "to manage this file\n"
1005 "to manage this file\n"
991 "(use 'hg revert %s' to cancel the "
1006 "(use 'hg revert %s' to cancel the "
992 "pending addition)\n")
1007 "pending addition)\n")
993 % (f, 3 * st.st_size // 1000000, join(f)))
1008 % (f, 3 * st.st_size // 1000000, join(f)))
994 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1009 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
995 ui.warn(_("%s not added: only files and symlinks "
1010 ui.warn(_("%s not added: only files and symlinks "
996 "supported currently\n") % join(f))
1011 "supported currently\n") % join(f))
997 rejected.append(p)
1012 rejected.append(p)
998 elif ds[f] in 'amn':
1013 elif ds[f] in 'amn':
999 ui.warn(_("%s already tracked!\n") % join(f))
1014 ui.warn(_("%s already tracked!\n") % join(f))
1000 elif ds[f] == 'r':
1015 elif ds[f] == 'r':
1001 ds.normallookup(f)
1016 ds.normallookup(f)
1002 else:
1017 else:
1003 ds.add(f)
1018 ds.add(f)
1004 return rejected
1019 return rejected
1005 finally:
1020 finally:
1006 wlock.release()
1021 wlock.release()
1007
1022
1008 def forget(self, files, prefix=""):
1023 def forget(self, files, prefix=""):
1009 join = lambda f: os.path.join(prefix, f)
1024 join = lambda f: os.path.join(prefix, f)
1010 wlock = self._repo.wlock()
1025 wlock = self._repo.wlock()
1011 try:
1026 try:
1012 rejected = []
1027 rejected = []
1013 for f in files:
1028 for f in files:
1014 if f not in self._repo.dirstate:
1029 if f not in self._repo.dirstate:
1015 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1030 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1016 rejected.append(f)
1031 rejected.append(f)
1017 elif self._repo.dirstate[f] != 'a':
1032 elif self._repo.dirstate[f] != 'a':
1018 self._repo.dirstate.remove(f)
1033 self._repo.dirstate.remove(f)
1019 else:
1034 else:
1020 self._repo.dirstate.drop(f)
1035 self._repo.dirstate.drop(f)
1021 return rejected
1036 return rejected
1022 finally:
1037 finally:
1023 wlock.release()
1038 wlock.release()
1024
1039
1025 def ancestors(self):
1040 def ancestors(self):
1026 for a in self._repo.changelog.ancestors(
1041 for a in self._repo.changelog.ancestors(
1027 [p.rev() for p in self._parents]):
1042 [p.rev() for p in self._parents]):
1028 yield changectx(self._repo, a)
1043 yield changectx(self._repo, a)
1029
1044
1030 def undelete(self, list):
1045 def undelete(self, list):
1031 pctxs = self.parents()
1046 pctxs = self.parents()
1032 wlock = self._repo.wlock()
1047 wlock = self._repo.wlock()
1033 try:
1048 try:
1034 for f in list:
1049 for f in list:
1035 if self._repo.dirstate[f] != 'r':
1050 if self._repo.dirstate[f] != 'r':
1036 self._repo.ui.warn(_("%s not removed!\n") % f)
1051 self._repo.ui.warn(_("%s not removed!\n") % f)
1037 else:
1052 else:
1038 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1053 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1039 t = fctx.data()
1054 t = fctx.data()
1040 self._repo.wwrite(f, t, fctx.flags())
1055 self._repo.wwrite(f, t, fctx.flags())
1041 self._repo.dirstate.normal(f)
1056 self._repo.dirstate.normal(f)
1042 finally:
1057 finally:
1043 wlock.release()
1058 wlock.release()
1044
1059
1045 def copy(self, source, dest):
1060 def copy(self, source, dest):
1046 p = self._repo.wjoin(dest)
1061 p = self._repo.wjoin(dest)
1047 if not os.path.lexists(p):
1062 if not os.path.lexists(p):
1048 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1063 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1049 elif not (os.path.isfile(p) or os.path.islink(p)):
1064 elif not (os.path.isfile(p) or os.path.islink(p)):
1050 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1065 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1051 "symbolic link\n") % dest)
1066 "symbolic link\n") % dest)
1052 else:
1067 else:
1053 wlock = self._repo.wlock()
1068 wlock = self._repo.wlock()
1054 try:
1069 try:
1055 if self._repo.dirstate[dest] in '?r':
1070 if self._repo.dirstate[dest] in '?r':
1056 self._repo.dirstate.add(dest)
1071 self._repo.dirstate.add(dest)
1057 self._repo.dirstate.copy(source, dest)
1072 self._repo.dirstate.copy(source, dest)
1058 finally:
1073 finally:
1059 wlock.release()
1074 wlock.release()
1060
1075
1061 def dirs(self):
1076 def dirs(self):
1062 return set(self._repo.dirstate.dirs())
1077 return set(self._repo.dirstate.dirs())
1063
1078
1064 class workingfilectx(filectx):
1079 class workingfilectx(filectx):
1065 """A workingfilectx object makes access to data related to a particular
1080 """A workingfilectx object makes access to data related to a particular
1066 file in the working directory convenient."""
1081 file in the working directory convenient."""
1067 def __init__(self, repo, path, filelog=None, workingctx=None):
1082 def __init__(self, repo, path, filelog=None, workingctx=None):
1068 """changeid can be a changeset revision, node, or tag.
1083 """changeid can be a changeset revision, node, or tag.
1069 fileid can be a file revision or node."""
1084 fileid can be a file revision or node."""
1070 self._repo = repo
1085 self._repo = repo
1071 self._path = path
1086 self._path = path
1072 self._changeid = None
1087 self._changeid = None
1073 self._filerev = self._filenode = None
1088 self._filerev = self._filenode = None
1074
1089
1075 if filelog:
1090 if filelog:
1076 self._filelog = filelog
1091 self._filelog = filelog
1077 if workingctx:
1092 if workingctx:
1078 self._changectx = workingctx
1093 self._changectx = workingctx
1079
1094
1080 @propertycache
1095 @propertycache
1081 def _changectx(self):
1096 def _changectx(self):
1082 return workingctx(self._repo)
1097 return workingctx(self._repo)
1083
1098
1084 def __nonzero__(self):
1099 def __nonzero__(self):
1085 return True
1100 return True
1086
1101
1087 def __str__(self):
1102 def __str__(self):
1088 return "%s@%s" % (self.path(), self._changectx)
1103 return "%s@%s" % (self.path(), self._changectx)
1089
1104
1090 def __repr__(self):
1105 def __repr__(self):
1091 return "<workingfilectx %s>" % str(self)
1106 return "<workingfilectx %s>" % str(self)
1092
1107
1093 def data(self):
1108 def data(self):
1094 return self._repo.wread(self._path)
1109 return self._repo.wread(self._path)
1095 def renamed(self):
1110 def renamed(self):
1096 rp = self._repo.dirstate.copied(self._path)
1111 rp = self._repo.dirstate.copied(self._path)
1097 if not rp:
1112 if not rp:
1098 return None
1113 return None
1099 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1114 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1100
1115
1101 def parents(self):
1116 def parents(self):
1102 '''return parent filectxs, following copies if necessary'''
1117 '''return parent filectxs, following copies if necessary'''
1103 def filenode(ctx, path):
1118 def filenode(ctx, path):
1104 return ctx._manifest.get(path, nullid)
1119 return ctx._manifest.get(path, nullid)
1105
1120
1106 path = self._path
1121 path = self._path
1107 fl = self._filelog
1122 fl = self._filelog
1108 pcl = self._changectx._parents
1123 pcl = self._changectx._parents
1109 renamed = self.renamed()
1124 renamed = self.renamed()
1110
1125
1111 if renamed:
1126 if renamed:
1112 pl = [renamed + (None,)]
1127 pl = [renamed + (None,)]
1113 else:
1128 else:
1114 pl = [(path, filenode(pcl[0], path), fl)]
1129 pl = [(path, filenode(pcl[0], path), fl)]
1115
1130
1116 for pc in pcl[1:]:
1131 for pc in pcl[1:]:
1117 pl.append((path, filenode(pc, path), fl))
1132 pl.append((path, filenode(pc, path), fl))
1118
1133
1119 return [filectx(self._repo, p, fileid=n, filelog=l)
1134 return [filectx(self._repo, p, fileid=n, filelog=l)
1120 for p, n, l in pl if n != nullid]
1135 for p, n, l in pl if n != nullid]
1121
1136
1122 def children(self):
1137 def children(self):
1123 return []
1138 return []
1124
1139
1125 def size(self):
1140 def size(self):
1126 return os.lstat(self._repo.wjoin(self._path)).st_size
1141 return os.lstat(self._repo.wjoin(self._path)).st_size
1127 def date(self):
1142 def date(self):
1128 t, tz = self._changectx.date()
1143 t, tz = self._changectx.date()
1129 try:
1144 try:
1130 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
1145 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
1131 except OSError, err:
1146 except OSError, err:
1132 if err.errno != errno.ENOENT:
1147 if err.errno != errno.ENOENT:
1133 raise
1148 raise
1134 return (t, tz)
1149 return (t, tz)
1135
1150
1136 def cmp(self, fctx):
1151 def cmp(self, fctx):
1137 """compare with other file context
1152 """compare with other file context
1138
1153
1139 returns True if different than fctx.
1154 returns True if different than fctx.
1140 """
1155 """
1141 # fctx should be a filectx (not a wfctx)
1156 # fctx should be a filectx (not a wfctx)
1142 # invert comparison to reuse the same code path
1157 # invert comparison to reuse the same code path
1143 return fctx.cmp(self)
1158 return fctx.cmp(self)
1144
1159
1145 class memctx(object):
1160 class memctx(object):
1146 """Use memctx to perform in-memory commits via localrepo.commitctx().
1161 """Use memctx to perform in-memory commits via localrepo.commitctx().
1147
1162
1148 Revision information is supplied at initialization time while
1163 Revision information is supplied at initialization time while
1149 related files data and is made available through a callback
1164 related files data and is made available through a callback
1150 mechanism. 'repo' is the current localrepo, 'parents' is a
1165 mechanism. 'repo' is the current localrepo, 'parents' is a
1151 sequence of two parent revisions identifiers (pass None for every
1166 sequence of two parent revisions identifiers (pass None for every
1152 missing parent), 'text' is the commit message and 'files' lists
1167 missing parent), 'text' is the commit message and 'files' lists
1153 names of files touched by the revision (normalized and relative to
1168 names of files touched by the revision (normalized and relative to
1154 repository root).
1169 repository root).
1155
1170
1156 filectxfn(repo, memctx, path) is a callable receiving the
1171 filectxfn(repo, memctx, path) is a callable receiving the
1157 repository, the current memctx object and the normalized path of
1172 repository, the current memctx object and the normalized path of
1158 requested file, relative to repository root. It is fired by the
1173 requested file, relative to repository root. It is fired by the
1159 commit function for every file in 'files', but calls order is
1174 commit function for every file in 'files', but calls order is
1160 undefined. If the file is available in the revision being
1175 undefined. If the file is available in the revision being
1161 committed (updated or added), filectxfn returns a memfilectx
1176 committed (updated or added), filectxfn returns a memfilectx
1162 object. If the file was removed, filectxfn raises an
1177 object. If the file was removed, filectxfn raises an
1163 IOError. Moved files are represented by marking the source file
1178 IOError. Moved files are represented by marking the source file
1164 removed and the new file added with copy information (see
1179 removed and the new file added with copy information (see
1165 memfilectx).
1180 memfilectx).
1166
1181
1167 user receives the committer name and defaults to current
1182 user receives the committer name and defaults to current
1168 repository username, date is the commit date in any format
1183 repository username, date is the commit date in any format
1169 supported by util.parsedate() and defaults to current date, extra
1184 supported by util.parsedate() and defaults to current date, extra
1170 is a dictionary of metadata or is left empty.
1185 is a dictionary of metadata or is left empty.
1171 """
1186 """
1172 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1187 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1173 date=None, extra=None):
1188 date=None, extra=None):
1174 self._repo = repo
1189 self._repo = repo
1175 self._rev = None
1190 self._rev = None
1176 self._node = None
1191 self._node = None
1177 self._text = text
1192 self._text = text
1178 self._date = date and util.parsedate(date) or util.makedate()
1193 self._date = date and util.parsedate(date) or util.makedate()
1179 self._user = user
1194 self._user = user
1180 parents = [(p or nullid) for p in parents]
1195 parents = [(p or nullid) for p in parents]
1181 p1, p2 = parents
1196 p1, p2 = parents
1182 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1197 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1183 files = sorted(set(files))
1198 files = sorted(set(files))
1184 self._status = [files, [], [], [], []]
1199 self._status = [files, [], [], [], []]
1185 self._filectxfn = filectxfn
1200 self._filectxfn = filectxfn
1186
1201
1187 self._extra = extra and extra.copy() or {}
1202 self._extra = extra and extra.copy() or {}
1188 if self._extra.get('branch', '') == '':
1203 if self._extra.get('branch', '') == '':
1189 self._extra['branch'] = 'default'
1204 self._extra['branch'] = 'default'
1190
1205
1191 def __str__(self):
1206 def __str__(self):
1192 return str(self._parents[0]) + "+"
1207 return str(self._parents[0]) + "+"
1193
1208
1194 def __int__(self):
1209 def __int__(self):
1195 return self._rev
1210 return self._rev
1196
1211
1197 def __nonzero__(self):
1212 def __nonzero__(self):
1198 return True
1213 return True
1199
1214
1200 def __getitem__(self, key):
1215 def __getitem__(self, key):
1201 return self.filectx(key)
1216 return self.filectx(key)
1202
1217
1203 def p1(self):
1218 def p1(self):
1204 return self._parents[0]
1219 return self._parents[0]
1205 def p2(self):
1220 def p2(self):
1206 return self._parents[1]
1221 return self._parents[1]
1207
1222
1208 def user(self):
1223 def user(self):
1209 return self._user or self._repo.ui.username()
1224 return self._user or self._repo.ui.username()
1210 def date(self):
1225 def date(self):
1211 return self._date
1226 return self._date
1212 def description(self):
1227 def description(self):
1213 return self._text
1228 return self._text
1214 def files(self):
1229 def files(self):
1215 return self.modified()
1230 return self.modified()
1216 def modified(self):
1231 def modified(self):
1217 return self._status[0]
1232 return self._status[0]
1218 def added(self):
1233 def added(self):
1219 return self._status[1]
1234 return self._status[1]
1220 def removed(self):
1235 def removed(self):
1221 return self._status[2]
1236 return self._status[2]
1222 def deleted(self):
1237 def deleted(self):
1223 return self._status[3]
1238 return self._status[3]
1224 def unknown(self):
1239 def unknown(self):
1225 return self._status[4]
1240 return self._status[4]
1226 def ignored(self):
1241 def ignored(self):
1227 return self._status[5]
1242 return self._status[5]
1228 def clean(self):
1243 def clean(self):
1229 return self._status[6]
1244 return self._status[6]
1230 def branch(self):
1245 def branch(self):
1231 return encoding.tolocal(self._extra['branch'])
1246 return encoding.tolocal(self._extra['branch'])
1232 def extra(self):
1247 def extra(self):
1233 return self._extra
1248 return self._extra
1234 def flags(self, f):
1249 def flags(self, f):
1235 return self[f].flags()
1250 return self[f].flags()
1236
1251
1237 def parents(self):
1252 def parents(self):
1238 """return contexts for each parent changeset"""
1253 """return contexts for each parent changeset"""
1239 return self._parents
1254 return self._parents
1240
1255
1241 def filectx(self, path, filelog=None):
1256 def filectx(self, path, filelog=None):
1242 """get a file context from the working directory"""
1257 """get a file context from the working directory"""
1243 return self._filectxfn(self._repo, self, path)
1258 return self._filectxfn(self._repo, self, path)
1244
1259
1245 def commit(self):
1260 def commit(self):
1246 """commit context to the repo"""
1261 """commit context to the repo"""
1247 return self._repo.commitctx(self)
1262 return self._repo.commitctx(self)
1248
1263
1249 class memfilectx(object):
1264 class memfilectx(object):
1250 """memfilectx represents an in-memory file to commit.
1265 """memfilectx represents an in-memory file to commit.
1251
1266
1252 See memctx for more details.
1267 See memctx for more details.
1253 """
1268 """
1254 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1269 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1255 """
1270 """
1256 path is the normalized file path relative to repository root.
1271 path is the normalized file path relative to repository root.
1257 data is the file content as a string.
1272 data is the file content as a string.
1258 islink is True if the file is a symbolic link.
1273 islink is True if the file is a symbolic link.
1259 isexec is True if the file is executable.
1274 isexec is True if the file is executable.
1260 copied is the source file path if current file was copied in the
1275 copied is the source file path if current file was copied in the
1261 revision being committed, or None."""
1276 revision being committed, or None."""
1262 self._path = path
1277 self._path = path
1263 self._data = data
1278 self._data = data
1264 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1279 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1265 self._copied = None
1280 self._copied = None
1266 if copied:
1281 if copied:
1267 self._copied = (copied, nullid)
1282 self._copied = (copied, nullid)
1268
1283
1269 def __nonzero__(self):
1284 def __nonzero__(self):
1270 return True
1285 return True
1271 def __str__(self):
1286 def __str__(self):
1272 return "%s@%s" % (self.path(), self._changectx)
1287 return "%s@%s" % (self.path(), self._changectx)
1273 def path(self):
1288 def path(self):
1274 return self._path
1289 return self._path
1275 def data(self):
1290 def data(self):
1276 return self._data
1291 return self._data
1277 def flags(self):
1292 def flags(self):
1278 return self._flags
1293 return self._flags
1279 def isexec(self):
1294 def isexec(self):
1280 return 'x' in self._flags
1295 return 'x' in self._flags
1281 def islink(self):
1296 def islink(self):
1282 return 'l' in self._flags
1297 return 'l' in self._flags
1283 def renamed(self):
1298 def renamed(self):
1284 return self._copied
1299 return self._copied
@@ -1,1736 +1,1745 b''
1 # revset.py - revision set queries for mercurial
1 # revset.py - revision set queries for mercurial
2 #
2 #
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2010 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 import re
8 import re
9 import parser, util, error, discovery, hbisect, phases
9 import parser, util, error, discovery, hbisect, phases
10 import node
10 import node
11 import bookmarks as bookmarksmod
11 import bookmarks as bookmarksmod
12 import match as matchmod
12 import match as matchmod
13 from i18n import _
13 from i18n import _
14 import encoding
14 import encoding
15
15
16 def _revancestors(repo, revs, followfirst):
16 def _revancestors(repo, revs, followfirst):
17 """Like revlog.ancestors(), but supports followfirst."""
17 """Like revlog.ancestors(), but supports followfirst."""
18 cut = followfirst and 1 or None
18 cut = followfirst and 1 or None
19 cl = repo.changelog
19 cl = repo.changelog
20 visit = util.deque(revs)
20 visit = util.deque(revs)
21 seen = set([node.nullrev])
21 seen = set([node.nullrev])
22 while visit:
22 while visit:
23 for parent in cl.parentrevs(visit.popleft())[:cut]:
23 for parent in cl.parentrevs(visit.popleft())[:cut]:
24 if parent not in seen:
24 if parent not in seen:
25 visit.append(parent)
25 visit.append(parent)
26 seen.add(parent)
26 seen.add(parent)
27 yield parent
27 yield parent
28
28
29 def _revdescendants(repo, revs, followfirst):
29 def _revdescendants(repo, revs, followfirst):
30 """Like revlog.descendants() but supports followfirst."""
30 """Like revlog.descendants() but supports followfirst."""
31 cut = followfirst and 1 or None
31 cut = followfirst and 1 or None
32 cl = repo.changelog
32 cl = repo.changelog
33 first = min(revs)
33 first = min(revs)
34 nullrev = node.nullrev
34 nullrev = node.nullrev
35 if first == nullrev:
35 if first == nullrev:
36 # Are there nodes with a null first parent and a non-null
36 # Are there nodes with a null first parent and a non-null
37 # second one? Maybe. Do we care? Probably not.
37 # second one? Maybe. Do we care? Probably not.
38 for i in cl:
38 for i in cl:
39 yield i
39 yield i
40 return
40 return
41
41
42 seen = set(revs)
42 seen = set(revs)
43 for i in xrange(first + 1, len(cl)):
43 for i in xrange(first + 1, len(cl)):
44 for x in cl.parentrevs(i)[:cut]:
44 for x in cl.parentrevs(i)[:cut]:
45 if x != nullrev and x in seen:
45 if x != nullrev and x in seen:
46 seen.add(i)
46 seen.add(i)
47 yield i
47 yield i
48 break
48 break
49
49
50 def _revsbetween(repo, roots, heads):
50 def _revsbetween(repo, roots, heads):
51 """Return all paths between roots and heads, inclusive of both endpoint
51 """Return all paths between roots and heads, inclusive of both endpoint
52 sets."""
52 sets."""
53 if not roots:
53 if not roots:
54 return []
54 return []
55 parentrevs = repo.changelog.parentrevs
55 parentrevs = repo.changelog.parentrevs
56 visit = heads[:]
56 visit = heads[:]
57 reachable = set()
57 reachable = set()
58 seen = {}
58 seen = {}
59 minroot = min(roots)
59 minroot = min(roots)
60 roots = set(roots)
60 roots = set(roots)
61 # open-code the post-order traversal due to the tiny size of
61 # open-code the post-order traversal due to the tiny size of
62 # sys.getrecursionlimit()
62 # sys.getrecursionlimit()
63 while visit:
63 while visit:
64 rev = visit.pop()
64 rev = visit.pop()
65 if rev in roots:
65 if rev in roots:
66 reachable.add(rev)
66 reachable.add(rev)
67 parents = parentrevs(rev)
67 parents = parentrevs(rev)
68 seen[rev] = parents
68 seen[rev] = parents
69 for parent in parents:
69 for parent in parents:
70 if parent >= minroot and parent not in seen:
70 if parent >= minroot and parent not in seen:
71 visit.append(parent)
71 visit.append(parent)
72 if not reachable:
72 if not reachable:
73 return []
73 return []
74 for rev in sorted(seen):
74 for rev in sorted(seen):
75 for parent in seen[rev]:
75 for parent in seen[rev]:
76 if parent in reachable:
76 if parent in reachable:
77 reachable.add(rev)
77 reachable.add(rev)
78 return sorted(reachable)
78 return sorted(reachable)
79
79
80 elements = {
80 elements = {
81 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
81 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
82 "~": (18, None, ("ancestor", 18)),
82 "~": (18, None, ("ancestor", 18)),
83 "^": (18, None, ("parent", 18), ("parentpost", 18)),
83 "^": (18, None, ("parent", 18), ("parentpost", 18)),
84 "-": (5, ("negate", 19), ("minus", 5)),
84 "-": (5, ("negate", 19), ("minus", 5)),
85 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
85 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
86 ("dagrangepost", 17)),
86 ("dagrangepost", 17)),
87 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
87 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
88 ("dagrangepost", 17)),
88 ("dagrangepost", 17)),
89 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
89 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
90 "not": (10, ("not", 10)),
90 "not": (10, ("not", 10)),
91 "!": (10, ("not", 10)),
91 "!": (10, ("not", 10)),
92 "and": (5, None, ("and", 5)),
92 "and": (5, None, ("and", 5)),
93 "&": (5, None, ("and", 5)),
93 "&": (5, None, ("and", 5)),
94 "or": (4, None, ("or", 4)),
94 "or": (4, None, ("or", 4)),
95 "|": (4, None, ("or", 4)),
95 "|": (4, None, ("or", 4)),
96 "+": (4, None, ("or", 4)),
96 "+": (4, None, ("or", 4)),
97 ",": (2, None, ("list", 2)),
97 ",": (2, None, ("list", 2)),
98 ")": (0, None, None),
98 ")": (0, None, None),
99 "symbol": (0, ("symbol",), None),
99 "symbol": (0, ("symbol",), None),
100 "string": (0, ("string",), None),
100 "string": (0, ("string",), None),
101 "end": (0, None, None),
101 "end": (0, None, None),
102 }
102 }
103
103
104 keywords = set(['and', 'or', 'not'])
104 keywords = set(['and', 'or', 'not'])
105
105
106 def tokenize(program):
106 def tokenize(program):
107 pos, l = 0, len(program)
107 pos, l = 0, len(program)
108 while pos < l:
108 while pos < l:
109 c = program[pos]
109 c = program[pos]
110 if c.isspace(): # skip inter-token whitespace
110 if c.isspace(): # skip inter-token whitespace
111 pass
111 pass
112 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
112 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
113 yield ('::', None, pos)
113 yield ('::', None, pos)
114 pos += 1 # skip ahead
114 pos += 1 # skip ahead
115 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
115 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
116 yield ('..', None, pos)
116 yield ('..', None, pos)
117 pos += 1 # skip ahead
117 pos += 1 # skip ahead
118 elif c in "():,-|&+!~^": # handle simple operators
118 elif c in "():,-|&+!~^": # handle simple operators
119 yield (c, None, pos)
119 yield (c, None, pos)
120 elif (c in '"\'' or c == 'r' and
120 elif (c in '"\'' or c == 'r' and
121 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
121 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
122 if c == 'r':
122 if c == 'r':
123 pos += 1
123 pos += 1
124 c = program[pos]
124 c = program[pos]
125 decode = lambda x: x
125 decode = lambda x: x
126 else:
126 else:
127 decode = lambda x: x.decode('string-escape')
127 decode = lambda x: x.decode('string-escape')
128 pos += 1
128 pos += 1
129 s = pos
129 s = pos
130 while pos < l: # find closing quote
130 while pos < l: # find closing quote
131 d = program[pos]
131 d = program[pos]
132 if d == '\\': # skip over escaped characters
132 if d == '\\': # skip over escaped characters
133 pos += 2
133 pos += 2
134 continue
134 continue
135 if d == c:
135 if d == c:
136 yield ('string', decode(program[s:pos]), s)
136 yield ('string', decode(program[s:pos]), s)
137 break
137 break
138 pos += 1
138 pos += 1
139 else:
139 else:
140 raise error.ParseError(_("unterminated string"), s)
140 raise error.ParseError(_("unterminated string"), s)
141 # gather up a symbol/keyword
141 # gather up a symbol/keyword
142 elif c.isalnum() or c in '._' or ord(c) > 127:
142 elif c.isalnum() or c in '._' or ord(c) > 127:
143 s = pos
143 s = pos
144 pos += 1
144 pos += 1
145 while pos < l: # find end of symbol
145 while pos < l: # find end of symbol
146 d = program[pos]
146 d = program[pos]
147 if not (d.isalnum() or d in "._/" or ord(d) > 127):
147 if not (d.isalnum() or d in "._/" or ord(d) > 127):
148 break
148 break
149 if d == '.' and program[pos - 1] == '.': # special case for ..
149 if d == '.' and program[pos - 1] == '.': # special case for ..
150 pos -= 1
150 pos -= 1
151 break
151 break
152 pos += 1
152 pos += 1
153 sym = program[s:pos]
153 sym = program[s:pos]
154 if sym in keywords: # operator keywords
154 if sym in keywords: # operator keywords
155 yield (sym, None, s)
155 yield (sym, None, s)
156 else:
156 else:
157 yield ('symbol', sym, s)
157 yield ('symbol', sym, s)
158 pos -= 1
158 pos -= 1
159 else:
159 else:
160 raise error.ParseError(_("syntax error"), pos)
160 raise error.ParseError(_("syntax error"), pos)
161 pos += 1
161 pos += 1
162 yield ('end', None, pos)
162 yield ('end', None, pos)
163
163
164 # helpers
164 # helpers
165
165
166 def getstring(x, err):
166 def getstring(x, err):
167 if x and (x[0] == 'string' or x[0] == 'symbol'):
167 if x and (x[0] == 'string' or x[0] == 'symbol'):
168 return x[1]
168 return x[1]
169 raise error.ParseError(err)
169 raise error.ParseError(err)
170
170
171 def getlist(x):
171 def getlist(x):
172 if not x:
172 if not x:
173 return []
173 return []
174 if x[0] == 'list':
174 if x[0] == 'list':
175 return getlist(x[1]) + [x[2]]
175 return getlist(x[1]) + [x[2]]
176 return [x]
176 return [x]
177
177
178 def getargs(x, min, max, err):
178 def getargs(x, min, max, err):
179 l = getlist(x)
179 l = getlist(x)
180 if len(l) < min or (max >= 0 and len(l) > max):
180 if len(l) < min or (max >= 0 and len(l) > max):
181 raise error.ParseError(err)
181 raise error.ParseError(err)
182 return l
182 return l
183
183
184 def getset(repo, subset, x):
184 def getset(repo, subset, x):
185 if not x:
185 if not x:
186 raise error.ParseError(_("missing argument"))
186 raise error.ParseError(_("missing argument"))
187 return methods[x[0]](repo, subset, *x[1:])
187 return methods[x[0]](repo, subset, *x[1:])
188
188
189 def _getrevsource(repo, r):
189 def _getrevsource(repo, r):
190 extra = repo[r].extra()
190 extra = repo[r].extra()
191 for label in ('source', 'transplant_source', 'rebase_source'):
191 for label in ('source', 'transplant_source', 'rebase_source'):
192 if label in extra:
192 if label in extra:
193 try:
193 try:
194 return repo[extra[label]].rev()
194 return repo[extra[label]].rev()
195 except error.RepoLookupError:
195 except error.RepoLookupError:
196 pass
196 pass
197 return None
197 return None
198
198
199 # operator methods
199 # operator methods
200
200
201 def stringset(repo, subset, x):
201 def stringset(repo, subset, x):
202 x = repo[x].rev()
202 x = repo[x].rev()
203 if x == -1 and len(subset) == len(repo):
203 if x == -1 and len(subset) == len(repo):
204 return [-1]
204 return [-1]
205 if len(subset) == len(repo) or x in subset:
205 if len(subset) == len(repo) or x in subset:
206 return [x]
206 return [x]
207 return []
207 return []
208
208
209 def symbolset(repo, subset, x):
209 def symbolset(repo, subset, x):
210 if x in symbols:
210 if x in symbols:
211 raise error.ParseError(_("can't use %s here") % x)
211 raise error.ParseError(_("can't use %s here") % x)
212 return stringset(repo, subset, x)
212 return stringset(repo, subset, x)
213
213
214 def rangeset(repo, subset, x, y):
214 def rangeset(repo, subset, x, y):
215 m = getset(repo, subset, x)
215 m = getset(repo, subset, x)
216 if not m:
216 if not m:
217 m = getset(repo, range(len(repo)), x)
217 m = getset(repo, range(len(repo)), x)
218
218
219 n = getset(repo, subset, y)
219 n = getset(repo, subset, y)
220 if not n:
220 if not n:
221 n = getset(repo, range(len(repo)), y)
221 n = getset(repo, range(len(repo)), y)
222
222
223 if not m or not n:
223 if not m or not n:
224 return []
224 return []
225 m, n = m[0], n[-1]
225 m, n = m[0], n[-1]
226
226
227 if m < n:
227 if m < n:
228 r = range(m, n + 1)
228 r = range(m, n + 1)
229 else:
229 else:
230 r = range(m, n - 1, -1)
230 r = range(m, n - 1, -1)
231 s = set(subset)
231 s = set(subset)
232 return [x for x in r if x in s]
232 return [x for x in r if x in s]
233
233
234 def dagrange(repo, subset, x, y):
234 def dagrange(repo, subset, x, y):
235 if subset:
235 if subset:
236 r = range(len(repo))
236 r = range(len(repo))
237 xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
237 xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
238 s = set(subset)
238 s = set(subset)
239 return [r for r in xs if r in s]
239 return [r for r in xs if r in s]
240 return []
240 return []
241
241
242 def andset(repo, subset, x, y):
242 def andset(repo, subset, x, y):
243 return getset(repo, getset(repo, subset, x), y)
243 return getset(repo, getset(repo, subset, x), y)
244
244
245 def orset(repo, subset, x, y):
245 def orset(repo, subset, x, y):
246 xl = getset(repo, subset, x)
246 xl = getset(repo, subset, x)
247 s = set(xl)
247 s = set(xl)
248 yl = getset(repo, [r for r in subset if r not in s], y)
248 yl = getset(repo, [r for r in subset if r not in s], y)
249 return xl + yl
249 return xl + yl
250
250
251 def notset(repo, subset, x):
251 def notset(repo, subset, x):
252 s = set(getset(repo, subset, x))
252 s = set(getset(repo, subset, x))
253 return [r for r in subset if r not in s]
253 return [r for r in subset if r not in s]
254
254
255 def listset(repo, subset, a, b):
255 def listset(repo, subset, a, b):
256 raise error.ParseError(_("can't use a list in this context"))
256 raise error.ParseError(_("can't use a list in this context"))
257
257
258 def func(repo, subset, a, b):
258 def func(repo, subset, a, b):
259 if a[0] == 'symbol' and a[1] in symbols:
259 if a[0] == 'symbol' and a[1] in symbols:
260 return symbols[a[1]](repo, subset, b)
260 return symbols[a[1]](repo, subset, b)
261 raise error.ParseError(_("not a function: %s") % a[1])
261 raise error.ParseError(_("not a function: %s") % a[1])
262
262
263 # functions
263 # functions
264
264
265 def adds(repo, subset, x):
265 def adds(repo, subset, x):
266 """``adds(pattern)``
266 """``adds(pattern)``
267 Changesets that add a file matching pattern.
267 Changesets that add a file matching pattern.
268 """
268 """
269 # i18n: "adds" is a keyword
269 # i18n: "adds" is a keyword
270 pat = getstring(x, _("adds requires a pattern"))
270 pat = getstring(x, _("adds requires a pattern"))
271 return checkstatus(repo, subset, pat, 1)
271 return checkstatus(repo, subset, pat, 1)
272
272
273 def ancestor(repo, subset, x):
273 def ancestor(repo, subset, x):
274 """``ancestor(single, single)``
274 """``ancestor(single, single)``
275 Greatest common ancestor of the two changesets.
275 Greatest common ancestor of the two changesets.
276 """
276 """
277 # i18n: "ancestor" is a keyword
277 # i18n: "ancestor" is a keyword
278 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
278 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
279 r = range(len(repo))
279 r = range(len(repo))
280 a = getset(repo, r, l[0])
280 a = getset(repo, r, l[0])
281 b = getset(repo, r, l[1])
281 b = getset(repo, r, l[1])
282 if len(a) != 1 or len(b) != 1:
282 if len(a) != 1 or len(b) != 1:
283 # i18n: "ancestor" is a keyword
283 # i18n: "ancestor" is a keyword
284 raise error.ParseError(_("ancestor arguments must be single revisions"))
284 raise error.ParseError(_("ancestor arguments must be single revisions"))
285 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
285 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
286
286
287 return [r for r in an if r in subset]
287 return [r for r in an if r in subset]
288
288
289 def _ancestors(repo, subset, x, followfirst=False):
289 def _ancestors(repo, subset, x, followfirst=False):
290 args = getset(repo, range(len(repo)), x)
290 args = getset(repo, range(len(repo)), x)
291 if not args:
291 if not args:
292 return []
292 return []
293 s = set(_revancestors(repo, args, followfirst)) | set(args)
293 s = set(_revancestors(repo, args, followfirst)) | set(args)
294 return [r for r in subset if r in s]
294 return [r for r in subset if r in s]
295
295
296 def ancestors(repo, subset, x):
296 def ancestors(repo, subset, x):
297 """``ancestors(set)``
297 """``ancestors(set)``
298 Changesets that are ancestors of a changeset in set.
298 Changesets that are ancestors of a changeset in set.
299 """
299 """
300 return _ancestors(repo, subset, x)
300 return _ancestors(repo, subset, x)
301
301
302 def _firstancestors(repo, subset, x):
302 def _firstancestors(repo, subset, x):
303 # ``_firstancestors(set)``
303 # ``_firstancestors(set)``
304 # Like ``ancestors(set)`` but follows only the first parents.
304 # Like ``ancestors(set)`` but follows only the first parents.
305 return _ancestors(repo, subset, x, followfirst=True)
305 return _ancestors(repo, subset, x, followfirst=True)
306
306
307 def ancestorspec(repo, subset, x, n):
307 def ancestorspec(repo, subset, x, n):
308 """``set~n``
308 """``set~n``
309 Changesets that are the Nth ancestor (first parents only) of a changeset
309 Changesets that are the Nth ancestor (first parents only) of a changeset
310 in set.
310 in set.
311 """
311 """
312 try:
312 try:
313 n = int(n[1])
313 n = int(n[1])
314 except (TypeError, ValueError):
314 except (TypeError, ValueError):
315 raise error.ParseError(_("~ expects a number"))
315 raise error.ParseError(_("~ expects a number"))
316 ps = set()
316 ps = set()
317 cl = repo.changelog
317 cl = repo.changelog
318 for r in getset(repo, subset, x):
318 for r in getset(repo, subset, x):
319 for i in range(n):
319 for i in range(n):
320 r = cl.parentrevs(r)[0]
320 r = cl.parentrevs(r)[0]
321 ps.add(r)
321 ps.add(r)
322 return [r for r in subset if r in ps]
322 return [r for r in subset if r in ps]
323
323
324 def author(repo, subset, x):
324 def author(repo, subset, x):
325 """``author(string)``
325 """``author(string)``
326 Alias for ``user(string)``.
326 Alias for ``user(string)``.
327 """
327 """
328 # i18n: "author" is a keyword
328 # i18n: "author" is a keyword
329 n = encoding.lower(getstring(x, _("author requires a string")))
329 n = encoding.lower(getstring(x, _("author requires a string")))
330 kind, pattern, matcher = _substringmatcher(n)
330 kind, pattern, matcher = _substringmatcher(n)
331 return [r for r in subset if matcher(encoding.lower(repo[r].user()))]
331 return [r for r in subset if matcher(encoding.lower(repo[r].user()))]
332
332
333 def bisect(repo, subset, x):
333 def bisect(repo, subset, x):
334 """``bisect(string)``
334 """``bisect(string)``
335 Changesets marked in the specified bisect status:
335 Changesets marked in the specified bisect status:
336
336
337 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
337 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
338 - ``goods``, ``bads`` : csets topologicaly good/bad
338 - ``goods``, ``bads`` : csets topologicaly good/bad
339 - ``range`` : csets taking part in the bisection
339 - ``range`` : csets taking part in the bisection
340 - ``pruned`` : csets that are goods, bads or skipped
340 - ``pruned`` : csets that are goods, bads or skipped
341 - ``untested`` : csets whose fate is yet unknown
341 - ``untested`` : csets whose fate is yet unknown
342 - ``ignored`` : csets ignored due to DAG topology
342 - ``ignored`` : csets ignored due to DAG topology
343 - ``current`` : the cset currently being bisected
343 - ``current`` : the cset currently being bisected
344 """
344 """
345 status = getstring(x, _("bisect requires a string")).lower()
345 status = getstring(x, _("bisect requires a string")).lower()
346 state = set(hbisect.get(repo, status))
346 state = set(hbisect.get(repo, status))
347 return [r for r in subset if r in state]
347 return [r for r in subset if r in state]
348
348
349 # Backward-compatibility
349 # Backward-compatibility
350 # - no help entry so that we do not advertise it any more
350 # - no help entry so that we do not advertise it any more
351 def bisected(repo, subset, x):
351 def bisected(repo, subset, x):
352 return bisect(repo, subset, x)
352 return bisect(repo, subset, x)
353
353
354 def bookmark(repo, subset, x):
354 def bookmark(repo, subset, x):
355 """``bookmark([name])``
355 """``bookmark([name])``
356 The named bookmark or all bookmarks.
356 The named bookmark or all bookmarks.
357
357
358 If `name` starts with `re:`, the remainder of the name is treated as
358 If `name` starts with `re:`, the remainder of the name is treated as
359 a regular expression. To match a bookmark that actually starts with `re:`,
359 a regular expression. To match a bookmark that actually starts with `re:`,
360 use the prefix `literal:`.
360 use the prefix `literal:`.
361 """
361 """
362 # i18n: "bookmark" is a keyword
362 # i18n: "bookmark" is a keyword
363 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
363 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
364 if args:
364 if args:
365 bm = getstring(args[0],
365 bm = getstring(args[0],
366 # i18n: "bookmark" is a keyword
366 # i18n: "bookmark" is a keyword
367 _('the argument to bookmark must be a string'))
367 _('the argument to bookmark must be a string'))
368 kind, pattern, matcher = _stringmatcher(bm)
368 kind, pattern, matcher = _stringmatcher(bm)
369 if kind == 'literal':
369 if kind == 'literal':
370 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
370 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
371 if not bmrev:
371 if not bmrev:
372 raise util.Abort(_("bookmark '%s' does not exist") % bm)
372 raise util.Abort(_("bookmark '%s' does not exist") % bm)
373 bmrev = repo[bmrev].rev()
373 bmrev = repo[bmrev].rev()
374 return [r for r in subset if r == bmrev]
374 return [r for r in subset if r == bmrev]
375 else:
375 else:
376 matchrevs = set()
376 matchrevs = set()
377 for name, bmrev in bookmarksmod.listbookmarks(repo).iteritems():
377 for name, bmrev in bookmarksmod.listbookmarks(repo).iteritems():
378 if matcher(name):
378 if matcher(name):
379 matchrevs.add(bmrev)
379 matchrevs.add(bmrev)
380 if not matchrevs:
380 if not matchrevs:
381 raise util.Abort(_("no bookmarks exist that match '%s'")
381 raise util.Abort(_("no bookmarks exist that match '%s'")
382 % pattern)
382 % pattern)
383 bmrevs = set()
383 bmrevs = set()
384 for bmrev in matchrevs:
384 for bmrev in matchrevs:
385 bmrevs.add(repo[bmrev].rev())
385 bmrevs.add(repo[bmrev].rev())
386 return [r for r in subset if r in bmrevs]
386 return [r for r in subset if r in bmrevs]
387
387
388 bms = set([repo[r].rev()
388 bms = set([repo[r].rev()
389 for r in bookmarksmod.listbookmarks(repo).values()])
389 for r in bookmarksmod.listbookmarks(repo).values()])
390 return [r for r in subset if r in bms]
390 return [r for r in subset if r in bms]
391
391
392 def branch(repo, subset, x):
392 def branch(repo, subset, x):
393 """``branch(string or set)``
393 """``branch(string or set)``
394 All changesets belonging to the given branch or the branches of the given
394 All changesets belonging to the given branch or the branches of the given
395 changesets.
395 changesets.
396
396
397 If `string` starts with `re:`, the remainder of the name is treated as
397 If `string` starts with `re:`, the remainder of the name is treated as
398 a regular expression. To match a branch that actually starts with `re:`,
398 a regular expression. To match a branch that actually starts with `re:`,
399 use the prefix `literal:`.
399 use the prefix `literal:`.
400 """
400 """
401 try:
401 try:
402 b = getstring(x, '')
402 b = getstring(x, '')
403 except error.ParseError:
403 except error.ParseError:
404 # not a string, but another revspec, e.g. tip()
404 # not a string, but another revspec, e.g. tip()
405 pass
405 pass
406 else:
406 else:
407 kind, pattern, matcher = _stringmatcher(b)
407 kind, pattern, matcher = _stringmatcher(b)
408 if kind == 'literal':
408 if kind == 'literal':
409 # note: falls through to the revspec case if no branch with
409 # note: falls through to the revspec case if no branch with
410 # this name exists
410 # this name exists
411 if pattern in repo.branchmap():
411 if pattern in repo.branchmap():
412 return [r for r in subset if matcher(repo[r].branch())]
412 return [r for r in subset if matcher(repo[r].branch())]
413 else:
413 else:
414 return [r for r in subset if matcher(repo[r].branch())]
414 return [r for r in subset if matcher(repo[r].branch())]
415
415
416 s = getset(repo, range(len(repo)), x)
416 s = getset(repo, range(len(repo)), x)
417 b = set()
417 b = set()
418 for r in s:
418 for r in s:
419 b.add(repo[r].branch())
419 b.add(repo[r].branch())
420 s = set(s)
420 s = set(s)
421 return [r for r in subset if r in s or repo[r].branch() in b]
421 return [r for r in subset if r in s or repo[r].branch() in b]
422
422
423 def checkstatus(repo, subset, pat, field):
423 def checkstatus(repo, subset, pat, field):
424 m = None
424 m = None
425 s = []
425 s = []
426 hasset = matchmod.patkind(pat) == 'set'
426 hasset = matchmod.patkind(pat) == 'set'
427 fname = None
427 fname = None
428 for r in subset:
428 for r in subset:
429 c = repo[r]
429 c = repo[r]
430 if not m or hasset:
430 if not m or hasset:
431 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
431 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
432 if not m.anypats() and len(m.files()) == 1:
432 if not m.anypats() and len(m.files()) == 1:
433 fname = m.files()[0]
433 fname = m.files()[0]
434 if fname is not None:
434 if fname is not None:
435 if fname not in c.files():
435 if fname not in c.files():
436 continue
436 continue
437 else:
437 else:
438 for f in c.files():
438 for f in c.files():
439 if m(f):
439 if m(f):
440 break
440 break
441 else:
441 else:
442 continue
442 continue
443 files = repo.status(c.p1().node(), c.node())[field]
443 files = repo.status(c.p1().node(), c.node())[field]
444 if fname is not None:
444 if fname is not None:
445 if fname in files:
445 if fname in files:
446 s.append(r)
446 s.append(r)
447 else:
447 else:
448 for f in files:
448 for f in files:
449 if m(f):
449 if m(f):
450 s.append(r)
450 s.append(r)
451 break
451 break
452 return s
452 return s
453
453
454 def _children(repo, narrow, parentset):
454 def _children(repo, narrow, parentset):
455 cs = set()
455 cs = set()
456 pr = repo.changelog.parentrevs
456 pr = repo.changelog.parentrevs
457 for r in narrow:
457 for r in narrow:
458 for p in pr(r):
458 for p in pr(r):
459 if p in parentset:
459 if p in parentset:
460 cs.add(r)
460 cs.add(r)
461 return cs
461 return cs
462
462
463 def children(repo, subset, x):
463 def children(repo, subset, x):
464 """``children(set)``
464 """``children(set)``
465 Child changesets of changesets in set.
465 Child changesets of changesets in set.
466 """
466 """
467 s = set(getset(repo, range(len(repo)), x))
467 s = set(getset(repo, range(len(repo)), x))
468 cs = _children(repo, subset, s)
468 cs = _children(repo, subset, s)
469 return [r for r in subset if r in cs]
469 return [r for r in subset if r in cs]
470
470
471 def closed(repo, subset, x):
471 def closed(repo, subset, x):
472 """``closed()``
472 """``closed()``
473 Changeset is closed.
473 Changeset is closed.
474 """
474 """
475 # i18n: "closed" is a keyword
475 # i18n: "closed" is a keyword
476 getargs(x, 0, 0, _("closed takes no arguments"))
476 getargs(x, 0, 0, _("closed takes no arguments"))
477 return [r for r in subset if repo[r].closesbranch()]
477 return [r for r in subset if repo[r].closesbranch()]
478
478
479 def contains(repo, subset, x):
479 def contains(repo, subset, x):
480 """``contains(pattern)``
480 """``contains(pattern)``
481 Revision contains a file matching pattern. See :hg:`help patterns`
481 Revision contains a file matching pattern. See :hg:`help patterns`
482 for information about file patterns.
482 for information about file patterns.
483 """
483 """
484 # i18n: "contains" is a keyword
484 # i18n: "contains" is a keyword
485 pat = getstring(x, _("contains requires a pattern"))
485 pat = getstring(x, _("contains requires a pattern"))
486 m = None
486 m = None
487 s = []
487 s = []
488 if not matchmod.patkind(pat):
488 if not matchmod.patkind(pat):
489 for r in subset:
489 for r in subset:
490 if pat in repo[r]:
490 if pat in repo[r]:
491 s.append(r)
491 s.append(r)
492 else:
492 else:
493 for r in subset:
493 for r in subset:
494 c = repo[r]
494 c = repo[r]
495 if not m or matchmod.patkind(pat) == 'set':
495 if not m or matchmod.patkind(pat) == 'set':
496 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
496 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
497 for f in c.manifest():
497 for f in c.manifest():
498 if m(f):
498 if m(f):
499 s.append(r)
499 s.append(r)
500 break
500 break
501 return s
501 return s
502
502
503 def converted(repo, subset, x):
503 def converted(repo, subset, x):
504 """``converted([id])``
504 """``converted([id])``
505 Changesets converted from the given identifier in the old repository if
505 Changesets converted from the given identifier in the old repository if
506 present, or all converted changesets if no identifier is specified.
506 present, or all converted changesets if no identifier is specified.
507 """
507 """
508
508
509 # There is exactly no chance of resolving the revision, so do a simple
509 # There is exactly no chance of resolving the revision, so do a simple
510 # string compare and hope for the best
510 # string compare and hope for the best
511
511
512 # i18n: "converted" is a keyword
512 # i18n: "converted" is a keyword
513 rev = None
513 rev = None
514 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
514 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
515 if l:
515 if l:
516 rev = getstring(l[0], _('converted requires a revision'))
516 rev = getstring(l[0], _('converted requires a revision'))
517
517
518 def _matchvalue(r):
518 def _matchvalue(r):
519 source = repo[r].extra().get('convert_revision', None)
519 source = repo[r].extra().get('convert_revision', None)
520 return source is not None and (rev is None or source.startswith(rev))
520 return source is not None and (rev is None or source.startswith(rev))
521
521
522 return [r for r in subset if _matchvalue(r)]
522 return [r for r in subset if _matchvalue(r)]
523
523
524 def date(repo, subset, x):
524 def date(repo, subset, x):
525 """``date(interval)``
525 """``date(interval)``
526 Changesets within the interval, see :hg:`help dates`.
526 Changesets within the interval, see :hg:`help dates`.
527 """
527 """
528 # i18n: "date" is a keyword
528 # i18n: "date" is a keyword
529 ds = getstring(x, _("date requires a string"))
529 ds = getstring(x, _("date requires a string"))
530 dm = util.matchdate(ds)
530 dm = util.matchdate(ds)
531 return [r for r in subset if dm(repo[r].date()[0])]
531 return [r for r in subset if dm(repo[r].date()[0])]
532
532
533 def desc(repo, subset, x):
533 def desc(repo, subset, x):
534 """``desc(string)``
534 """``desc(string)``
535 Search commit message for string. The match is case-insensitive.
535 Search commit message for string. The match is case-insensitive.
536 """
536 """
537 # i18n: "desc" is a keyword
537 # i18n: "desc" is a keyword
538 ds = encoding.lower(getstring(x, _("desc requires a string")))
538 ds = encoding.lower(getstring(x, _("desc requires a string")))
539 l = []
539 l = []
540 for r in subset:
540 for r in subset:
541 c = repo[r]
541 c = repo[r]
542 if ds in encoding.lower(c.description()):
542 if ds in encoding.lower(c.description()):
543 l.append(r)
543 l.append(r)
544 return l
544 return l
545
545
546 def _descendants(repo, subset, x, followfirst=False):
546 def _descendants(repo, subset, x, followfirst=False):
547 args = getset(repo, range(len(repo)), x)
547 args = getset(repo, range(len(repo)), x)
548 if not args:
548 if not args:
549 return []
549 return []
550 s = set(_revdescendants(repo, args, followfirst)) | set(args)
550 s = set(_revdescendants(repo, args, followfirst)) | set(args)
551 return [r for r in subset if r in s]
551 return [r for r in subset if r in s]
552
552
553 def descendants(repo, subset, x):
553 def descendants(repo, subset, x):
554 """``descendants(set)``
554 """``descendants(set)``
555 Changesets which are descendants of changesets in set.
555 Changesets which are descendants of changesets in set.
556 """
556 """
557 return _descendants(repo, subset, x)
557 return _descendants(repo, subset, x)
558
558
559 def _firstdescendants(repo, subset, x):
559 def _firstdescendants(repo, subset, x):
560 # ``_firstdescendants(set)``
560 # ``_firstdescendants(set)``
561 # Like ``descendants(set)`` but follows only the first parents.
561 # Like ``descendants(set)`` but follows only the first parents.
562 return _descendants(repo, subset, x, followfirst=True)
562 return _descendants(repo, subset, x, followfirst=True)
563
563
564 def draft(repo, subset, x):
564 def draft(repo, subset, x):
565 """``draft()``
565 """``draft()``
566 Changeset in draft phase."""
566 Changeset in draft phase."""
567 getargs(x, 0, 0, _("draft takes no arguments"))
567 getargs(x, 0, 0, _("draft takes no arguments"))
568 pc = repo._phasecache
568 pc = repo._phasecache
569 return [r for r in subset if pc.phase(repo, r) == phases.draft]
569 return [r for r in subset if pc.phase(repo, r) == phases.draft]
570
570
571 def extra(repo, subset, x):
571 def extra(repo, subset, x):
572 """``extra(label, [value])``
572 """``extra(label, [value])``
573 Changesets with the given label in the extra metadata, with the given
573 Changesets with the given label in the extra metadata, with the given
574 optional value.
574 optional value.
575
575
576 If `value` starts with `re:`, the remainder of the value is treated as
576 If `value` starts with `re:`, the remainder of the value is treated as
577 a regular expression. To match a value that actually starts with `re:`,
577 a regular expression. To match a value that actually starts with `re:`,
578 use the prefix `literal:`.
578 use the prefix `literal:`.
579 """
579 """
580
580
581 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
581 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
582 label = getstring(l[0], _('first argument to extra must be a string'))
582 label = getstring(l[0], _('first argument to extra must be a string'))
583 value = None
583 value = None
584
584
585 if len(l) > 1:
585 if len(l) > 1:
586 value = getstring(l[1], _('second argument to extra must be a string'))
586 value = getstring(l[1], _('second argument to extra must be a string'))
587 kind, value, matcher = _stringmatcher(value)
587 kind, value, matcher = _stringmatcher(value)
588
588
589 def _matchvalue(r):
589 def _matchvalue(r):
590 extra = repo[r].extra()
590 extra = repo[r].extra()
591 return label in extra and (value is None or matcher(extra[label]))
591 return label in extra and (value is None or matcher(extra[label]))
592
592
593 return [r for r in subset if _matchvalue(r)]
593 return [r for r in subset if _matchvalue(r)]
594
594
595 def filelog(repo, subset, x):
595 def filelog(repo, subset, x):
596 """``filelog(pattern)``
596 """``filelog(pattern)``
597 Changesets connected to the specified filelog.
597 Changesets connected to the specified filelog.
598 """
598 """
599
599
600 pat = getstring(x, _("filelog requires a pattern"))
600 pat = getstring(x, _("filelog requires a pattern"))
601 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
601 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
602 ctx=repo[None])
602 ctx=repo[None])
603 s = set()
603 s = set()
604
604
605 if not matchmod.patkind(pat):
605 if not matchmod.patkind(pat):
606 for f in m.files():
606 for f in m.files():
607 fl = repo.file(f)
607 fl = repo.file(f)
608 for fr in fl:
608 for fr in fl:
609 s.add(fl.linkrev(fr))
609 s.add(fl.linkrev(fr))
610 else:
610 else:
611 for f in repo[None]:
611 for f in repo[None]:
612 if m(f):
612 if m(f):
613 fl = repo.file(f)
613 fl = repo.file(f)
614 for fr in fl:
614 for fr in fl:
615 s.add(fl.linkrev(fr))
615 s.add(fl.linkrev(fr))
616
616
617 return [r for r in subset if r in s]
617 return [r for r in subset if r in s]
618
618
619 def first(repo, subset, x):
619 def first(repo, subset, x):
620 """``first(set, [n])``
620 """``first(set, [n])``
621 An alias for limit().
621 An alias for limit().
622 """
622 """
623 return limit(repo, subset, x)
623 return limit(repo, subset, x)
624
624
625 def _follow(repo, subset, x, name, followfirst=False):
625 def _follow(repo, subset, x, name, followfirst=False):
626 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
626 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
627 c = repo['.']
627 c = repo['.']
628 if l:
628 if l:
629 x = getstring(l[0], _("%s expected a filename") % name)
629 x = getstring(l[0], _("%s expected a filename") % name)
630 if x in c:
630 if x in c:
631 cx = c[x]
631 cx = c[x]
632 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
632 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
633 # include the revision responsible for the most recent version
633 # include the revision responsible for the most recent version
634 s.add(cx.linkrev())
634 s.add(cx.linkrev())
635 else:
635 else:
636 return []
636 return []
637 else:
637 else:
638 s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
638 s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
639
639
640 return [r for r in subset if r in s]
640 return [r for r in subset if r in s]
641
641
642 def follow(repo, subset, x):
642 def follow(repo, subset, x):
643 """``follow([file])``
643 """``follow([file])``
644 An alias for ``::.`` (ancestors of the working copy's first parent).
644 An alias for ``::.`` (ancestors of the working copy's first parent).
645 If a filename is specified, the history of the given file is followed,
645 If a filename is specified, the history of the given file is followed,
646 including copies.
646 including copies.
647 """
647 """
648 return _follow(repo, subset, x, 'follow')
648 return _follow(repo, subset, x, 'follow')
649
649
650 def _followfirst(repo, subset, x):
650 def _followfirst(repo, subset, x):
651 # ``followfirst([file])``
651 # ``followfirst([file])``
652 # Like ``follow([file])`` but follows only the first parent of
652 # Like ``follow([file])`` but follows only the first parent of
653 # every revision or file revision.
653 # every revision or file revision.
654 return _follow(repo, subset, x, '_followfirst', followfirst=True)
654 return _follow(repo, subset, x, '_followfirst', followfirst=True)
655
655
656 def getall(repo, subset, x):
656 def getall(repo, subset, x):
657 """``all()``
657 """``all()``
658 All changesets, the same as ``0:tip``.
658 All changesets, the same as ``0:tip``.
659 """
659 """
660 # i18n: "all" is a keyword
660 # i18n: "all" is a keyword
661 getargs(x, 0, 0, _("all takes no arguments"))
661 getargs(x, 0, 0, _("all takes no arguments"))
662 return subset
662 return subset
663
663
664 def grep(repo, subset, x):
664 def grep(repo, subset, x):
665 """``grep(regex)``
665 """``grep(regex)``
666 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
666 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
667 to ensure special escape characters are handled correctly. Unlike
667 to ensure special escape characters are handled correctly. Unlike
668 ``keyword(string)``, the match is case-sensitive.
668 ``keyword(string)``, the match is case-sensitive.
669 """
669 """
670 try:
670 try:
671 # i18n: "grep" is a keyword
671 # i18n: "grep" is a keyword
672 gr = re.compile(getstring(x, _("grep requires a string")))
672 gr = re.compile(getstring(x, _("grep requires a string")))
673 except re.error, e:
673 except re.error, e:
674 raise error.ParseError(_('invalid match pattern: %s') % e)
674 raise error.ParseError(_('invalid match pattern: %s') % e)
675 l = []
675 l = []
676 for r in subset:
676 for r in subset:
677 c = repo[r]
677 c = repo[r]
678 for e in c.files() + [c.user(), c.description()]:
678 for e in c.files() + [c.user(), c.description()]:
679 if gr.search(e):
679 if gr.search(e):
680 l.append(r)
680 l.append(r)
681 break
681 break
682 return l
682 return l
683
683
684 def _matchfiles(repo, subset, x):
684 def _matchfiles(repo, subset, x):
685 # _matchfiles takes a revset list of prefixed arguments:
685 # _matchfiles takes a revset list of prefixed arguments:
686 #
686 #
687 # [p:foo, i:bar, x:baz]
687 # [p:foo, i:bar, x:baz]
688 #
688 #
689 # builds a match object from them and filters subset. Allowed
689 # builds a match object from them and filters subset. Allowed
690 # prefixes are 'p:' for regular patterns, 'i:' for include
690 # prefixes are 'p:' for regular patterns, 'i:' for include
691 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
691 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
692 # a revision identifier, or the empty string to reference the
692 # a revision identifier, or the empty string to reference the
693 # working directory, from which the match object is
693 # working directory, from which the match object is
694 # initialized. Use 'd:' to set the default matching mode, default
694 # initialized. Use 'd:' to set the default matching mode, default
695 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
695 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
696
696
697 # i18n: "_matchfiles" is a keyword
697 # i18n: "_matchfiles" is a keyword
698 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
698 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
699 pats, inc, exc = [], [], []
699 pats, inc, exc = [], [], []
700 hasset = False
700 hasset = False
701 rev, default = None, None
701 rev, default = None, None
702 for arg in l:
702 for arg in l:
703 s = getstring(arg, _("_matchfiles requires string arguments"))
703 s = getstring(arg, _("_matchfiles requires string arguments"))
704 prefix, value = s[:2], s[2:]
704 prefix, value = s[:2], s[2:]
705 if prefix == 'p:':
705 if prefix == 'p:':
706 pats.append(value)
706 pats.append(value)
707 elif prefix == 'i:':
707 elif prefix == 'i:':
708 inc.append(value)
708 inc.append(value)
709 elif prefix == 'x:':
709 elif prefix == 'x:':
710 exc.append(value)
710 exc.append(value)
711 elif prefix == 'r:':
711 elif prefix == 'r:':
712 if rev is not None:
712 if rev is not None:
713 raise error.ParseError(_('_matchfiles expected at most one '
713 raise error.ParseError(_('_matchfiles expected at most one '
714 'revision'))
714 'revision'))
715 rev = value
715 rev = value
716 elif prefix == 'd:':
716 elif prefix == 'd:':
717 if default is not None:
717 if default is not None:
718 raise error.ParseError(_('_matchfiles expected at most one '
718 raise error.ParseError(_('_matchfiles expected at most one '
719 'default mode'))
719 'default mode'))
720 default = value
720 default = value
721 else:
721 else:
722 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
722 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
723 if not hasset and matchmod.patkind(value) == 'set':
723 if not hasset and matchmod.patkind(value) == 'set':
724 hasset = True
724 hasset = True
725 if not default:
725 if not default:
726 default = 'glob'
726 default = 'glob'
727 m = None
727 m = None
728 s = []
728 s = []
729 for r in subset:
729 for r in subset:
730 c = repo[r]
730 c = repo[r]
731 if not m or (hasset and rev is None):
731 if not m or (hasset and rev is None):
732 ctx = c
732 ctx = c
733 if rev is not None:
733 if rev is not None:
734 ctx = repo[rev or None]
734 ctx = repo[rev or None]
735 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
735 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
736 exclude=exc, ctx=ctx, default=default)
736 exclude=exc, ctx=ctx, default=default)
737 for f in c.files():
737 for f in c.files():
738 if m(f):
738 if m(f):
739 s.append(r)
739 s.append(r)
740 break
740 break
741 return s
741 return s
742
742
743 def hasfile(repo, subset, x):
743 def hasfile(repo, subset, x):
744 """``file(pattern)``
744 """``file(pattern)``
745 Changesets affecting files matched by pattern.
745 Changesets affecting files matched by pattern.
746 """
746 """
747 # i18n: "file" is a keyword
747 # i18n: "file" is a keyword
748 pat = getstring(x, _("file requires a pattern"))
748 pat = getstring(x, _("file requires a pattern"))
749 return _matchfiles(repo, subset, ('string', 'p:' + pat))
749 return _matchfiles(repo, subset, ('string', 'p:' + pat))
750
750
751 def head(repo, subset, x):
751 def head(repo, subset, x):
752 """``head()``
752 """``head()``
753 Changeset is a named branch head.
753 Changeset is a named branch head.
754 """
754 """
755 # i18n: "head" is a keyword
755 # i18n: "head" is a keyword
756 getargs(x, 0, 0, _("head takes no arguments"))
756 getargs(x, 0, 0, _("head takes no arguments"))
757 hs = set()
757 hs = set()
758 for b, ls in repo.branchmap().iteritems():
758 for b, ls in repo.branchmap().iteritems():
759 hs.update(repo[h].rev() for h in ls)
759 hs.update(repo[h].rev() for h in ls)
760 return [r for r in subset if r in hs]
760 return [r for r in subset if r in hs]
761
761
762 def heads(repo, subset, x):
762 def heads(repo, subset, x):
763 """``heads(set)``
763 """``heads(set)``
764 Members of set with no children in set.
764 Members of set with no children in set.
765 """
765 """
766 s = getset(repo, subset, x)
766 s = getset(repo, subset, x)
767 ps = set(parents(repo, subset, x))
767 ps = set(parents(repo, subset, x))
768 return [r for r in s if r not in ps]
768 return [r for r in s if r not in ps]
769
769
770 def keyword(repo, subset, x):
770 def keyword(repo, subset, x):
771 """``keyword(string)``
771 """``keyword(string)``
772 Search commit message, user name, and names of changed files for
772 Search commit message, user name, and names of changed files for
773 string. The match is case-insensitive.
773 string. The match is case-insensitive.
774 """
774 """
775 # i18n: "keyword" is a keyword
775 # i18n: "keyword" is a keyword
776 kw = encoding.lower(getstring(x, _("keyword requires a string")))
776 kw = encoding.lower(getstring(x, _("keyword requires a string")))
777 l = []
777 l = []
778 for r in subset:
778 for r in subset:
779 c = repo[r]
779 c = repo[r]
780 t = " ".join(c.files() + [c.user(), c.description()])
780 t = " ".join(c.files() + [c.user(), c.description()])
781 if kw in encoding.lower(t):
781 if kw in encoding.lower(t):
782 l.append(r)
782 l.append(r)
783 return l
783 return l
784
784
785 def limit(repo, subset, x):
785 def limit(repo, subset, x):
786 """``limit(set, [n])``
786 """``limit(set, [n])``
787 First n members of set, defaulting to 1.
787 First n members of set, defaulting to 1.
788 """
788 """
789 # i18n: "limit" is a keyword
789 # i18n: "limit" is a keyword
790 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
790 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
791 try:
791 try:
792 lim = 1
792 lim = 1
793 if len(l) == 2:
793 if len(l) == 2:
794 # i18n: "limit" is a keyword
794 # i18n: "limit" is a keyword
795 lim = int(getstring(l[1], _("limit requires a number")))
795 lim = int(getstring(l[1], _("limit requires a number")))
796 except (TypeError, ValueError):
796 except (TypeError, ValueError):
797 # i18n: "limit" is a keyword
797 # i18n: "limit" is a keyword
798 raise error.ParseError(_("limit expects a number"))
798 raise error.ParseError(_("limit expects a number"))
799 ss = set(subset)
799 ss = set(subset)
800 os = getset(repo, range(len(repo)), l[0])[:lim]
800 os = getset(repo, range(len(repo)), l[0])[:lim]
801 return [r for r in os if r in ss]
801 return [r for r in os if r in ss]
802
802
803 def last(repo, subset, x):
803 def last(repo, subset, x):
804 """``last(set, [n])``
804 """``last(set, [n])``
805 Last n members of set, defaulting to 1.
805 Last n members of set, defaulting to 1.
806 """
806 """
807 # i18n: "last" is a keyword
807 # i18n: "last" is a keyword
808 l = getargs(x, 1, 2, _("last requires one or two arguments"))
808 l = getargs(x, 1, 2, _("last requires one or two arguments"))
809 try:
809 try:
810 lim = 1
810 lim = 1
811 if len(l) == 2:
811 if len(l) == 2:
812 # i18n: "last" is a keyword
812 # i18n: "last" is a keyword
813 lim = int(getstring(l[1], _("last requires a number")))
813 lim = int(getstring(l[1], _("last requires a number")))
814 except (TypeError, ValueError):
814 except (TypeError, ValueError):
815 # i18n: "last" is a keyword
815 # i18n: "last" is a keyword
816 raise error.ParseError(_("last expects a number"))
816 raise error.ParseError(_("last expects a number"))
817 ss = set(subset)
817 ss = set(subset)
818 os = getset(repo, range(len(repo)), l[0])[-lim:]
818 os = getset(repo, range(len(repo)), l[0])[-lim:]
819 return [r for r in os if r in ss]
819 return [r for r in os if r in ss]
820
820
821 def maxrev(repo, subset, x):
821 def maxrev(repo, subset, x):
822 """``max(set)``
822 """``max(set)``
823 Changeset with highest revision number in set.
823 Changeset with highest revision number in set.
824 """
824 """
825 os = getset(repo, range(len(repo)), x)
825 os = getset(repo, range(len(repo)), x)
826 if os:
826 if os:
827 m = max(os)
827 m = max(os)
828 if m in subset:
828 if m in subset:
829 return [m]
829 return [m]
830 return []
830 return []
831
831
832 def merge(repo, subset, x):
832 def merge(repo, subset, x):
833 """``merge()``
833 """``merge()``
834 Changeset is a merge changeset.
834 Changeset is a merge changeset.
835 """
835 """
836 # i18n: "merge" is a keyword
836 # i18n: "merge" is a keyword
837 getargs(x, 0, 0, _("merge takes no arguments"))
837 getargs(x, 0, 0, _("merge takes no arguments"))
838 cl = repo.changelog
838 cl = repo.changelog
839 return [r for r in subset if cl.parentrevs(r)[1] != -1]
839 return [r for r in subset if cl.parentrevs(r)[1] != -1]
840
840
841 def minrev(repo, subset, x):
841 def minrev(repo, subset, x):
842 """``min(set)``
842 """``min(set)``
843 Changeset with lowest revision number in set.
843 Changeset with lowest revision number in set.
844 """
844 """
845 os = getset(repo, range(len(repo)), x)
845 os = getset(repo, range(len(repo)), x)
846 if os:
846 if os:
847 m = min(os)
847 m = min(os)
848 if m in subset:
848 if m in subset:
849 return [m]
849 return [m]
850 return []
850 return []
851
851
852 def modifies(repo, subset, x):
852 def modifies(repo, subset, x):
853 """``modifies(pattern)``
853 """``modifies(pattern)``
854 Changesets modifying files matched by pattern.
854 Changesets modifying files matched by pattern.
855 """
855 """
856 # i18n: "modifies" is a keyword
856 # i18n: "modifies" is a keyword
857 pat = getstring(x, _("modifies requires a pattern"))
857 pat = getstring(x, _("modifies requires a pattern"))
858 return checkstatus(repo, subset, pat, 0)
858 return checkstatus(repo, subset, pat, 0)
859
859
860 def node_(repo, subset, x):
860 def node_(repo, subset, x):
861 """``id(string)``
861 """``id(string)``
862 Revision non-ambiguously specified by the given hex string prefix.
862 Revision non-ambiguously specified by the given hex string prefix.
863 """
863 """
864 # i18n: "id" is a keyword
864 # i18n: "id" is a keyword
865 l = getargs(x, 1, 1, _("id requires one argument"))
865 l = getargs(x, 1, 1, _("id requires one argument"))
866 # i18n: "id" is a keyword
866 # i18n: "id" is a keyword
867 n = getstring(l[0], _("id requires a string"))
867 n = getstring(l[0], _("id requires a string"))
868 if len(n) == 40:
868 if len(n) == 40:
869 rn = repo[n].rev()
869 rn = repo[n].rev()
870 else:
870 else:
871 rn = None
871 rn = None
872 pm = repo.changelog._partialmatch(n)
872 pm = repo.changelog._partialmatch(n)
873 if pm is not None:
873 if pm is not None:
874 rn = repo.changelog.rev(pm)
874 rn = repo.changelog.rev(pm)
875
875
876 return [r for r in subset if r == rn]
876 return [r for r in subset if r == rn]
877
877
878 def obsolete(repo, subset, x):
878 def obsolete(repo, subset, x):
879 """``obsolete()``
879 """``obsolete()``
880 Mutable changeset with a newer version."""
880 Mutable changeset with a newer version."""
881 getargs(x, 0, 0, _("obsolete takes no arguments"))
881 getargs(x, 0, 0, _("obsolete takes no arguments"))
882 return [r for r in subset if repo[r].obsolete()]
882 return [r for r in subset if repo[r].obsolete()]
883
883
884 def outgoing(repo, subset, x):
884 def outgoing(repo, subset, x):
885 """``outgoing([path])``
885 """``outgoing([path])``
886 Changesets not found in the specified destination repository, or the
886 Changesets not found in the specified destination repository, or the
887 default push location.
887 default push location.
888 """
888 """
889 import hg # avoid start-up nasties
889 import hg # avoid start-up nasties
890 # i18n: "outgoing" is a keyword
890 # i18n: "outgoing" is a keyword
891 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
891 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
892 # i18n: "outgoing" is a keyword
892 # i18n: "outgoing" is a keyword
893 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
893 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
894 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
894 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
895 dest, branches = hg.parseurl(dest)
895 dest, branches = hg.parseurl(dest)
896 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
896 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
897 if revs:
897 if revs:
898 revs = [repo.lookup(rev) for rev in revs]
898 revs = [repo.lookup(rev) for rev in revs]
899 other = hg.peer(repo, {}, dest)
899 other = hg.peer(repo, {}, dest)
900 repo.ui.pushbuffer()
900 repo.ui.pushbuffer()
901 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
901 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
902 repo.ui.popbuffer()
902 repo.ui.popbuffer()
903 cl = repo.changelog
903 cl = repo.changelog
904 o = set([cl.rev(r) for r in outgoing.missing])
904 o = set([cl.rev(r) for r in outgoing.missing])
905 return [r for r in subset if r in o]
905 return [r for r in subset if r in o]
906
906
907 def p1(repo, subset, x):
907 def p1(repo, subset, x):
908 """``p1([set])``
908 """``p1([set])``
909 First parent of changesets in set, or the working directory.
909 First parent of changesets in set, or the working directory.
910 """
910 """
911 if x is None:
911 if x is None:
912 p = repo[x].p1().rev()
912 p = repo[x].p1().rev()
913 return [r for r in subset if r == p]
913 return [r for r in subset if r == p]
914
914
915 ps = set()
915 ps = set()
916 cl = repo.changelog
916 cl = repo.changelog
917 for r in getset(repo, range(len(repo)), x):
917 for r in getset(repo, range(len(repo)), x):
918 ps.add(cl.parentrevs(r)[0])
918 ps.add(cl.parentrevs(r)[0])
919 return [r for r in subset if r in ps]
919 return [r for r in subset if r in ps]
920
920
921 def p2(repo, subset, x):
921 def p2(repo, subset, x):
922 """``p2([set])``
922 """``p2([set])``
923 Second parent of changesets in set, or the working directory.
923 Second parent of changesets in set, or the working directory.
924 """
924 """
925 if x is None:
925 if x is None:
926 ps = repo[x].parents()
926 ps = repo[x].parents()
927 try:
927 try:
928 p = ps[1].rev()
928 p = ps[1].rev()
929 return [r for r in subset if r == p]
929 return [r for r in subset if r == p]
930 except IndexError:
930 except IndexError:
931 return []
931 return []
932
932
933 ps = set()
933 ps = set()
934 cl = repo.changelog
934 cl = repo.changelog
935 for r in getset(repo, range(len(repo)), x):
935 for r in getset(repo, range(len(repo)), x):
936 ps.add(cl.parentrevs(r)[1])
936 ps.add(cl.parentrevs(r)[1])
937 return [r for r in subset if r in ps]
937 return [r for r in subset if r in ps]
938
938
939 def parents(repo, subset, x):
939 def parents(repo, subset, x):
940 """``parents([set])``
940 """``parents([set])``
941 The set of all parents for all changesets in set, or the working directory.
941 The set of all parents for all changesets in set, or the working directory.
942 """
942 """
943 if x is None:
943 if x is None:
944 ps = tuple(p.rev() for p in repo[x].parents())
944 ps = tuple(p.rev() for p in repo[x].parents())
945 return [r for r in subset if r in ps]
945 return [r for r in subset if r in ps]
946
946
947 ps = set()
947 ps = set()
948 cl = repo.changelog
948 cl = repo.changelog
949 for r in getset(repo, range(len(repo)), x):
949 for r in getset(repo, range(len(repo)), x):
950 ps.update(cl.parentrevs(r))
950 ps.update(cl.parentrevs(r))
951 return [r for r in subset if r in ps]
951 return [r for r in subset if r in ps]
952
952
953 def parentspec(repo, subset, x, n):
953 def parentspec(repo, subset, x, n):
954 """``set^0``
954 """``set^0``
955 The set.
955 The set.
956 ``set^1`` (or ``set^``), ``set^2``
956 ``set^1`` (or ``set^``), ``set^2``
957 First or second parent, respectively, of all changesets in set.
957 First or second parent, respectively, of all changesets in set.
958 """
958 """
959 try:
959 try:
960 n = int(n[1])
960 n = int(n[1])
961 if n not in (0, 1, 2):
961 if n not in (0, 1, 2):
962 raise ValueError
962 raise ValueError
963 except (TypeError, ValueError):
963 except (TypeError, ValueError):
964 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
964 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
965 ps = set()
965 ps = set()
966 cl = repo.changelog
966 cl = repo.changelog
967 for r in getset(repo, subset, x):
967 for r in getset(repo, subset, x):
968 if n == 0:
968 if n == 0:
969 ps.add(r)
969 ps.add(r)
970 elif n == 1:
970 elif n == 1:
971 ps.add(cl.parentrevs(r)[0])
971 ps.add(cl.parentrevs(r)[0])
972 elif n == 2:
972 elif n == 2:
973 parents = cl.parentrevs(r)
973 parents = cl.parentrevs(r)
974 if len(parents) > 1:
974 if len(parents) > 1:
975 ps.add(parents[1])
975 ps.add(parents[1])
976 return [r for r in subset if r in ps]
976 return [r for r in subset if r in ps]
977
977
978 def present(repo, subset, x):
978 def present(repo, subset, x):
979 """``present(set)``
979 """``present(set)``
980 An empty set, if any revision in set isn't found; otherwise,
980 An empty set, if any revision in set isn't found; otherwise,
981 all revisions in set.
981 all revisions in set.
982
982
983 If any of specified revisions is not present in the local repository,
983 If any of specified revisions is not present in the local repository,
984 the query is normally aborted. But this predicate allows the query
984 the query is normally aborted. But this predicate allows the query
985 to continue even in such cases.
985 to continue even in such cases.
986 """
986 """
987 try:
987 try:
988 return getset(repo, subset, x)
988 return getset(repo, subset, x)
989 except error.RepoLookupError:
989 except error.RepoLookupError:
990 return []
990 return []
991
991
992 def public(repo, subset, x):
992 def public(repo, subset, x):
993 """``public()``
993 """``public()``
994 Changeset in public phase."""
994 Changeset in public phase."""
995 getargs(x, 0, 0, _("public takes no arguments"))
995 getargs(x, 0, 0, _("public takes no arguments"))
996 pc = repo._phasecache
996 pc = repo._phasecache
997 return [r for r in subset if pc.phase(repo, r) == phases.public]
997 return [r for r in subset if pc.phase(repo, r) == phases.public]
998
998
999 def remote(repo, subset, x):
999 def remote(repo, subset, x):
1000 """``remote([id [,path]])``
1000 """``remote([id [,path]])``
1001 Local revision that corresponds to the given identifier in a
1001 Local revision that corresponds to the given identifier in a
1002 remote repository, if present. Here, the '.' identifier is a
1002 remote repository, if present. Here, the '.' identifier is a
1003 synonym for the current local branch.
1003 synonym for the current local branch.
1004 """
1004 """
1005
1005
1006 import hg # avoid start-up nasties
1006 import hg # avoid start-up nasties
1007 # i18n: "remote" is a keyword
1007 # i18n: "remote" is a keyword
1008 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1008 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1009
1009
1010 q = '.'
1010 q = '.'
1011 if len(l) > 0:
1011 if len(l) > 0:
1012 # i18n: "remote" is a keyword
1012 # i18n: "remote" is a keyword
1013 q = getstring(l[0], _("remote requires a string id"))
1013 q = getstring(l[0], _("remote requires a string id"))
1014 if q == '.':
1014 if q == '.':
1015 q = repo['.'].branch()
1015 q = repo['.'].branch()
1016
1016
1017 dest = ''
1017 dest = ''
1018 if len(l) > 1:
1018 if len(l) > 1:
1019 # i18n: "remote" is a keyword
1019 # i18n: "remote" is a keyword
1020 dest = getstring(l[1], _("remote requires a repository path"))
1020 dest = getstring(l[1], _("remote requires a repository path"))
1021 dest = repo.ui.expandpath(dest or 'default')
1021 dest = repo.ui.expandpath(dest or 'default')
1022 dest, branches = hg.parseurl(dest)
1022 dest, branches = hg.parseurl(dest)
1023 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1023 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1024 if revs:
1024 if revs:
1025 revs = [repo.lookup(rev) for rev in revs]
1025 revs = [repo.lookup(rev) for rev in revs]
1026 other = hg.peer(repo, {}, dest)
1026 other = hg.peer(repo, {}, dest)
1027 n = other.lookup(q)
1027 n = other.lookup(q)
1028 if n in repo:
1028 if n in repo:
1029 r = repo[n].rev()
1029 r = repo[n].rev()
1030 if r in subset:
1030 if r in subset:
1031 return [r]
1031 return [r]
1032 return []
1032 return []
1033
1033
1034 def removes(repo, subset, x):
1034 def removes(repo, subset, x):
1035 """``removes(pattern)``
1035 """``removes(pattern)``
1036 Changesets which remove files matching pattern.
1036 Changesets which remove files matching pattern.
1037 """
1037 """
1038 # i18n: "removes" is a keyword
1038 # i18n: "removes" is a keyword
1039 pat = getstring(x, _("removes requires a pattern"))
1039 pat = getstring(x, _("removes requires a pattern"))
1040 return checkstatus(repo, subset, pat, 2)
1040 return checkstatus(repo, subset, pat, 2)
1041
1041
1042 def rev(repo, subset, x):
1042 def rev(repo, subset, x):
1043 """``rev(number)``
1043 """``rev(number)``
1044 Revision with the given numeric identifier.
1044 Revision with the given numeric identifier.
1045 """
1045 """
1046 # i18n: "rev" is a keyword
1046 # i18n: "rev" is a keyword
1047 l = getargs(x, 1, 1, _("rev requires one argument"))
1047 l = getargs(x, 1, 1, _("rev requires one argument"))
1048 try:
1048 try:
1049 # i18n: "rev" is a keyword
1049 # i18n: "rev" is a keyword
1050 l = int(getstring(l[0], _("rev requires a number")))
1050 l = int(getstring(l[0], _("rev requires a number")))
1051 except (TypeError, ValueError):
1051 except (TypeError, ValueError):
1052 # i18n: "rev" is a keyword
1052 # i18n: "rev" is a keyword
1053 raise error.ParseError(_("rev expects a number"))
1053 raise error.ParseError(_("rev expects a number"))
1054 return [r for r in subset if r == l]
1054 return [r for r in subset if r == l]
1055
1055
1056 def matching(repo, subset, x):
1056 def matching(repo, subset, x):
1057 """``matching(revision [, field])``
1057 """``matching(revision [, field])``
1058 Changesets in which a given set of fields match the set of fields in the
1058 Changesets in which a given set of fields match the set of fields in the
1059 selected revision or set.
1059 selected revision or set.
1060
1060
1061 To match more than one field pass the list of fields to match separated
1061 To match more than one field pass the list of fields to match separated
1062 by spaces (e.g. ``author description``).
1062 by spaces (e.g. ``author description``).
1063
1063
1064 Valid fields are most regular revision fields and some special fields.
1064 Valid fields are most regular revision fields and some special fields.
1065
1065
1066 Regular revision fields are ``description``, ``author``, ``branch``,
1066 Regular revision fields are ``description``, ``author``, ``branch``,
1067 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1067 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1068 and ``diff``.
1068 and ``diff``.
1069 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1069 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1070 contents of the revision. Two revisions matching their ``diff`` will
1070 contents of the revision. Two revisions matching their ``diff`` will
1071 also match their ``files``.
1071 also match their ``files``.
1072
1072
1073 Special fields are ``summary`` and ``metadata``:
1073 Special fields are ``summary`` and ``metadata``:
1074 ``summary`` matches the first line of the description.
1074 ``summary`` matches the first line of the description.
1075 ``metadata`` is equivalent to matching ``description user date``
1075 ``metadata`` is equivalent to matching ``description user date``
1076 (i.e. it matches the main metadata fields).
1076 (i.e. it matches the main metadata fields).
1077
1077
1078 ``metadata`` is the default field which is used when no fields are
1078 ``metadata`` is the default field which is used when no fields are
1079 specified. You can match more than one field at a time.
1079 specified. You can match more than one field at a time.
1080 """
1080 """
1081 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1081 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1082
1082
1083 revs = getset(repo, xrange(len(repo)), l[0])
1083 revs = getset(repo, xrange(len(repo)), l[0])
1084
1084
1085 fieldlist = ['metadata']
1085 fieldlist = ['metadata']
1086 if len(l) > 1:
1086 if len(l) > 1:
1087 fieldlist = getstring(l[1],
1087 fieldlist = getstring(l[1],
1088 _("matching requires a string "
1088 _("matching requires a string "
1089 "as its second argument")).split()
1089 "as its second argument")).split()
1090
1090
1091 # Make sure that there are no repeated fields,
1091 # Make sure that there are no repeated fields,
1092 # expand the 'special' 'metadata' field type
1092 # expand the 'special' 'metadata' field type
1093 # and check the 'files' whenever we check the 'diff'
1093 # and check the 'files' whenever we check the 'diff'
1094 fields = []
1094 fields = []
1095 for field in fieldlist:
1095 for field in fieldlist:
1096 if field == 'metadata':
1096 if field == 'metadata':
1097 fields += ['user', 'description', 'date']
1097 fields += ['user', 'description', 'date']
1098 elif field == 'diff':
1098 elif field == 'diff':
1099 # a revision matching the diff must also match the files
1099 # a revision matching the diff must also match the files
1100 # since matching the diff is very costly, make sure to
1100 # since matching the diff is very costly, make sure to
1101 # also match the files first
1101 # also match the files first
1102 fields += ['files', 'diff']
1102 fields += ['files', 'diff']
1103 else:
1103 else:
1104 if field == 'author':
1104 if field == 'author':
1105 field = 'user'
1105 field = 'user'
1106 fields.append(field)
1106 fields.append(field)
1107 fields = set(fields)
1107 fields = set(fields)
1108 if 'summary' in fields and 'description' in fields:
1108 if 'summary' in fields and 'description' in fields:
1109 # If a revision matches its description it also matches its summary
1109 # If a revision matches its description it also matches its summary
1110 fields.discard('summary')
1110 fields.discard('summary')
1111
1111
1112 # We may want to match more than one field
1112 # We may want to match more than one field
1113 # Not all fields take the same amount of time to be matched
1113 # Not all fields take the same amount of time to be matched
1114 # Sort the selected fields in order of increasing matching cost
1114 # Sort the selected fields in order of increasing matching cost
1115 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1115 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1116 'files', 'description', 'substate', 'diff']
1116 'files', 'description', 'substate', 'diff']
1117 def fieldkeyfunc(f):
1117 def fieldkeyfunc(f):
1118 try:
1118 try:
1119 return fieldorder.index(f)
1119 return fieldorder.index(f)
1120 except ValueError:
1120 except ValueError:
1121 # assume an unknown field is very costly
1121 # assume an unknown field is very costly
1122 return len(fieldorder)
1122 return len(fieldorder)
1123 fields = list(fields)
1123 fields = list(fields)
1124 fields.sort(key=fieldkeyfunc)
1124 fields.sort(key=fieldkeyfunc)
1125
1125
1126 # Each field will be matched with its own "getfield" function
1126 # Each field will be matched with its own "getfield" function
1127 # which will be added to the getfieldfuncs array of functions
1127 # which will be added to the getfieldfuncs array of functions
1128 getfieldfuncs = []
1128 getfieldfuncs = []
1129 _funcs = {
1129 _funcs = {
1130 'user': lambda r: repo[r].user(),
1130 'user': lambda r: repo[r].user(),
1131 'branch': lambda r: repo[r].branch(),
1131 'branch': lambda r: repo[r].branch(),
1132 'date': lambda r: repo[r].date(),
1132 'date': lambda r: repo[r].date(),
1133 'description': lambda r: repo[r].description(),
1133 'description': lambda r: repo[r].description(),
1134 'files': lambda r: repo[r].files(),
1134 'files': lambda r: repo[r].files(),
1135 'parents': lambda r: repo[r].parents(),
1135 'parents': lambda r: repo[r].parents(),
1136 'phase': lambda r: repo[r].phase(),
1136 'phase': lambda r: repo[r].phase(),
1137 'substate': lambda r: repo[r].substate,
1137 'substate': lambda r: repo[r].substate,
1138 'summary': lambda r: repo[r].description().splitlines()[0],
1138 'summary': lambda r: repo[r].description().splitlines()[0],
1139 'diff': lambda r: list(repo[r].diff(git=True),)
1139 'diff': lambda r: list(repo[r].diff(git=True),)
1140 }
1140 }
1141 for info in fields:
1141 for info in fields:
1142 getfield = _funcs.get(info, None)
1142 getfield = _funcs.get(info, None)
1143 if getfield is None:
1143 if getfield is None:
1144 raise error.ParseError(
1144 raise error.ParseError(
1145 _("unexpected field name passed to matching: %s") % info)
1145 _("unexpected field name passed to matching: %s") % info)
1146 getfieldfuncs.append(getfield)
1146 getfieldfuncs.append(getfield)
1147 # convert the getfield array of functions into a "getinfo" function
1147 # convert the getfield array of functions into a "getinfo" function
1148 # which returns an array of field values (or a single value if there
1148 # which returns an array of field values (or a single value if there
1149 # is only one field to match)
1149 # is only one field to match)
1150 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1150 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1151
1151
1152 matches = set()
1152 matches = set()
1153 for rev in revs:
1153 for rev in revs:
1154 target = getinfo(rev)
1154 target = getinfo(rev)
1155 for r in subset:
1155 for r in subset:
1156 match = True
1156 match = True
1157 for n, f in enumerate(getfieldfuncs):
1157 for n, f in enumerate(getfieldfuncs):
1158 if target[n] != f(r):
1158 if target[n] != f(r):
1159 match = False
1159 match = False
1160 break
1160 break
1161 if match:
1161 if match:
1162 matches.add(r)
1162 matches.add(r)
1163 return [r for r in subset if r in matches]
1163 return [r for r in subset if r in matches]
1164
1164
1165 def reverse(repo, subset, x):
1165 def reverse(repo, subset, x):
1166 """``reverse(set)``
1166 """``reverse(set)``
1167 Reverse order of set.
1167 Reverse order of set.
1168 """
1168 """
1169 l = getset(repo, subset, x)
1169 l = getset(repo, subset, x)
1170 if not isinstance(l, list):
1170 if not isinstance(l, list):
1171 l = list(l)
1171 l = list(l)
1172 l.reverse()
1172 l.reverse()
1173 return l
1173 return l
1174
1174
1175 def roots(repo, subset, x):
1175 def roots(repo, subset, x):
1176 """``roots(set)``
1176 """``roots(set)``
1177 Changesets in set with no parent changeset in set.
1177 Changesets in set with no parent changeset in set.
1178 """
1178 """
1179 s = set(getset(repo, xrange(len(repo)), x))
1179 s = set(getset(repo, xrange(len(repo)), x))
1180 subset = [r for r in subset if r in s]
1180 subset = [r for r in subset if r in s]
1181 cs = _children(repo, subset, s)
1181 cs = _children(repo, subset, s)
1182 return [r for r in subset if r not in cs]
1182 return [r for r in subset if r not in cs]
1183
1183
1184 def secret(repo, subset, x):
1184 def secret(repo, subset, x):
1185 """``secret()``
1185 """``secret()``
1186 Changeset in secret phase."""
1186 Changeset in secret phase."""
1187 getargs(x, 0, 0, _("secret takes no arguments"))
1187 getargs(x, 0, 0, _("secret takes no arguments"))
1188 pc = repo._phasecache
1188 pc = repo._phasecache
1189 return [r for r in subset if pc.phase(repo, r) == phases.secret]
1189 return [r for r in subset if pc.phase(repo, r) == phases.secret]
1190
1190
1191 def sort(repo, subset, x):
1191 def sort(repo, subset, x):
1192 """``sort(set[, [-]key...])``
1192 """``sort(set[, [-]key...])``
1193 Sort set by keys. The default sort order is ascending, specify a key
1193 Sort set by keys. The default sort order is ascending, specify a key
1194 as ``-key`` to sort in descending order.
1194 as ``-key`` to sort in descending order.
1195
1195
1196 The keys can be:
1196 The keys can be:
1197
1197
1198 - ``rev`` for the revision number,
1198 - ``rev`` for the revision number,
1199 - ``branch`` for the branch name,
1199 - ``branch`` for the branch name,
1200 - ``desc`` for the commit message (description),
1200 - ``desc`` for the commit message (description),
1201 - ``user`` for user name (``author`` can be used as an alias),
1201 - ``user`` for user name (``author`` can be used as an alias),
1202 - ``date`` for the commit date
1202 - ``date`` for the commit date
1203 """
1203 """
1204 # i18n: "sort" is a keyword
1204 # i18n: "sort" is a keyword
1205 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1205 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1206 keys = "rev"
1206 keys = "rev"
1207 if len(l) == 2:
1207 if len(l) == 2:
1208 keys = getstring(l[1], _("sort spec must be a string"))
1208 keys = getstring(l[1], _("sort spec must be a string"))
1209
1209
1210 s = l[0]
1210 s = l[0]
1211 keys = keys.split()
1211 keys = keys.split()
1212 l = []
1212 l = []
1213 def invert(s):
1213 def invert(s):
1214 return "".join(chr(255 - ord(c)) for c in s)
1214 return "".join(chr(255 - ord(c)) for c in s)
1215 for r in getset(repo, subset, s):
1215 for r in getset(repo, subset, s):
1216 c = repo[r]
1216 c = repo[r]
1217 e = []
1217 e = []
1218 for k in keys:
1218 for k in keys:
1219 if k == 'rev':
1219 if k == 'rev':
1220 e.append(r)
1220 e.append(r)
1221 elif k == '-rev':
1221 elif k == '-rev':
1222 e.append(-r)
1222 e.append(-r)
1223 elif k == 'branch':
1223 elif k == 'branch':
1224 e.append(c.branch())
1224 e.append(c.branch())
1225 elif k == '-branch':
1225 elif k == '-branch':
1226 e.append(invert(c.branch()))
1226 e.append(invert(c.branch()))
1227 elif k == 'desc':
1227 elif k == 'desc':
1228 e.append(c.description())
1228 e.append(c.description())
1229 elif k == '-desc':
1229 elif k == '-desc':
1230 e.append(invert(c.description()))
1230 e.append(invert(c.description()))
1231 elif k in 'user author':
1231 elif k in 'user author':
1232 e.append(c.user())
1232 e.append(c.user())
1233 elif k in '-user -author':
1233 elif k in '-user -author':
1234 e.append(invert(c.user()))
1234 e.append(invert(c.user()))
1235 elif k == 'date':
1235 elif k == 'date':
1236 e.append(c.date()[0])
1236 e.append(c.date()[0])
1237 elif k == '-date':
1237 elif k == '-date':
1238 e.append(-c.date()[0])
1238 e.append(-c.date()[0])
1239 else:
1239 else:
1240 raise error.ParseError(_("unknown sort key %r") % k)
1240 raise error.ParseError(_("unknown sort key %r") % k)
1241 e.append(r)
1241 e.append(r)
1242 l.append(e)
1242 l.append(e)
1243 l.sort()
1243 l.sort()
1244 return [e[-1] for e in l]
1244 return [e[-1] for e in l]
1245
1245
1246 def _stringmatcher(pattern):
1246 def _stringmatcher(pattern):
1247 """
1247 """
1248 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1248 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1249 returns the matcher name, pattern, and matcher function.
1249 returns the matcher name, pattern, and matcher function.
1250 missing or unknown prefixes are treated as literal matches.
1250 missing or unknown prefixes are treated as literal matches.
1251
1251
1252 helper for tests:
1252 helper for tests:
1253 >>> def test(pattern, *tests):
1253 >>> def test(pattern, *tests):
1254 ... kind, pattern, matcher = _stringmatcher(pattern)
1254 ... kind, pattern, matcher = _stringmatcher(pattern)
1255 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1255 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1256
1256
1257 exact matching (no prefix):
1257 exact matching (no prefix):
1258 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1258 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1259 ('literal', 'abcdefg', [False, False, True])
1259 ('literal', 'abcdefg', [False, False, True])
1260
1260
1261 regex matching ('re:' prefix)
1261 regex matching ('re:' prefix)
1262 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1262 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1263 ('re', 'a.+b', [False, False, True])
1263 ('re', 'a.+b', [False, False, True])
1264
1264
1265 force exact matches ('literal:' prefix)
1265 force exact matches ('literal:' prefix)
1266 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1266 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1267 ('literal', 're:foobar', [False, True])
1267 ('literal', 're:foobar', [False, True])
1268
1268
1269 unknown prefixes are ignored and treated as literals
1269 unknown prefixes are ignored and treated as literals
1270 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1270 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1271 ('literal', 'foo:bar', [False, False, True])
1271 ('literal', 'foo:bar', [False, False, True])
1272 """
1272 """
1273 if pattern.startswith('re:'):
1273 if pattern.startswith('re:'):
1274 pattern = pattern[3:]
1274 pattern = pattern[3:]
1275 try:
1275 try:
1276 regex = re.compile(pattern)
1276 regex = re.compile(pattern)
1277 except re.error, e:
1277 except re.error, e:
1278 raise error.ParseError(_('invalid regular expression: %s')
1278 raise error.ParseError(_('invalid regular expression: %s')
1279 % e)
1279 % e)
1280 return 're', pattern, regex.search
1280 return 're', pattern, regex.search
1281 elif pattern.startswith('literal:'):
1281 elif pattern.startswith('literal:'):
1282 pattern = pattern[8:]
1282 pattern = pattern[8:]
1283 return 'literal', pattern, pattern.__eq__
1283 return 'literal', pattern, pattern.__eq__
1284
1284
1285 def _substringmatcher(pattern):
1285 def _substringmatcher(pattern):
1286 kind, pattern, matcher = _stringmatcher(pattern)
1286 kind, pattern, matcher = _stringmatcher(pattern)
1287 if kind == 'literal':
1287 if kind == 'literal':
1288 matcher = lambda s: pattern in s
1288 matcher = lambda s: pattern in s
1289 return kind, pattern, matcher
1289 return kind, pattern, matcher
1290
1290
1291 def tag(repo, subset, x):
1291 def tag(repo, subset, x):
1292 """``tag([name])``
1292 """``tag([name])``
1293 The specified tag by name, or all tagged revisions if no name is given.
1293 The specified tag by name, or all tagged revisions if no name is given.
1294 """
1294 """
1295 # i18n: "tag" is a keyword
1295 # i18n: "tag" is a keyword
1296 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1296 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1297 cl = repo.changelog
1297 cl = repo.changelog
1298 if args:
1298 if args:
1299 pattern = getstring(args[0],
1299 pattern = getstring(args[0],
1300 # i18n: "tag" is a keyword
1300 # i18n: "tag" is a keyword
1301 _('the argument to tag must be a string'))
1301 _('the argument to tag must be a string'))
1302 kind, pattern, matcher = _stringmatcher(pattern)
1302 kind, pattern, matcher = _stringmatcher(pattern)
1303 if kind == 'literal':
1303 if kind == 'literal':
1304 # avoid resolving all tags
1304 # avoid resolving all tags
1305 tn = repo._tagscache.tags.get(pattern, None)
1305 tn = repo._tagscache.tags.get(pattern, None)
1306 if tn is None:
1306 if tn is None:
1307 raise util.Abort(_("tag '%s' does not exist") % pattern)
1307 raise util.Abort(_("tag '%s' does not exist") % pattern)
1308 s = set([repo[tn].rev()])
1308 s = set([repo[tn].rev()])
1309 else:
1309 else:
1310 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1310 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1311 if not s:
1311 if not s:
1312 raise util.Abort(_("no tags exist that match '%s'") % pattern)
1312 raise util.Abort(_("no tags exist that match '%s'") % pattern)
1313 else:
1313 else:
1314 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1314 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1315 return [r for r in subset if r in s]
1315 return [r for r in subset if r in s]
1316
1316
1317 def tagged(repo, subset, x):
1317 def tagged(repo, subset, x):
1318 return tag(repo, subset, x)
1318 return tag(repo, subset, x)
1319
1319
1320 def unstable(repo, subset, x):
1321 """``unstable()``
1322 Unstable changesets are non-obsolete with obsolete descendants."""
1323 getargs(x, 0, 0, _("obsolete takes no arguments"))
1324 unstableset = set(repo.revs('(obsolete()::) - obsolete()'))
1325 return [r for r in subset if r in unstableset]
1326
1327
1320 def user(repo, subset, x):
1328 def user(repo, subset, x):
1321 """``user(string)``
1329 """``user(string)``
1322 User name contains string. The match is case-insensitive.
1330 User name contains string. The match is case-insensitive.
1323
1331
1324 If `string` starts with `re:`, the remainder of the string is treated as
1332 If `string` starts with `re:`, the remainder of the string is treated as
1325 a regular expression. To match a user that actually contains `re:`, use
1333 a regular expression. To match a user that actually contains `re:`, use
1326 the prefix `literal:`.
1334 the prefix `literal:`.
1327 """
1335 """
1328 return author(repo, subset, x)
1336 return author(repo, subset, x)
1329
1337
1330 # for internal use
1338 # for internal use
1331 def _list(repo, subset, x):
1339 def _list(repo, subset, x):
1332 s = getstring(x, "internal error")
1340 s = getstring(x, "internal error")
1333 if not s:
1341 if not s:
1334 return []
1342 return []
1335 if not isinstance(subset, set):
1343 if not isinstance(subset, set):
1336 subset = set(subset)
1344 subset = set(subset)
1337 ls = [repo[r].rev() for r in s.split('\0')]
1345 ls = [repo[r].rev() for r in s.split('\0')]
1338 return [r for r in ls if r in subset]
1346 return [r for r in ls if r in subset]
1339
1347
1340 symbols = {
1348 symbols = {
1341 "adds": adds,
1349 "adds": adds,
1342 "all": getall,
1350 "all": getall,
1343 "ancestor": ancestor,
1351 "ancestor": ancestor,
1344 "ancestors": ancestors,
1352 "ancestors": ancestors,
1345 "_firstancestors": _firstancestors,
1353 "_firstancestors": _firstancestors,
1346 "author": author,
1354 "author": author,
1347 "bisect": bisect,
1355 "bisect": bisect,
1348 "bisected": bisected,
1356 "bisected": bisected,
1349 "bookmark": bookmark,
1357 "bookmark": bookmark,
1350 "branch": branch,
1358 "branch": branch,
1351 "children": children,
1359 "children": children,
1352 "closed": closed,
1360 "closed": closed,
1353 "contains": contains,
1361 "contains": contains,
1354 "converted": converted,
1362 "converted": converted,
1355 "date": date,
1363 "date": date,
1356 "desc": desc,
1364 "desc": desc,
1357 "descendants": descendants,
1365 "descendants": descendants,
1358 "_firstdescendants": _firstdescendants,
1366 "_firstdescendants": _firstdescendants,
1359 "draft": draft,
1367 "draft": draft,
1360 "extra": extra,
1368 "extra": extra,
1361 "file": hasfile,
1369 "file": hasfile,
1362 "filelog": filelog,
1370 "filelog": filelog,
1363 "first": first,
1371 "first": first,
1364 "follow": follow,
1372 "follow": follow,
1365 "_followfirst": _followfirst,
1373 "_followfirst": _followfirst,
1366 "grep": grep,
1374 "grep": grep,
1367 "head": head,
1375 "head": head,
1368 "heads": heads,
1376 "heads": heads,
1369 "id": node_,
1377 "id": node_,
1370 "keyword": keyword,
1378 "keyword": keyword,
1371 "last": last,
1379 "last": last,
1372 "limit": limit,
1380 "limit": limit,
1373 "_matchfiles": _matchfiles,
1381 "_matchfiles": _matchfiles,
1374 "max": maxrev,
1382 "max": maxrev,
1375 "merge": merge,
1383 "merge": merge,
1376 "min": minrev,
1384 "min": minrev,
1377 "modifies": modifies,
1385 "modifies": modifies,
1378 "obsolete": obsolete,
1386 "obsolete": obsolete,
1379 "outgoing": outgoing,
1387 "outgoing": outgoing,
1380 "p1": p1,
1388 "p1": p1,
1381 "p2": p2,
1389 "p2": p2,
1382 "parents": parents,
1390 "parents": parents,
1383 "present": present,
1391 "present": present,
1384 "public": public,
1392 "public": public,
1385 "remote": remote,
1393 "remote": remote,
1386 "removes": removes,
1394 "removes": removes,
1387 "rev": rev,
1395 "rev": rev,
1388 "reverse": reverse,
1396 "reverse": reverse,
1389 "roots": roots,
1397 "roots": roots,
1390 "sort": sort,
1398 "sort": sort,
1391 "secret": secret,
1399 "secret": secret,
1392 "matching": matching,
1400 "matching": matching,
1393 "tag": tag,
1401 "tag": tag,
1394 "tagged": tagged,
1402 "tagged": tagged,
1395 "user": user,
1403 "user": user,
1404 "unstable": unstable,
1396 "_list": _list,
1405 "_list": _list,
1397 }
1406 }
1398
1407
1399 methods = {
1408 methods = {
1400 "range": rangeset,
1409 "range": rangeset,
1401 "dagrange": dagrange,
1410 "dagrange": dagrange,
1402 "string": stringset,
1411 "string": stringset,
1403 "symbol": symbolset,
1412 "symbol": symbolset,
1404 "and": andset,
1413 "and": andset,
1405 "or": orset,
1414 "or": orset,
1406 "not": notset,
1415 "not": notset,
1407 "list": listset,
1416 "list": listset,
1408 "func": func,
1417 "func": func,
1409 "ancestor": ancestorspec,
1418 "ancestor": ancestorspec,
1410 "parent": parentspec,
1419 "parent": parentspec,
1411 "parentpost": p1,
1420 "parentpost": p1,
1412 }
1421 }
1413
1422
1414 def optimize(x, small):
1423 def optimize(x, small):
1415 if x is None:
1424 if x is None:
1416 return 0, x
1425 return 0, x
1417
1426
1418 smallbonus = 1
1427 smallbonus = 1
1419 if small:
1428 if small:
1420 smallbonus = .5
1429 smallbonus = .5
1421
1430
1422 op = x[0]
1431 op = x[0]
1423 if op == 'minus':
1432 if op == 'minus':
1424 return optimize(('and', x[1], ('not', x[2])), small)
1433 return optimize(('and', x[1], ('not', x[2])), small)
1425 elif op == 'dagrangepre':
1434 elif op == 'dagrangepre':
1426 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1435 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1427 elif op == 'dagrangepost':
1436 elif op == 'dagrangepost':
1428 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1437 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1429 elif op == 'rangepre':
1438 elif op == 'rangepre':
1430 return optimize(('range', ('string', '0'), x[1]), small)
1439 return optimize(('range', ('string', '0'), x[1]), small)
1431 elif op == 'rangepost':
1440 elif op == 'rangepost':
1432 return optimize(('range', x[1], ('string', 'tip')), small)
1441 return optimize(('range', x[1], ('string', 'tip')), small)
1433 elif op == 'negate':
1442 elif op == 'negate':
1434 return optimize(('string',
1443 return optimize(('string',
1435 '-' + getstring(x[1], _("can't negate that"))), small)
1444 '-' + getstring(x[1], _("can't negate that"))), small)
1436 elif op in 'string symbol negate':
1445 elif op in 'string symbol negate':
1437 return smallbonus, x # single revisions are small
1446 return smallbonus, x # single revisions are small
1438 elif op == 'and':
1447 elif op == 'and':
1439 wa, ta = optimize(x[1], True)
1448 wa, ta = optimize(x[1], True)
1440 wb, tb = optimize(x[2], True)
1449 wb, tb = optimize(x[2], True)
1441 w = min(wa, wb)
1450 w = min(wa, wb)
1442 if wa > wb:
1451 if wa > wb:
1443 return w, (op, tb, ta)
1452 return w, (op, tb, ta)
1444 return w, (op, ta, tb)
1453 return w, (op, ta, tb)
1445 elif op == 'or':
1454 elif op == 'or':
1446 wa, ta = optimize(x[1], False)
1455 wa, ta = optimize(x[1], False)
1447 wb, tb = optimize(x[2], False)
1456 wb, tb = optimize(x[2], False)
1448 if wb < wa:
1457 if wb < wa:
1449 wb, wa = wa, wb
1458 wb, wa = wa, wb
1450 return max(wa, wb), (op, ta, tb)
1459 return max(wa, wb), (op, ta, tb)
1451 elif op == 'not':
1460 elif op == 'not':
1452 o = optimize(x[1], not small)
1461 o = optimize(x[1], not small)
1453 return o[0], (op, o[1])
1462 return o[0], (op, o[1])
1454 elif op == 'parentpost':
1463 elif op == 'parentpost':
1455 o = optimize(x[1], small)
1464 o = optimize(x[1], small)
1456 return o[0], (op, o[1])
1465 return o[0], (op, o[1])
1457 elif op == 'group':
1466 elif op == 'group':
1458 return optimize(x[1], small)
1467 return optimize(x[1], small)
1459 elif op in 'dagrange range list parent ancestorspec':
1468 elif op in 'dagrange range list parent ancestorspec':
1460 if op == 'parent':
1469 if op == 'parent':
1461 # x^:y means (x^) : y, not x ^ (:y)
1470 # x^:y means (x^) : y, not x ^ (:y)
1462 post = ('parentpost', x[1])
1471 post = ('parentpost', x[1])
1463 if x[2][0] == 'dagrangepre':
1472 if x[2][0] == 'dagrangepre':
1464 return optimize(('dagrange', post, x[2][1]), small)
1473 return optimize(('dagrange', post, x[2][1]), small)
1465 elif x[2][0] == 'rangepre':
1474 elif x[2][0] == 'rangepre':
1466 return optimize(('range', post, x[2][1]), small)
1475 return optimize(('range', post, x[2][1]), small)
1467
1476
1468 wa, ta = optimize(x[1], small)
1477 wa, ta = optimize(x[1], small)
1469 wb, tb = optimize(x[2], small)
1478 wb, tb = optimize(x[2], small)
1470 return wa + wb, (op, ta, tb)
1479 return wa + wb, (op, ta, tb)
1471 elif op == 'func':
1480 elif op == 'func':
1472 f = getstring(x[1], _("not a symbol"))
1481 f = getstring(x[1], _("not a symbol"))
1473 wa, ta = optimize(x[2], small)
1482 wa, ta = optimize(x[2], small)
1474 if f in ("author branch closed date desc file grep keyword "
1483 if f in ("author branch closed date desc file grep keyword "
1475 "outgoing user"):
1484 "outgoing user"):
1476 w = 10 # slow
1485 w = 10 # slow
1477 elif f in "modifies adds removes":
1486 elif f in "modifies adds removes":
1478 w = 30 # slower
1487 w = 30 # slower
1479 elif f == "contains":
1488 elif f == "contains":
1480 w = 100 # very slow
1489 w = 100 # very slow
1481 elif f == "ancestor":
1490 elif f == "ancestor":
1482 w = 1 * smallbonus
1491 w = 1 * smallbonus
1483 elif f in "reverse limit first":
1492 elif f in "reverse limit first":
1484 w = 0
1493 w = 0
1485 elif f in "sort":
1494 elif f in "sort":
1486 w = 10 # assume most sorts look at changelog
1495 w = 10 # assume most sorts look at changelog
1487 else:
1496 else:
1488 w = 1
1497 w = 1
1489 return w + wa, (op, x[1], ta)
1498 return w + wa, (op, x[1], ta)
1490 return 1, x
1499 return 1, x
1491
1500
1492 _aliasarg = ('func', ('symbol', '_aliasarg'))
1501 _aliasarg = ('func', ('symbol', '_aliasarg'))
1493 def _getaliasarg(tree):
1502 def _getaliasarg(tree):
1494 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1503 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1495 return X, None otherwise.
1504 return X, None otherwise.
1496 """
1505 """
1497 if (len(tree) == 3 and tree[:2] == _aliasarg
1506 if (len(tree) == 3 and tree[:2] == _aliasarg
1498 and tree[2][0] == 'string'):
1507 and tree[2][0] == 'string'):
1499 return tree[2][1]
1508 return tree[2][1]
1500 return None
1509 return None
1501
1510
1502 def _checkaliasarg(tree, known=None):
1511 def _checkaliasarg(tree, known=None):
1503 """Check tree contains no _aliasarg construct or only ones which
1512 """Check tree contains no _aliasarg construct or only ones which
1504 value is in known. Used to avoid alias placeholders injection.
1513 value is in known. Used to avoid alias placeholders injection.
1505 """
1514 """
1506 if isinstance(tree, tuple):
1515 if isinstance(tree, tuple):
1507 arg = _getaliasarg(tree)
1516 arg = _getaliasarg(tree)
1508 if arg is not None and (not known or arg not in known):
1517 if arg is not None and (not known or arg not in known):
1509 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1518 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1510 for t in tree:
1519 for t in tree:
1511 _checkaliasarg(t, known)
1520 _checkaliasarg(t, known)
1512
1521
1513 class revsetalias(object):
1522 class revsetalias(object):
1514 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1523 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1515 args = None
1524 args = None
1516
1525
1517 def __init__(self, name, value):
1526 def __init__(self, name, value):
1518 '''Aliases like:
1527 '''Aliases like:
1519
1528
1520 h = heads(default)
1529 h = heads(default)
1521 b($1) = ancestors($1) - ancestors(default)
1530 b($1) = ancestors($1) - ancestors(default)
1522 '''
1531 '''
1523 m = self.funcre.search(name)
1532 m = self.funcre.search(name)
1524 if m:
1533 if m:
1525 self.name = m.group(1)
1534 self.name = m.group(1)
1526 self.tree = ('func', ('symbol', m.group(1)))
1535 self.tree = ('func', ('symbol', m.group(1)))
1527 self.args = [x.strip() for x in m.group(2).split(',')]
1536 self.args = [x.strip() for x in m.group(2).split(',')]
1528 for arg in self.args:
1537 for arg in self.args:
1529 # _aliasarg() is an unknown symbol only used separate
1538 # _aliasarg() is an unknown symbol only used separate
1530 # alias argument placeholders from regular strings.
1539 # alias argument placeholders from regular strings.
1531 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1540 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1532 else:
1541 else:
1533 self.name = name
1542 self.name = name
1534 self.tree = ('symbol', name)
1543 self.tree = ('symbol', name)
1535
1544
1536 self.replacement, pos = parse(value)
1545 self.replacement, pos = parse(value)
1537 if pos != len(value):
1546 if pos != len(value):
1538 raise error.ParseError(_('invalid token'), pos)
1547 raise error.ParseError(_('invalid token'), pos)
1539 # Check for placeholder injection
1548 # Check for placeholder injection
1540 _checkaliasarg(self.replacement, self.args)
1549 _checkaliasarg(self.replacement, self.args)
1541
1550
1542 def _getalias(aliases, tree):
1551 def _getalias(aliases, tree):
1543 """If tree looks like an unexpanded alias, return it. Return None
1552 """If tree looks like an unexpanded alias, return it. Return None
1544 otherwise.
1553 otherwise.
1545 """
1554 """
1546 if isinstance(tree, tuple) and tree:
1555 if isinstance(tree, tuple) and tree:
1547 if tree[0] == 'symbol' and len(tree) == 2:
1556 if tree[0] == 'symbol' and len(tree) == 2:
1548 name = tree[1]
1557 name = tree[1]
1549 alias = aliases.get(name)
1558 alias = aliases.get(name)
1550 if alias and alias.args is None and alias.tree == tree:
1559 if alias and alias.args is None and alias.tree == tree:
1551 return alias
1560 return alias
1552 if tree[0] == 'func' and len(tree) > 1:
1561 if tree[0] == 'func' and len(tree) > 1:
1553 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1562 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1554 name = tree[1][1]
1563 name = tree[1][1]
1555 alias = aliases.get(name)
1564 alias = aliases.get(name)
1556 if alias and alias.args is not None and alias.tree == tree[:2]:
1565 if alias and alias.args is not None and alias.tree == tree[:2]:
1557 return alias
1566 return alias
1558 return None
1567 return None
1559
1568
1560 def _expandargs(tree, args):
1569 def _expandargs(tree, args):
1561 """Replace _aliasarg instances with the substitution value of the
1570 """Replace _aliasarg instances with the substitution value of the
1562 same name in args, recursively.
1571 same name in args, recursively.
1563 """
1572 """
1564 if not tree or not isinstance(tree, tuple):
1573 if not tree or not isinstance(tree, tuple):
1565 return tree
1574 return tree
1566 arg = _getaliasarg(tree)
1575 arg = _getaliasarg(tree)
1567 if arg is not None:
1576 if arg is not None:
1568 return args[arg]
1577 return args[arg]
1569 return tuple(_expandargs(t, args) for t in tree)
1578 return tuple(_expandargs(t, args) for t in tree)
1570
1579
1571 def _expandaliases(aliases, tree, expanding, cache):
1580 def _expandaliases(aliases, tree, expanding, cache):
1572 """Expand aliases in tree, recursively.
1581 """Expand aliases in tree, recursively.
1573
1582
1574 'aliases' is a dictionary mapping user defined aliases to
1583 'aliases' is a dictionary mapping user defined aliases to
1575 revsetalias objects.
1584 revsetalias objects.
1576 """
1585 """
1577 if not isinstance(tree, tuple):
1586 if not isinstance(tree, tuple):
1578 # Do not expand raw strings
1587 # Do not expand raw strings
1579 return tree
1588 return tree
1580 alias = _getalias(aliases, tree)
1589 alias = _getalias(aliases, tree)
1581 if alias is not None:
1590 if alias is not None:
1582 if alias in expanding:
1591 if alias in expanding:
1583 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1592 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1584 'detected') % alias.name)
1593 'detected') % alias.name)
1585 expanding.append(alias)
1594 expanding.append(alias)
1586 if alias.name not in cache:
1595 if alias.name not in cache:
1587 cache[alias.name] = _expandaliases(aliases, alias.replacement,
1596 cache[alias.name] = _expandaliases(aliases, alias.replacement,
1588 expanding, cache)
1597 expanding, cache)
1589 result = cache[alias.name]
1598 result = cache[alias.name]
1590 expanding.pop()
1599 expanding.pop()
1591 if alias.args is not None:
1600 if alias.args is not None:
1592 l = getlist(tree[2])
1601 l = getlist(tree[2])
1593 if len(l) != len(alias.args):
1602 if len(l) != len(alias.args):
1594 raise error.ParseError(
1603 raise error.ParseError(
1595 _('invalid number of arguments: %s') % len(l))
1604 _('invalid number of arguments: %s') % len(l))
1596 l = [_expandaliases(aliases, a, [], cache) for a in l]
1605 l = [_expandaliases(aliases, a, [], cache) for a in l]
1597 result = _expandargs(result, dict(zip(alias.args, l)))
1606 result = _expandargs(result, dict(zip(alias.args, l)))
1598 else:
1607 else:
1599 result = tuple(_expandaliases(aliases, t, expanding, cache)
1608 result = tuple(_expandaliases(aliases, t, expanding, cache)
1600 for t in tree)
1609 for t in tree)
1601 return result
1610 return result
1602
1611
1603 def findaliases(ui, tree):
1612 def findaliases(ui, tree):
1604 _checkaliasarg(tree)
1613 _checkaliasarg(tree)
1605 aliases = {}
1614 aliases = {}
1606 for k, v in ui.configitems('revsetalias'):
1615 for k, v in ui.configitems('revsetalias'):
1607 alias = revsetalias(k, v)
1616 alias = revsetalias(k, v)
1608 aliases[alias.name] = alias
1617 aliases[alias.name] = alias
1609 return _expandaliases(aliases, tree, [], {})
1618 return _expandaliases(aliases, tree, [], {})
1610
1619
1611 parse = parser.parser(tokenize, elements).parse
1620 parse = parser.parser(tokenize, elements).parse
1612
1621
1613 def match(ui, spec):
1622 def match(ui, spec):
1614 if not spec:
1623 if not spec:
1615 raise error.ParseError(_("empty query"))
1624 raise error.ParseError(_("empty query"))
1616 tree, pos = parse(spec)
1625 tree, pos = parse(spec)
1617 if (pos != len(spec)):
1626 if (pos != len(spec)):
1618 raise error.ParseError(_("invalid token"), pos)
1627 raise error.ParseError(_("invalid token"), pos)
1619 if ui:
1628 if ui:
1620 tree = findaliases(ui, tree)
1629 tree = findaliases(ui, tree)
1621 weight, tree = optimize(tree, True)
1630 weight, tree = optimize(tree, True)
1622 def mfunc(repo, subset):
1631 def mfunc(repo, subset):
1623 return getset(repo, subset, tree)
1632 return getset(repo, subset, tree)
1624 return mfunc
1633 return mfunc
1625
1634
1626 def formatspec(expr, *args):
1635 def formatspec(expr, *args):
1627 '''
1636 '''
1628 This is a convenience function for using revsets internally, and
1637 This is a convenience function for using revsets internally, and
1629 escapes arguments appropriately. Aliases are intentionally ignored
1638 escapes arguments appropriately. Aliases are intentionally ignored
1630 so that intended expression behavior isn't accidentally subverted.
1639 so that intended expression behavior isn't accidentally subverted.
1631
1640
1632 Supported arguments:
1641 Supported arguments:
1633
1642
1634 %r = revset expression, parenthesized
1643 %r = revset expression, parenthesized
1635 %d = int(arg), no quoting
1644 %d = int(arg), no quoting
1636 %s = string(arg), escaped and single-quoted
1645 %s = string(arg), escaped and single-quoted
1637 %b = arg.branch(), escaped and single-quoted
1646 %b = arg.branch(), escaped and single-quoted
1638 %n = hex(arg), single-quoted
1647 %n = hex(arg), single-quoted
1639 %% = a literal '%'
1648 %% = a literal '%'
1640
1649
1641 Prefixing the type with 'l' specifies a parenthesized list of that type.
1650 Prefixing the type with 'l' specifies a parenthesized list of that type.
1642
1651
1643 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1652 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1644 '(10 or 11):: and ((this()) or (that()))'
1653 '(10 or 11):: and ((this()) or (that()))'
1645 >>> formatspec('%d:: and not %d::', 10, 20)
1654 >>> formatspec('%d:: and not %d::', 10, 20)
1646 '10:: and not 20::'
1655 '10:: and not 20::'
1647 >>> formatspec('%ld or %ld', [], [1])
1656 >>> formatspec('%ld or %ld', [], [1])
1648 "_list('') or 1"
1657 "_list('') or 1"
1649 >>> formatspec('keyword(%s)', 'foo\\xe9')
1658 >>> formatspec('keyword(%s)', 'foo\\xe9')
1650 "keyword('foo\\\\xe9')"
1659 "keyword('foo\\\\xe9')"
1651 >>> b = lambda: 'default'
1660 >>> b = lambda: 'default'
1652 >>> b.branch = b
1661 >>> b.branch = b
1653 >>> formatspec('branch(%b)', b)
1662 >>> formatspec('branch(%b)', b)
1654 "branch('default')"
1663 "branch('default')"
1655 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1664 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1656 "root(_list('a\\x00b\\x00c\\x00d'))"
1665 "root(_list('a\\x00b\\x00c\\x00d'))"
1657 '''
1666 '''
1658
1667
1659 def quote(s):
1668 def quote(s):
1660 return repr(str(s))
1669 return repr(str(s))
1661
1670
1662 def argtype(c, arg):
1671 def argtype(c, arg):
1663 if c == 'd':
1672 if c == 'd':
1664 return str(int(arg))
1673 return str(int(arg))
1665 elif c == 's':
1674 elif c == 's':
1666 return quote(arg)
1675 return quote(arg)
1667 elif c == 'r':
1676 elif c == 'r':
1668 parse(arg) # make sure syntax errors are confined
1677 parse(arg) # make sure syntax errors are confined
1669 return '(%s)' % arg
1678 return '(%s)' % arg
1670 elif c == 'n':
1679 elif c == 'n':
1671 return quote(node.hex(arg))
1680 return quote(node.hex(arg))
1672 elif c == 'b':
1681 elif c == 'b':
1673 return quote(arg.branch())
1682 return quote(arg.branch())
1674
1683
1675 def listexp(s, t):
1684 def listexp(s, t):
1676 l = len(s)
1685 l = len(s)
1677 if l == 0:
1686 if l == 0:
1678 return "_list('')"
1687 return "_list('')"
1679 elif l == 1:
1688 elif l == 1:
1680 return argtype(t, s[0])
1689 return argtype(t, s[0])
1681 elif t == 'd':
1690 elif t == 'd':
1682 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1691 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1683 elif t == 's':
1692 elif t == 's':
1684 return "_list('%s')" % "\0".join(s)
1693 return "_list('%s')" % "\0".join(s)
1685 elif t == 'n':
1694 elif t == 'n':
1686 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1695 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1687 elif t == 'b':
1696 elif t == 'b':
1688 return "_list('%s')" % "\0".join(a.branch() for a in s)
1697 return "_list('%s')" % "\0".join(a.branch() for a in s)
1689
1698
1690 m = l // 2
1699 m = l // 2
1691 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1700 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1692
1701
1693 ret = ''
1702 ret = ''
1694 pos = 0
1703 pos = 0
1695 arg = 0
1704 arg = 0
1696 while pos < len(expr):
1705 while pos < len(expr):
1697 c = expr[pos]
1706 c = expr[pos]
1698 if c == '%':
1707 if c == '%':
1699 pos += 1
1708 pos += 1
1700 d = expr[pos]
1709 d = expr[pos]
1701 if d == '%':
1710 if d == '%':
1702 ret += d
1711 ret += d
1703 elif d in 'dsnbr':
1712 elif d in 'dsnbr':
1704 ret += argtype(d, args[arg])
1713 ret += argtype(d, args[arg])
1705 arg += 1
1714 arg += 1
1706 elif d == 'l':
1715 elif d == 'l':
1707 # a list of some type
1716 # a list of some type
1708 pos += 1
1717 pos += 1
1709 d = expr[pos]
1718 d = expr[pos]
1710 ret += listexp(list(args[arg]), d)
1719 ret += listexp(list(args[arg]), d)
1711 arg += 1
1720 arg += 1
1712 else:
1721 else:
1713 raise util.Abort('unexpected revspec format character %s' % d)
1722 raise util.Abort('unexpected revspec format character %s' % d)
1714 else:
1723 else:
1715 ret += c
1724 ret += c
1716 pos += 1
1725 pos += 1
1717
1726
1718 return ret
1727 return ret
1719
1728
1720 def prettyformat(tree):
1729 def prettyformat(tree):
1721 def _prettyformat(tree, level, lines):
1730 def _prettyformat(tree, level, lines):
1722 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1731 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1723 lines.append((level, str(tree)))
1732 lines.append((level, str(tree)))
1724 else:
1733 else:
1725 lines.append((level, '(%s' % tree[0]))
1734 lines.append((level, '(%s' % tree[0]))
1726 for s in tree[1:]:
1735 for s in tree[1:]:
1727 _prettyformat(s, level + 1, lines)
1736 _prettyformat(s, level + 1, lines)
1728 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1737 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1729
1738
1730 lines = []
1739 lines = []
1731 _prettyformat(tree, 0, lines)
1740 _prettyformat(tree, 0, lines)
1732 output = '\n'.join((' '*l + s) for l, s in lines)
1741 output = '\n'.join((' '*l + s) for l, s in lines)
1733 return output
1742 return output
1734
1743
1735 # tell hggettext to extract docstrings from these functions:
1744 # tell hggettext to extract docstrings from these functions:
1736 i18nfunctions = symbols.values()
1745 i18nfunctions = symbols.values()
@@ -1,245 +1,317 b''
1
1 $ cat >> $HGRCPATH << EOF
2 > [extensions]
3 > graphlog=
4 > [phases]
5 > # public changeset are not obsolete
6 > publish=false
7 > EOF
2 $ mkcommit() {
8 $ mkcommit() {
3 > echo "$1" > "$1"
9 > echo "$1" > "$1"
4 > hg add "$1"
10 > hg add "$1"
5 > hg ci -m "add $1"
11 > hg ci -m "add $1"
6 > }
12 > }
7 $ getid() {
13 $ getid() {
8 > hg id --debug -ir "desc('$1')"
14 > hg id --debug -ir "desc('$1')"
9 > }
15 > }
10
16
11
17
12 $ hg init tmpa
18 $ hg init tmpa
13 $ cd tmpa
19 $ cd tmpa
14
20
15 Killing a single changeset without replacement
21 Killing a single changeset without replacement
16
22
17 $ mkcommit kill_me
23 $ mkcommit kill_me
18 $ hg debugobsolete -d '0 0' `getid kill_me` -u babar
24 $ hg debugobsolete -d '0 0' `getid kill_me` -u babar
19 $ hg debugobsolete
25 $ hg debugobsolete
20 97b7c2d76b1845ed3eb988cd612611e72406cef0 0 {'date': '0 0', 'user': 'babar'}
26 97b7c2d76b1845ed3eb988cd612611e72406cef0 0 {'date': '0 0', 'user': 'babar'}
21 $ cd ..
27 $ cd ..
22
28
23 Killing a single changeset with replacement
29 Killing a single changeset with replacement
24
30
25 $ hg init tmpb
31 $ hg init tmpb
26 $ cd tmpb
32 $ cd tmpb
27 $ mkcommit a
33 $ mkcommit a
28 $ mkcommit b
34 $ mkcommit b
29 $ mkcommit original_c
35 $ mkcommit original_c
30 $ hg up "desc('b')"
36 $ hg up "desc('b')"
31 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
37 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
32 $ mkcommit new_c
38 $ mkcommit new_c
33 created new head
39 created new head
34 $ hg debugobsolete `getid original_c` `getid new_c` -d '56 12'
40 $ hg debugobsolete `getid original_c` `getid new_c` -d '56 12'
35 $ hg debugobsolete
41 $ hg debugobsolete
36 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
42 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
37
43
38 do it again (it read the obsstore before adding new changeset)
44 do it again (it read the obsstore before adding new changeset)
39
45
40 $ hg up '.^'
46 $ hg up '.^'
41 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
47 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
42 $ mkcommit new_2_c
48 $ mkcommit new_2_c
43 created new head
49 created new head
44 $ hg debugobsolete -d '1337 0' `getid new_c` `getid new_2_c`
50 $ hg debugobsolete -d '1337 0' `getid new_c` `getid new_2_c`
45 $ hg debugobsolete
51 $ hg debugobsolete
46 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
52 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
47 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
53 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
48
54
49 Register two markers with a missing node
55 Register two markers with a missing node
50
56
51 $ hg up '.^'
57 $ hg up '.^'
52 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
58 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
53 $ mkcommit new_3_c
59 $ mkcommit new_3_c
54 created new head
60 created new head
55 $ hg debugobsolete -d '1338 0' `getid new_2_c` 1337133713371337133713371337133713371337
61 $ hg debugobsolete -d '1338 0' `getid new_2_c` 1337133713371337133713371337133713371337
56 $ hg debugobsolete -d '1339 0' 1337133713371337133713371337133713371337 `getid new_3_c`
62 $ hg debugobsolete -d '1339 0' 1337133713371337133713371337133713371337 `getid new_3_c`
57 $ hg debugobsolete
63 $ hg debugobsolete
58 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
64 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
59 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
65 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
60 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
66 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
61 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
67 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
62
68
63 Check that graphlog detect that a changeset is obsolete:
69 Check that graphlog detect that a changeset is obsolete:
64
70
65 $ hg --config 'extensions.graphlog=' glog
71 $ hg glog
66 @ changeset: 5:5601fb93a350
72 @ changeset: 5:5601fb93a350
67 | tag: tip
73 | tag: tip
68 | parent: 1:7c3bad9141dc
74 | parent: 1:7c3bad9141dc
69 | user: test
75 | user: test
70 | date: Thu Jan 01 00:00:00 1970 +0000
76 | date: Thu Jan 01 00:00:00 1970 +0000
71 | summary: add new_3_c
77 | summary: add new_3_c
72 |
78 |
73 | x changeset: 4:ca819180edb9
79 | x changeset: 4:ca819180edb9
74 |/ parent: 1:7c3bad9141dc
80 |/ parent: 1:7c3bad9141dc
75 | user: test
81 | user: test
76 | date: Thu Jan 01 00:00:00 1970 +0000
82 | date: Thu Jan 01 00:00:00 1970 +0000
77 | summary: add new_2_c
83 | summary: add new_2_c
78 |
84 |
79 | x changeset: 3:cdbce2fbb163
85 | x changeset: 3:cdbce2fbb163
80 |/ parent: 1:7c3bad9141dc
86 |/ parent: 1:7c3bad9141dc
81 | user: test
87 | user: test
82 | date: Thu Jan 01 00:00:00 1970 +0000
88 | date: Thu Jan 01 00:00:00 1970 +0000
83 | summary: add new_c
89 | summary: add new_c
84 |
90 |
85 | x changeset: 2:245bde4270cd
91 | x changeset: 2:245bde4270cd
86 |/ user: test
92 |/ user: test
87 | date: Thu Jan 01 00:00:00 1970 +0000
93 | date: Thu Jan 01 00:00:00 1970 +0000
88 | summary: add original_c
94 | summary: add original_c
89 |
95 |
90 o changeset: 1:7c3bad9141dc
96 o changeset: 1:7c3bad9141dc
91 | user: test
97 | user: test
92 | date: Thu Jan 01 00:00:00 1970 +0000
98 | date: Thu Jan 01 00:00:00 1970 +0000
93 | summary: add b
99 | summary: add b
94 |
100 |
95 o changeset: 0:1f0dee641bb7
101 o changeset: 0:1f0dee641bb7
96 user: test
102 user: test
97 date: Thu Jan 01 00:00:00 1970 +0000
103 date: Thu Jan 01 00:00:00 1970 +0000
98 summary: add a
104 summary: add a
99
105
100
106
101 Check that public changeset are not accounted as obsolete:
107 Check that public changeset are not accounted as obsolete:
102
108
103 $ hg phase --public 2
109 $ hg phase --public 2
104 $ hg --config 'extensions.graphlog=' glog
110 $ hg --config 'extensions.graphlog=' glog
105 @ changeset: 5:5601fb93a350
111 @ changeset: 5:5601fb93a350
106 | tag: tip
112 | tag: tip
107 | parent: 1:7c3bad9141dc
113 | parent: 1:7c3bad9141dc
108 | user: test
114 | user: test
109 | date: Thu Jan 01 00:00:00 1970 +0000
115 | date: Thu Jan 01 00:00:00 1970 +0000
110 | summary: add new_3_c
116 | summary: add new_3_c
111 |
117 |
112 | x changeset: 4:ca819180edb9
118 | x changeset: 4:ca819180edb9
113 |/ parent: 1:7c3bad9141dc
119 |/ parent: 1:7c3bad9141dc
114 | user: test
120 | user: test
115 | date: Thu Jan 01 00:00:00 1970 +0000
121 | date: Thu Jan 01 00:00:00 1970 +0000
116 | summary: add new_2_c
122 | summary: add new_2_c
117 |
123 |
118 | x changeset: 3:cdbce2fbb163
124 | x changeset: 3:cdbce2fbb163
119 |/ parent: 1:7c3bad9141dc
125 |/ parent: 1:7c3bad9141dc
120 | user: test
126 | user: test
121 | date: Thu Jan 01 00:00:00 1970 +0000
127 | date: Thu Jan 01 00:00:00 1970 +0000
122 | summary: add new_c
128 | summary: add new_c
123 |
129 |
124 | o changeset: 2:245bde4270cd
130 | o changeset: 2:245bde4270cd
125 |/ user: test
131 |/ user: test
126 | date: Thu Jan 01 00:00:00 1970 +0000
132 | date: Thu Jan 01 00:00:00 1970 +0000
127 | summary: add original_c
133 | summary: add original_c
128 |
134 |
129 o changeset: 1:7c3bad9141dc
135 o changeset: 1:7c3bad9141dc
130 | user: test
136 | user: test
131 | date: Thu Jan 01 00:00:00 1970 +0000
137 | date: Thu Jan 01 00:00:00 1970 +0000
132 | summary: add b
138 | summary: add b
133 |
139 |
134 o changeset: 0:1f0dee641bb7
140 o changeset: 0:1f0dee641bb7
135 user: test
141 user: test
136 date: Thu Jan 01 00:00:00 1970 +0000
142 date: Thu Jan 01 00:00:00 1970 +0000
137 summary: add a
143 summary: add a
138
144
139
145
140 $ cd ..
146 $ cd ..
141
147
142 Exchange Test
148 Exchange Test
143 ============================
149 ============================
144
150
145 Destination repo does not have any data
151 Destination repo does not have any data
146 ---------------------------------------
152 ---------------------------------------
147
153
148 Try to pull markers
154 Try to pull markers
149
155
150 $ hg init tmpc
156 $ hg init tmpc
151 $ cd tmpc
157 $ cd tmpc
152 $ hg pull ../tmpb
158 $ hg pull ../tmpb
153 pulling from ../tmpb
159 pulling from ../tmpb
154 requesting all changes
160 requesting all changes
155 adding changesets
161 adding changesets
156 adding manifests
162 adding manifests
157 adding file changes
163 adding file changes
158 added 6 changesets with 6 changes to 6 files (+3 heads)
164 added 6 changesets with 6 changes to 6 files (+3 heads)
159 (run 'hg heads' to see heads, 'hg merge' to merge)
165 (run 'hg heads' to see heads, 'hg merge' to merge)
160 $ hg debugobsolete
166 $ hg debugobsolete
161 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
167 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
162 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
168 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
163 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
169 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
164 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
170 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
165
171
166 Rollback//Transaction support
172 Rollback//Transaction support
167
173
168 $ hg debugobsolete -d '1340 0' aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
174 $ hg debugobsolete -d '1340 0' aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
169 $ hg debugobsolete
175 $ hg debugobsolete
170 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
176 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
171 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
177 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
172 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
178 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
173 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
179 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
174 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 0 {'date': '1340 0', 'user': 'test'}
180 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 0 {'date': '1340 0', 'user': 'test'}
175 $ hg rollback -n
181 $ hg rollback -n
176 repository tip rolled back to revision 5 (undo debugobsolete)
182 repository tip rolled back to revision 5 (undo debugobsolete)
177 $ hg rollback
183 $ hg rollback
178 repository tip rolled back to revision 5 (undo debugobsolete)
184 repository tip rolled back to revision 5 (undo debugobsolete)
179 $ hg debugobsolete
185 $ hg debugobsolete
180 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
186 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
181 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
187 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
182 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
188 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
183 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
189 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
184
190
185 $ cd ..
191 $ cd ..
186
192
187 Try to pull markers
193 Try to pull markers
188
194
189 $ hg init tmpd
195 $ hg init tmpd
190 $ hg -R tmpb push tmpd
196 $ hg -R tmpb push tmpd
191 pushing to tmpd
197 pushing to tmpd
192 searching for changes
198 searching for changes
193 abort: push includes an obsolete changeset: cdbce2fbb163!
199 abort: push includes an obsolete changeset: cdbce2fbb163!
194 [255]
200 [255]
195 $ hg -R tmpd debugobsolete
201 $ hg -R tmpd debugobsolete
196 $ hg -R tmpb push tmpd --rev 'not obsolete()'
202 $ hg -R tmpb push tmpd --rev 'not obsolete()'
197 pushing to tmpd
203 pushing to tmpd
198 searching for changes
204 searching for changes
199 adding changesets
205 adding changesets
200 adding manifests
206 adding manifests
201 adding file changes
207 adding file changes
202 added 4 changesets with 4 changes to 4 files (+1 heads)
208 added 4 changesets with 4 changes to 4 files (+1 heads)
203 $ hg -R tmpd debugobsolete
209 $ hg -R tmpd debugobsolete
204 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
210 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
205 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
211 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
206 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
212 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
207 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
213 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
208
214
209
215
210 Destination repo have existing data
216 Destination repo have existing data
211 ---------------------------------------
217 ---------------------------------------
212
218
213 On pull
219 On pull
214
220
215 $ hg init tmpe
221 $ hg init tmpe
216 $ cd tmpe
222 $ cd tmpe
217 $ hg debugobsolete -d '1339 0' 2448244824482448244824482448244824482448 1339133913391339133913391339133913391339
223 $ hg debugobsolete -d '1339 0' 2448244824482448244824482448244824482448 1339133913391339133913391339133913391339
218 $ hg pull ../tmpb
224 $ hg pull ../tmpb
219 pulling from ../tmpb
225 pulling from ../tmpb
220 requesting all changes
226 requesting all changes
221 adding changesets
227 adding changesets
222 adding manifests
228 adding manifests
223 adding file changes
229 adding file changes
224 added 6 changesets with 6 changes to 6 files (+3 heads)
230 added 6 changesets with 6 changes to 6 files (+3 heads)
225 (run 'hg heads' to see heads, 'hg merge' to merge)
231 (run 'hg heads' to see heads, 'hg merge' to merge)
226 $ hg debugobsolete
232 $ hg debugobsolete
227 2448244824482448244824482448244824482448 1339133913391339133913391339133913391339 0 {'date': '1339 0', 'user': 'test'}
233 2448244824482448244824482448244824482448 1339133913391339133913391339133913391339 0 {'date': '1339 0', 'user': 'test'}
228 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
234 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
229 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
235 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
230 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
236 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
231 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
237 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
232
238
239
233 On push
240 On push
234
241
235 $ hg push ../tmpc
242 $ hg push ../tmpc
236 pushing to ../tmpc
243 pushing to ../tmpc
237 searching for changes
244 searching for changes
238 no changes found
245 no changes found
239 [1]
246 [1]
240 $ hg -R ../tmpc debugobsolete
247 $ hg -R ../tmpc debugobsolete
241 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
248 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f 0 {'date': '56 12', 'user': 'test'}
242 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
249 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
243 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
250 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
244 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
251 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
245 2448244824482448244824482448244824482448 1339133913391339133913391339133913391339 0 {'date': '1339 0', 'user': 'test'}
252 2448244824482448244824482448244824482448 1339133913391339133913391339133913391339 0 {'date': '1339 0', 'user': 'test'}
253
254 detect outgoing obsolete and unstable
255 ---------------------------------------
256
257 $ hg glog
258 o changeset: 5:5601fb93a350
259 | tag: tip
260 | parent: 1:7c3bad9141dc
261 | user: test
262 | date: Thu Jan 01 00:00:00 1970 +0000
263 | summary: add new_3_c
264 |
265 | x changeset: 4:ca819180edb9
266 |/ parent: 1:7c3bad9141dc
267 | user: test
268 | date: Thu Jan 01 00:00:00 1970 +0000
269 | summary: add new_2_c
270 |
271 | x changeset: 3:cdbce2fbb163
272 |/ parent: 1:7c3bad9141dc
273 | user: test
274 | date: Thu Jan 01 00:00:00 1970 +0000
275 | summary: add new_c
276 |
277 | o changeset: 2:245bde4270cd
278 |/ user: test
279 | date: Thu Jan 01 00:00:00 1970 +0000
280 | summary: add original_c
281 |
282 o changeset: 1:7c3bad9141dc
283 | user: test
284 | date: Thu Jan 01 00:00:00 1970 +0000
285 | summary: add b
286 |
287 o changeset: 0:1f0dee641bb7
288 user: test
289 date: Thu Jan 01 00:00:00 1970 +0000
290 summary: add a
291
292 $ hg up -q 'desc("new_2_c")'
293 $ mkcommit original_d
294 $ hg glog -r '::unstable()'
295 @ changeset: 6:7878242aeece
296 | tag: tip
297 | parent: 4:ca819180edb9
298 | user: test
299 | date: Thu Jan 01 00:00:00 1970 +0000
300 | summary: add original_d
301 |
302 x changeset: 4:ca819180edb9
303 | parent: 1:7c3bad9141dc
304 | user: test
305 | date: Thu Jan 01 00:00:00 1970 +0000
306 | summary: add new_2_c
307 |
308 o changeset: 1:7c3bad9141dc
309 | user: test
310 | date: Thu Jan 01 00:00:00 1970 +0000
311 | summary: add b
312 |
313 o changeset: 0:1f0dee641bb7
314 user: test
315 date: Thu Jan 01 00:00:00 1970 +0000
316 summary: add a
317
General Comments 0
You need to be logged in to leave comments. Login now