##// END OF EJS Templates
bookmarks: don't use bookmarks.listbookmarks in local computations...
Kevin Bullock -
r18495:8260fa9f stable
parent child Browse files
Show More
@@ -1,219 +1,219 b''
1 1 # repoview.py - Filtered view of a localrepo object
2 2 #
3 3 # Copyright 2012 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
4 4 # Logilab SA <contact@logilab.fr>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 import copy
10 10 import phases
11 11 import util
12 import obsolete, bookmarks, revset
12 import obsolete, revset
13 13
14 14
15 15 def hideablerevs(repo):
16 16 """Revisions candidates to be hidden
17 17
18 18 This is a standalone function to help extensions to wrap it."""
19 19 return obsolete.getrevs(repo, 'obsolete')
20 20
21 21 def computehidden(repo):
22 22 """compute the set of hidden revision to filter
23 23
24 24 During most operation hidden should be filtered."""
25 25 assert not repo.changelog.filteredrevs
26 26 hideable = hideablerevs(repo)
27 27 if hideable:
28 28 cl = repo.changelog
29 29 firsthideable = min(hideable)
30 30 revs = cl.revs(start=firsthideable)
31 31 blockers = [r for r in revset._children(repo, revs, hideable)
32 32 if r not in hideable]
33 33 for par in repo[None].parents():
34 34 blockers.append(par.rev())
35 for bm in bookmarks.listbookmarks(repo).values():
35 for bm in repo._bookmarks.values():
36 36 blockers.append(repo[bm].rev())
37 37 blocked = cl.ancestors(blockers, inclusive=True)
38 38 return frozenset(r for r in hideable if r not in blocked)
39 39 return frozenset()
40 40
41 41 def computeunserved(repo):
42 42 """compute the set of revision that should be filtered when used a server
43 43
44 44 Secret and hidden changeset should not pretend to be here."""
45 45 assert not repo.changelog.filteredrevs
46 46 # fast path in simple case to avoid impact of non optimised code
47 47 hiddens = filterrevs(repo, 'visible')
48 48 if phases.hassecret(repo):
49 49 cl = repo.changelog
50 50 secret = phases.secret
51 51 getphase = repo._phasecache.phase
52 52 first = min(cl.rev(n) for n in repo._phasecache.phaseroots[secret])
53 53 revs = cl.revs(start=first)
54 54 secrets = set(r for r in revs if getphase(repo, r) >= secret)
55 55 return frozenset(hiddens | secrets)
56 56 else:
57 57 return hiddens
58 58 return frozenset()
59 59
60 60 def computemutable(repo):
61 61 """compute the set of revision that should be filtered when used a server
62 62
63 63 Secret and hidden changeset should not pretend to be here."""
64 64 assert not repo.changelog.filteredrevs
65 65 # fast check to avoid revset call on huge repo
66 66 if util.any(repo._phasecache.phaseroots[1:]):
67 67 getphase = repo._phasecache.phase
68 68 maymutable = filterrevs(repo, 'base')
69 69 return frozenset(r for r in maymutable if getphase(repo, r))
70 70 return frozenset()
71 71
72 72 def computeimpactable(repo):
73 73 """Everything impactable by mutable revision
74 74
75 75 The immutable filter still have some chance to get invalidated. This will
76 76 happen when:
77 77
78 78 - you garbage collect hidden changeset,
79 79 - public phase is moved backward,
80 80 - something is changed in the filtering (this could be fixed)
81 81
82 82 This filter out any mutable changeset and any public changeset that may be
83 83 impacted by something happening to a mutable revision.
84 84
85 85 This is achieved by filtered everything with a revision number egal or
86 86 higher than the first mutable changeset is filtered."""
87 87 assert not repo.changelog.filteredrevs
88 88 cl = repo.changelog
89 89 firstmutable = len(cl)
90 90 for roots in repo._phasecache.phaseroots[1:]:
91 91 if roots:
92 92 firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
93 93 # protect from nullrev root
94 94 firstmutable = max(0, firstmutable)
95 95 return frozenset(xrange(firstmutable, len(cl)))
96 96
97 97 # function to compute filtered set
98 98 filtertable = {'visible': computehidden,
99 99 'served': computeunserved,
100 100 'immutable': computemutable,
101 101 'base': computeimpactable}
102 102 ### Nearest subset relation
103 103 # Nearest subset of filter X is a filter Y so that:
104 104 # * Y is included in X,
105 105 # * X - Y is as small as possible.
106 106 # This create and ordering used for branchmap purpose.
107 107 # the ordering may be partial
108 108 subsettable = {None: 'visible',
109 109 'visible': 'served',
110 110 'served': 'immutable',
111 111 'immutable': 'base'}
112 112
113 113 def filterrevs(repo, filtername):
114 114 """returns set of filtered revision for this filter name"""
115 115 if filtername not in repo.filteredrevcache:
116 116 func = filtertable[filtername]
117 117 repo.filteredrevcache[filtername] = func(repo.unfiltered())
118 118 return repo.filteredrevcache[filtername]
119 119
120 120 class repoview(object):
121 121 """Provide a read/write view of a repo through a filtered changelog
122 122
123 123 This object is used to access a filtered version of a repository without
124 124 altering the original repository object itself. We can not alter the
125 125 original object for two main reasons:
126 126 - It prevents the use of a repo with multiple filters at the same time. In
127 127 particular when multiple threads are involved.
128 128 - It makes scope of the filtering harder to control.
129 129
130 130 This object behaves very closely to the original repository. All attribute
131 131 operations are done on the original repository:
132 132 - An access to `repoview.someattr` actually returns `repo.someattr`,
133 133 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
134 134 - A deletion of `repoview.someattr` actually drops `someattr`
135 135 from `repo.__dict__`.
136 136
137 137 The only exception is the `changelog` property. It is overridden to return
138 138 a (surface) copy of `repo.changelog` with some revisions filtered. The
139 139 `filtername` attribute of the view control the revisions that need to be
140 140 filtered. (the fact the changelog is copied is an implementation detail).
141 141
142 142 Unlike attributes, this object intercepts all method calls. This means that
143 143 all methods are run on the `repoview` object with the filtered `changelog`
144 144 property. For this purpose the simple `repoview` class must be mixed with
145 145 the actual class of the repository. This ensures that the resulting
146 146 `repoview` object have the very same methods than the repo object. This
147 147 leads to the property below.
148 148
149 149 repoview.method() --> repo.__class__.method(repoview)
150 150
151 151 The inheritance has to be done dynamically because `repo` can be of any
152 152 subclasses of `localrepo`. Eg: `bundlerepo` or `httprepo`.
153 153 """
154 154
155 155 def __init__(self, repo, filtername):
156 156 object.__setattr__(self, '_unfilteredrepo', repo)
157 157 object.__setattr__(self, 'filtername', filtername)
158 158 object.__setattr__(self, '_clcachekey', None)
159 159 object.__setattr__(self, '_clcache', None)
160 160
161 161 # not a cacheproperty on purpose we shall implement a proper cache later
162 162 @property
163 163 def changelog(self):
164 164 """return a filtered version of the changeset
165 165
166 166 this changelog must not be used for writing"""
167 167 # some cache may be implemented later
168 168 unfi = self._unfilteredrepo
169 169 unfichangelog = unfi.changelog
170 170 revs = filterrevs(unfi, self.filtername)
171 171 cl = self._clcache
172 172 newkey = (len(unfichangelog), unfichangelog.tip(), hash(revs))
173 173 if cl is not None:
174 174 # we need to check curkey too for some obscure reason.
175 175 # MQ test show a corruption of the underlying repo (in _clcache)
176 176 # without change in the cachekey.
177 177 oldfilter = cl.filteredrevs
178 178 try:
179 179 cl.filterrevs = () # disable filtering for tip
180 180 curkey = (len(cl), cl.tip(), hash(oldfilter))
181 181 finally:
182 182 cl.filteredrevs = oldfilter
183 183 if newkey != self._clcachekey or newkey != curkey:
184 184 cl = None
185 185 # could have been made None by the previous if
186 186 if cl is None:
187 187 cl = copy.copy(unfichangelog)
188 188 cl.filteredrevs = revs
189 189 object.__setattr__(self, '_clcache', cl)
190 190 object.__setattr__(self, '_clcachekey', newkey)
191 191 return cl
192 192
193 193 def unfiltered(self):
194 194 """Return an unfiltered version of a repo"""
195 195 return self._unfilteredrepo
196 196
197 197 def filtered(self, name):
198 198 """Return a filtered version of a repository"""
199 199 if name == self.filtername:
200 200 return self
201 201 return self.unfiltered().filtered(name)
202 202
203 203 # everything access are forwarded to the proxied repo
204 204 def __getattr__(self, attr):
205 205 return getattr(self._unfilteredrepo, attr)
206 206
207 207 def __setattr__(self, attr, value):
208 208 return setattr(self._unfilteredrepo, attr, value)
209 209
210 210 def __delattr__(self, attr):
211 211 return delattr(self._unfilteredrepo, attr)
212 212
213 213 # The `requirement` attribut is initialiazed during __init__. But
214 214 # __getattr__ won't be called as it also exists on the class. We need
215 215 # explicit forwarding to main repo here
216 216 @property
217 217 def requirements(self):
218 218 return self._unfilteredrepo.requirements
219 219
@@ -1,1932 +1,1931 b''
1 1 # revset.py - revision set queries for mercurial
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import re
9 9 import parser, util, error, discovery, hbisect, phases
10 10 import node
11 import bookmarks as bookmarksmod
12 11 import match as matchmod
13 12 from i18n import _
14 13 import encoding
15 14 import obsolete as obsmod
16 15 import repoview
17 16
18 17 def _revancestors(repo, revs, followfirst):
19 18 """Like revlog.ancestors(), but supports followfirst."""
20 19 cut = followfirst and 1 or None
21 20 cl = repo.changelog
22 21 visit = util.deque(revs)
23 22 seen = set([node.nullrev])
24 23 while visit:
25 24 for parent in cl.parentrevs(visit.popleft())[:cut]:
26 25 if parent not in seen:
27 26 visit.append(parent)
28 27 seen.add(parent)
29 28 yield parent
30 29
31 30 def _revdescendants(repo, revs, followfirst):
32 31 """Like revlog.descendants() but supports followfirst."""
33 32 cut = followfirst and 1 or None
34 33 cl = repo.changelog
35 34 first = min(revs)
36 35 nullrev = node.nullrev
37 36 if first == nullrev:
38 37 # Are there nodes with a null first parent and a non-null
39 38 # second one? Maybe. Do we care? Probably not.
40 39 for i in cl:
41 40 yield i
42 41 return
43 42
44 43 seen = set(revs)
45 44 for i in cl.revs(first + 1):
46 45 for x in cl.parentrevs(i)[:cut]:
47 46 if x != nullrev and x in seen:
48 47 seen.add(i)
49 48 yield i
50 49 break
51 50
52 51 def _revsbetween(repo, roots, heads):
53 52 """Return all paths between roots and heads, inclusive of both endpoint
54 53 sets."""
55 54 if not roots:
56 55 return []
57 56 parentrevs = repo.changelog.parentrevs
58 57 visit = heads[:]
59 58 reachable = set()
60 59 seen = {}
61 60 minroot = min(roots)
62 61 roots = set(roots)
63 62 # open-code the post-order traversal due to the tiny size of
64 63 # sys.getrecursionlimit()
65 64 while visit:
66 65 rev = visit.pop()
67 66 if rev in roots:
68 67 reachable.add(rev)
69 68 parents = parentrevs(rev)
70 69 seen[rev] = parents
71 70 for parent in parents:
72 71 if parent >= minroot and parent not in seen:
73 72 visit.append(parent)
74 73 if not reachable:
75 74 return []
76 75 for rev in sorted(seen):
77 76 for parent in seen[rev]:
78 77 if parent in reachable:
79 78 reachable.add(rev)
80 79 return sorted(reachable)
81 80
82 81 elements = {
83 82 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
84 83 "~": (18, None, ("ancestor", 18)),
85 84 "^": (18, None, ("parent", 18), ("parentpost", 18)),
86 85 "-": (5, ("negate", 19), ("minus", 5)),
87 86 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
88 87 ("dagrangepost", 17)),
89 88 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
90 89 ("dagrangepost", 17)),
91 90 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
92 91 "not": (10, ("not", 10)),
93 92 "!": (10, ("not", 10)),
94 93 "and": (5, None, ("and", 5)),
95 94 "&": (5, None, ("and", 5)),
96 95 "or": (4, None, ("or", 4)),
97 96 "|": (4, None, ("or", 4)),
98 97 "+": (4, None, ("or", 4)),
99 98 ",": (2, None, ("list", 2)),
100 99 ")": (0, None, None),
101 100 "symbol": (0, ("symbol",), None),
102 101 "string": (0, ("string",), None),
103 102 "end": (0, None, None),
104 103 }
105 104
106 105 keywords = set(['and', 'or', 'not'])
107 106
108 107 def tokenize(program):
109 108 '''
110 109 Parse a revset statement into a stream of tokens
111 110
112 111 Check that @ is a valid unquoted token character (issue3686):
113 112 >>> list(tokenize("@::"))
114 113 [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
115 114
116 115 '''
117 116
118 117 pos, l = 0, len(program)
119 118 while pos < l:
120 119 c = program[pos]
121 120 if c.isspace(): # skip inter-token whitespace
122 121 pass
123 122 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
124 123 yield ('::', None, pos)
125 124 pos += 1 # skip ahead
126 125 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
127 126 yield ('..', None, pos)
128 127 pos += 1 # skip ahead
129 128 elif c in "():,-|&+!~^": # handle simple operators
130 129 yield (c, None, pos)
131 130 elif (c in '"\'' or c == 'r' and
132 131 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
133 132 if c == 'r':
134 133 pos += 1
135 134 c = program[pos]
136 135 decode = lambda x: x
137 136 else:
138 137 decode = lambda x: x.decode('string-escape')
139 138 pos += 1
140 139 s = pos
141 140 while pos < l: # find closing quote
142 141 d = program[pos]
143 142 if d == '\\': # skip over escaped characters
144 143 pos += 2
145 144 continue
146 145 if d == c:
147 146 yield ('string', decode(program[s:pos]), s)
148 147 break
149 148 pos += 1
150 149 else:
151 150 raise error.ParseError(_("unterminated string"), s)
152 151 # gather up a symbol/keyword
153 152 elif c.isalnum() or c in '._@' or ord(c) > 127:
154 153 s = pos
155 154 pos += 1
156 155 while pos < l: # find end of symbol
157 156 d = program[pos]
158 157 if not (d.isalnum() or d in "._/@" or ord(d) > 127):
159 158 break
160 159 if d == '.' and program[pos - 1] == '.': # special case for ..
161 160 pos -= 1
162 161 break
163 162 pos += 1
164 163 sym = program[s:pos]
165 164 if sym in keywords: # operator keywords
166 165 yield (sym, None, s)
167 166 else:
168 167 yield ('symbol', sym, s)
169 168 pos -= 1
170 169 else:
171 170 raise error.ParseError(_("syntax error"), pos)
172 171 pos += 1
173 172 yield ('end', None, pos)
174 173
175 174 # helpers
176 175
177 176 def getstring(x, err):
178 177 if x and (x[0] == 'string' or x[0] == 'symbol'):
179 178 return x[1]
180 179 raise error.ParseError(err)
181 180
182 181 def getlist(x):
183 182 if not x:
184 183 return []
185 184 if x[0] == 'list':
186 185 return getlist(x[1]) + [x[2]]
187 186 return [x]
188 187
189 188 def getargs(x, min, max, err):
190 189 l = getlist(x)
191 190 if len(l) < min or (max >= 0 and len(l) > max):
192 191 raise error.ParseError(err)
193 192 return l
194 193
195 194 def getset(repo, subset, x):
196 195 if not x:
197 196 raise error.ParseError(_("missing argument"))
198 197 return methods[x[0]](repo, subset, *x[1:])
199 198
200 199 def _getrevsource(repo, r):
201 200 extra = repo[r].extra()
202 201 for label in ('source', 'transplant_source', 'rebase_source'):
203 202 if label in extra:
204 203 try:
205 204 return repo[extra[label]].rev()
206 205 except error.RepoLookupError:
207 206 pass
208 207 return None
209 208
210 209 # operator methods
211 210
212 211 def stringset(repo, subset, x):
213 212 x = repo[x].rev()
214 213 if x == -1 and len(subset) == len(repo):
215 214 return [-1]
216 215 if len(subset) == len(repo) or x in subset:
217 216 return [x]
218 217 return []
219 218
220 219 def symbolset(repo, subset, x):
221 220 if x in symbols:
222 221 raise error.ParseError(_("can't use %s here") % x)
223 222 return stringset(repo, subset, x)
224 223
225 224 def rangeset(repo, subset, x, y):
226 225 cl = repo.changelog
227 226 m = getset(repo, cl, x)
228 227 n = getset(repo, cl, y)
229 228
230 229 if not m or not n:
231 230 return []
232 231 m, n = m[0], n[-1]
233 232
234 233 if m < n:
235 234 r = range(m, n + 1)
236 235 else:
237 236 r = range(m, n - 1, -1)
238 237 s = set(subset)
239 238 return [x for x in r if x in s]
240 239
241 240 def dagrange(repo, subset, x, y):
242 241 if subset:
243 242 r = list(repo)
244 243 xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
245 244 s = set(subset)
246 245 return [r for r in xs if r in s]
247 246 return []
248 247
249 248 def andset(repo, subset, x, y):
250 249 return getset(repo, getset(repo, subset, x), y)
251 250
252 251 def orset(repo, subset, x, y):
253 252 xl = getset(repo, subset, x)
254 253 s = set(xl)
255 254 yl = getset(repo, [r for r in subset if r not in s], y)
256 255 return xl + yl
257 256
258 257 def notset(repo, subset, x):
259 258 s = set(getset(repo, subset, x))
260 259 return [r for r in subset if r not in s]
261 260
262 261 def listset(repo, subset, a, b):
263 262 raise error.ParseError(_("can't use a list in this context"))
264 263
265 264 def func(repo, subset, a, b):
266 265 if a[0] == 'symbol' and a[1] in symbols:
267 266 return symbols[a[1]](repo, subset, b)
268 267 raise error.ParseError(_("not a function: %s") % a[1])
269 268
270 269 # functions
271 270
272 271 def adds(repo, subset, x):
273 272 """``adds(pattern)``
274 273 Changesets that add a file matching pattern.
275 274 """
276 275 # i18n: "adds" is a keyword
277 276 pat = getstring(x, _("adds requires a pattern"))
278 277 return checkstatus(repo, subset, pat, 1)
279 278
280 279 def ancestor(repo, subset, x):
281 280 """``ancestor(single, single)``
282 281 Greatest common ancestor of the two changesets.
283 282 """
284 283 # i18n: "ancestor" is a keyword
285 284 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
286 285 r = list(repo)
287 286 a = getset(repo, r, l[0])
288 287 b = getset(repo, r, l[1])
289 288 if len(a) != 1 or len(b) != 1:
290 289 # i18n: "ancestor" is a keyword
291 290 raise error.ParseError(_("ancestor arguments must be single revisions"))
292 291 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
293 292
294 293 return [r for r in an if r in subset]
295 294
296 295 def _ancestors(repo, subset, x, followfirst=False):
297 296 args = getset(repo, list(repo), x)
298 297 if not args:
299 298 return []
300 299 s = set(_revancestors(repo, args, followfirst)) | set(args)
301 300 return [r for r in subset if r in s]
302 301
303 302 def ancestors(repo, subset, x):
304 303 """``ancestors(set)``
305 304 Changesets that are ancestors of a changeset in set.
306 305 """
307 306 return _ancestors(repo, subset, x)
308 307
309 308 def _firstancestors(repo, subset, x):
310 309 # ``_firstancestors(set)``
311 310 # Like ``ancestors(set)`` but follows only the first parents.
312 311 return _ancestors(repo, subset, x, followfirst=True)
313 312
314 313 def ancestorspec(repo, subset, x, n):
315 314 """``set~n``
316 315 Changesets that are the Nth ancestor (first parents only) of a changeset
317 316 in set.
318 317 """
319 318 try:
320 319 n = int(n[1])
321 320 except (TypeError, ValueError):
322 321 raise error.ParseError(_("~ expects a number"))
323 322 ps = set()
324 323 cl = repo.changelog
325 324 for r in getset(repo, cl, x):
326 325 for i in range(n):
327 326 r = cl.parentrevs(r)[0]
328 327 ps.add(r)
329 328 return [r for r in subset if r in ps]
330 329
331 330 def author(repo, subset, x):
332 331 """``author(string)``
333 332 Alias for ``user(string)``.
334 333 """
335 334 # i18n: "author" is a keyword
336 335 n = encoding.lower(getstring(x, _("author requires a string")))
337 336 kind, pattern, matcher = _substringmatcher(n)
338 337 return [r for r in subset if matcher(encoding.lower(repo[r].user()))]
339 338
340 339 def bisect(repo, subset, x):
341 340 """``bisect(string)``
342 341 Changesets marked in the specified bisect status:
343 342
344 343 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
345 344 - ``goods``, ``bads`` : csets topologically good/bad
346 345 - ``range`` : csets taking part in the bisection
347 346 - ``pruned`` : csets that are goods, bads or skipped
348 347 - ``untested`` : csets whose fate is yet unknown
349 348 - ``ignored`` : csets ignored due to DAG topology
350 349 - ``current`` : the cset currently being bisected
351 350 """
352 351 # i18n: "bisect" is a keyword
353 352 status = getstring(x, _("bisect requires a string")).lower()
354 353 state = set(hbisect.get(repo, status))
355 354 return [r for r in subset if r in state]
356 355
357 356 # Backward-compatibility
358 357 # - no help entry so that we do not advertise it any more
359 358 def bisected(repo, subset, x):
360 359 return bisect(repo, subset, x)
361 360
362 361 def bookmark(repo, subset, x):
363 362 """``bookmark([name])``
364 363 The named bookmark or all bookmarks.
365 364
366 365 If `name` starts with `re:`, the remainder of the name is treated as
367 366 a regular expression. To match a bookmark that actually starts with `re:`,
368 367 use the prefix `literal:`.
369 368 """
370 369 # i18n: "bookmark" is a keyword
371 370 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
372 371 if args:
373 372 bm = getstring(args[0],
374 373 # i18n: "bookmark" is a keyword
375 374 _('the argument to bookmark must be a string'))
376 375 kind, pattern, matcher = _stringmatcher(bm)
377 376 if kind == 'literal':
378 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
377 bmrev = repo._bookmarks.get(bm, None)
379 378 if not bmrev:
380 379 raise util.Abort(_("bookmark '%s' does not exist") % bm)
381 380 bmrev = repo[bmrev].rev()
382 381 return [r for r in subset if r == bmrev]
383 382 else:
384 383 matchrevs = set()
385 for name, bmrev in bookmarksmod.listbookmarks(repo).iteritems():
384 for name, bmrev in repo._bookmarks.iteritems():
386 385 if matcher(name):
387 386 matchrevs.add(bmrev)
388 387 if not matchrevs:
389 388 raise util.Abort(_("no bookmarks exist that match '%s'")
390 389 % pattern)
391 390 bmrevs = set()
392 391 for bmrev in matchrevs:
393 392 bmrevs.add(repo[bmrev].rev())
394 393 return [r for r in subset if r in bmrevs]
395 394
396 395 bms = set([repo[r].rev()
397 for r in bookmarksmod.listbookmarks(repo).values()])
396 for r in repo._bookmarks.values()])
398 397 return [r for r in subset if r in bms]
399 398
400 399 def branch(repo, subset, x):
401 400 """``branch(string or set)``
402 401 All changesets belonging to the given branch or the branches of the given
403 402 changesets.
404 403
405 404 If `string` starts with `re:`, the remainder of the name is treated as
406 405 a regular expression. To match a branch that actually starts with `re:`,
407 406 use the prefix `literal:`.
408 407 """
409 408 try:
410 409 b = getstring(x, '')
411 410 except error.ParseError:
412 411 # not a string, but another revspec, e.g. tip()
413 412 pass
414 413 else:
415 414 kind, pattern, matcher = _stringmatcher(b)
416 415 if kind == 'literal':
417 416 # note: falls through to the revspec case if no branch with
418 417 # this name exists
419 418 if pattern in repo.branchmap():
420 419 return [r for r in subset if matcher(repo[r].branch())]
421 420 else:
422 421 return [r for r in subset if matcher(repo[r].branch())]
423 422
424 423 s = getset(repo, list(repo), x)
425 424 b = set()
426 425 for r in s:
427 426 b.add(repo[r].branch())
428 427 s = set(s)
429 428 return [r for r in subset if r in s or repo[r].branch() in b]
430 429
431 430 def bumped(repo, subset, x):
432 431 """``bumped()``
433 432 Mutable changesets marked as successors of public changesets.
434 433
435 434 Only non-public and non-obsolete changesets can be `bumped`.
436 435 """
437 436 # i18n: "bumped" is a keyword
438 437 getargs(x, 0, 0, _("bumped takes no arguments"))
439 438 bumped = obsmod.getrevs(repo, 'bumped')
440 439 return [r for r in subset if r in bumped]
441 440
442 441 def bundle(repo, subset, x):
443 442 """``bundle()``
444 443 Changesets in the bundle.
445 444
446 445 Bundle must be specified by the -R option."""
447 446
448 447 try:
449 448 bundlerevs = repo.changelog.bundlerevs
450 449 except AttributeError:
451 450 raise util.Abort(_("no bundle provided - specify with -R"))
452 451 return [r for r in subset if r in bundlerevs]
453 452
454 453 def checkstatus(repo, subset, pat, field):
455 454 m = None
456 455 s = []
457 456 hasset = matchmod.patkind(pat) == 'set'
458 457 fname = None
459 458 for r in subset:
460 459 c = repo[r]
461 460 if not m or hasset:
462 461 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
463 462 if not m.anypats() and len(m.files()) == 1:
464 463 fname = m.files()[0]
465 464 if fname is not None:
466 465 if fname not in c.files():
467 466 continue
468 467 else:
469 468 for f in c.files():
470 469 if m(f):
471 470 break
472 471 else:
473 472 continue
474 473 files = repo.status(c.p1().node(), c.node())[field]
475 474 if fname is not None:
476 475 if fname in files:
477 476 s.append(r)
478 477 else:
479 478 for f in files:
480 479 if m(f):
481 480 s.append(r)
482 481 break
483 482 return s
484 483
485 484 def _children(repo, narrow, parentset):
486 485 cs = set()
487 486 if not parentset:
488 487 return cs
489 488 pr = repo.changelog.parentrevs
490 489 minrev = min(parentset)
491 490 for r in narrow:
492 491 if r <= minrev:
493 492 continue
494 493 for p in pr(r):
495 494 if p in parentset:
496 495 cs.add(r)
497 496 return cs
498 497
499 498 def children(repo, subset, x):
500 499 """``children(set)``
501 500 Child changesets of changesets in set.
502 501 """
503 502 s = set(getset(repo, list(repo), x))
504 503 cs = _children(repo, subset, s)
505 504 return [r for r in subset if r in cs]
506 505
507 506 def closed(repo, subset, x):
508 507 """``closed()``
509 508 Changeset is closed.
510 509 """
511 510 # i18n: "closed" is a keyword
512 511 getargs(x, 0, 0, _("closed takes no arguments"))
513 512 return [r for r in subset if repo[r].closesbranch()]
514 513
515 514 def contains(repo, subset, x):
516 515 """``contains(pattern)``
517 516 Revision contains a file matching pattern. See :hg:`help patterns`
518 517 for information about file patterns.
519 518 """
520 519 # i18n: "contains" is a keyword
521 520 pat = getstring(x, _("contains requires a pattern"))
522 521 m = None
523 522 s = []
524 523 if not matchmod.patkind(pat):
525 524 for r in subset:
526 525 if pat in repo[r]:
527 526 s.append(r)
528 527 else:
529 528 for r in subset:
530 529 c = repo[r]
531 530 if not m or matchmod.patkind(pat) == 'set':
532 531 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
533 532 for f in c.manifest():
534 533 if m(f):
535 534 s.append(r)
536 535 break
537 536 return s
538 537
539 538 def converted(repo, subset, x):
540 539 """``converted([id])``
541 540 Changesets converted from the given identifier in the old repository if
542 541 present, or all converted changesets if no identifier is specified.
543 542 """
544 543
545 544 # There is exactly no chance of resolving the revision, so do a simple
546 545 # string compare and hope for the best
547 546
548 547 rev = None
549 548 # i18n: "converted" is a keyword
550 549 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
551 550 if l:
552 551 # i18n: "converted" is a keyword
553 552 rev = getstring(l[0], _('converted requires a revision'))
554 553
555 554 def _matchvalue(r):
556 555 source = repo[r].extra().get('convert_revision', None)
557 556 return source is not None and (rev is None or source.startswith(rev))
558 557
559 558 return [r for r in subset if _matchvalue(r)]
560 559
561 560 def date(repo, subset, x):
562 561 """``date(interval)``
563 562 Changesets within the interval, see :hg:`help dates`.
564 563 """
565 564 # i18n: "date" is a keyword
566 565 ds = getstring(x, _("date requires a string"))
567 566 dm = util.matchdate(ds)
568 567 return [r for r in subset if dm(repo[r].date()[0])]
569 568
570 569 def desc(repo, subset, x):
571 570 """``desc(string)``
572 571 Search commit message for string. The match is case-insensitive.
573 572 """
574 573 # i18n: "desc" is a keyword
575 574 ds = encoding.lower(getstring(x, _("desc requires a string")))
576 575 l = []
577 576 for r in subset:
578 577 c = repo[r]
579 578 if ds in encoding.lower(c.description()):
580 579 l.append(r)
581 580 return l
582 581
583 582 def _descendants(repo, subset, x, followfirst=False):
584 583 args = getset(repo, list(repo), x)
585 584 if not args:
586 585 return []
587 586 s = set(_revdescendants(repo, args, followfirst)) | set(args)
588 587 return [r for r in subset if r in s]
589 588
590 589 def descendants(repo, subset, x):
591 590 """``descendants(set)``
592 591 Changesets which are descendants of changesets in set.
593 592 """
594 593 return _descendants(repo, subset, x)
595 594
596 595 def _firstdescendants(repo, subset, x):
597 596 # ``_firstdescendants(set)``
598 597 # Like ``descendants(set)`` but follows only the first parents.
599 598 return _descendants(repo, subset, x, followfirst=True)
600 599
601 600 def destination(repo, subset, x):
602 601 """``destination([set])``
603 602 Changesets that were created by a graft, transplant or rebase operation,
604 603 with the given revisions specified as the source. Omitting the optional set
605 604 is the same as passing all().
606 605 """
607 606 if x is not None:
608 607 args = set(getset(repo, list(repo), x))
609 608 else:
610 609 args = set(getall(repo, list(repo), x))
611 610
612 611 dests = set()
613 612
614 613 # subset contains all of the possible destinations that can be returned, so
615 614 # iterate over them and see if their source(s) were provided in the args.
616 615 # Even if the immediate src of r is not in the args, src's source (or
617 616 # further back) may be. Scanning back further than the immediate src allows
618 617 # transitive transplants and rebases to yield the same results as transitive
619 618 # grafts.
620 619 for r in subset:
621 620 src = _getrevsource(repo, r)
622 621 lineage = None
623 622
624 623 while src is not None:
625 624 if lineage is None:
626 625 lineage = list()
627 626
628 627 lineage.append(r)
629 628
630 629 # The visited lineage is a match if the current source is in the arg
631 630 # set. Since every candidate dest is visited by way of iterating
632 631 # subset, any dests further back in the lineage will be tested by a
633 632 # different iteration over subset. Likewise, if the src was already
634 633 # selected, the current lineage can be selected without going back
635 634 # further.
636 635 if src in args or src in dests:
637 636 dests.update(lineage)
638 637 break
639 638
640 639 r = src
641 640 src = _getrevsource(repo, r)
642 641
643 642 return [r for r in subset if r in dests]
644 643
645 644 def divergent(repo, subset, x):
646 645 """``divergent()``
647 646 Final successors of changesets with an alternative set of final successors.
648 647 """
649 648 # i18n: "divergent" is a keyword
650 649 getargs(x, 0, 0, _("divergent takes no arguments"))
651 650 divergent = obsmod.getrevs(repo, 'divergent')
652 651 return [r for r in subset if r in divergent]
653 652
654 653 def draft(repo, subset, x):
655 654 """``draft()``
656 655 Changeset in draft phase."""
657 656 # i18n: "draft" is a keyword
658 657 getargs(x, 0, 0, _("draft takes no arguments"))
659 658 pc = repo._phasecache
660 659 return [r for r in subset if pc.phase(repo, r) == phases.draft]
661 660
662 661 def extinct(repo, subset, x):
663 662 """``extinct()``
664 663 Obsolete changesets with obsolete descendants only.
665 664 """
666 665 # i18n: "extinct" is a keyword
667 666 getargs(x, 0, 0, _("extinct takes no arguments"))
668 667 extincts = obsmod.getrevs(repo, 'extinct')
669 668 return [r for r in subset if r in extincts]
670 669
671 670 def extra(repo, subset, x):
672 671 """``extra(label, [value])``
673 672 Changesets with the given label in the extra metadata, with the given
674 673 optional value.
675 674
676 675 If `value` starts with `re:`, the remainder of the value is treated as
677 676 a regular expression. To match a value that actually starts with `re:`,
678 677 use the prefix `literal:`.
679 678 """
680 679
681 680 # i18n: "extra" is a keyword
682 681 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
683 682 # i18n: "extra" is a keyword
684 683 label = getstring(l[0], _('first argument to extra must be a string'))
685 684 value = None
686 685
687 686 if len(l) > 1:
688 687 # i18n: "extra" is a keyword
689 688 value = getstring(l[1], _('second argument to extra must be a string'))
690 689 kind, value, matcher = _stringmatcher(value)
691 690
692 691 def _matchvalue(r):
693 692 extra = repo[r].extra()
694 693 return label in extra and (value is None or matcher(extra[label]))
695 694
696 695 return [r for r in subset if _matchvalue(r)]
697 696
698 697 def filelog(repo, subset, x):
699 698 """``filelog(pattern)``
700 699 Changesets connected to the specified filelog.
701 700
702 701 For performance reasons, ``filelog()`` does not show every changeset
703 702 that affects the requested file(s). See :hg:`help log` for details. For
704 703 a slower, more accurate result, use ``file()``.
705 704 """
706 705
707 706 # i18n: "filelog" is a keyword
708 707 pat = getstring(x, _("filelog requires a pattern"))
709 708 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
710 709 ctx=repo[None])
711 710 s = set()
712 711
713 712 if not matchmod.patkind(pat):
714 713 for f in m.files():
715 714 fl = repo.file(f)
716 715 for fr in fl:
717 716 s.add(fl.linkrev(fr))
718 717 else:
719 718 for f in repo[None]:
720 719 if m(f):
721 720 fl = repo.file(f)
722 721 for fr in fl:
723 722 s.add(fl.linkrev(fr))
724 723
725 724 return [r for r in subset if r in s]
726 725
727 726 def first(repo, subset, x):
728 727 """``first(set, [n])``
729 728 An alias for limit().
730 729 """
731 730 return limit(repo, subset, x)
732 731
733 732 def _follow(repo, subset, x, name, followfirst=False):
734 733 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
735 734 c = repo['.']
736 735 if l:
737 736 x = getstring(l[0], _("%s expected a filename") % name)
738 737 if x in c:
739 738 cx = c[x]
740 739 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
741 740 # include the revision responsible for the most recent version
742 741 s.add(cx.linkrev())
743 742 else:
744 743 return []
745 744 else:
746 745 s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
747 746
748 747 return [r for r in subset if r in s]
749 748
750 749 def follow(repo, subset, x):
751 750 """``follow([file])``
752 751 An alias for ``::.`` (ancestors of the working copy's first parent).
753 752 If a filename is specified, the history of the given file is followed,
754 753 including copies.
755 754 """
756 755 return _follow(repo, subset, x, 'follow')
757 756
758 757 def _followfirst(repo, subset, x):
759 758 # ``followfirst([file])``
760 759 # Like ``follow([file])`` but follows only the first parent of
761 760 # every revision or file revision.
762 761 return _follow(repo, subset, x, '_followfirst', followfirst=True)
763 762
764 763 def getall(repo, subset, x):
765 764 """``all()``
766 765 All changesets, the same as ``0:tip``.
767 766 """
768 767 # i18n: "all" is a keyword
769 768 getargs(x, 0, 0, _("all takes no arguments"))
770 769 return subset
771 770
772 771 def grep(repo, subset, x):
773 772 """``grep(regex)``
774 773 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
775 774 to ensure special escape characters are handled correctly. Unlike
776 775 ``keyword(string)``, the match is case-sensitive.
777 776 """
778 777 try:
779 778 # i18n: "grep" is a keyword
780 779 gr = re.compile(getstring(x, _("grep requires a string")))
781 780 except re.error, e:
782 781 raise error.ParseError(_('invalid match pattern: %s') % e)
783 782 l = []
784 783 for r in subset:
785 784 c = repo[r]
786 785 for e in c.files() + [c.user(), c.description()]:
787 786 if gr.search(e):
788 787 l.append(r)
789 788 break
790 789 return l
791 790
792 791 def _matchfiles(repo, subset, x):
793 792 # _matchfiles takes a revset list of prefixed arguments:
794 793 #
795 794 # [p:foo, i:bar, x:baz]
796 795 #
797 796 # builds a match object from them and filters subset. Allowed
798 797 # prefixes are 'p:' for regular patterns, 'i:' for include
799 798 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
800 799 # a revision identifier, or the empty string to reference the
801 800 # working directory, from which the match object is
802 801 # initialized. Use 'd:' to set the default matching mode, default
803 802 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
804 803
805 804 # i18n: "_matchfiles" is a keyword
806 805 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
807 806 pats, inc, exc = [], [], []
808 807 hasset = False
809 808 rev, default = None, None
810 809 for arg in l:
811 810 # i18n: "_matchfiles" is a keyword
812 811 s = getstring(arg, _("_matchfiles requires string arguments"))
813 812 prefix, value = s[:2], s[2:]
814 813 if prefix == 'p:':
815 814 pats.append(value)
816 815 elif prefix == 'i:':
817 816 inc.append(value)
818 817 elif prefix == 'x:':
819 818 exc.append(value)
820 819 elif prefix == 'r:':
821 820 if rev is not None:
822 821 # i18n: "_matchfiles" is a keyword
823 822 raise error.ParseError(_('_matchfiles expected at most one '
824 823 'revision'))
825 824 rev = value
826 825 elif prefix == 'd:':
827 826 if default is not None:
828 827 # i18n: "_matchfiles" is a keyword
829 828 raise error.ParseError(_('_matchfiles expected at most one '
830 829 'default mode'))
831 830 default = value
832 831 else:
833 832 # i18n: "_matchfiles" is a keyword
834 833 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
835 834 if not hasset and matchmod.patkind(value) == 'set':
836 835 hasset = True
837 836 if not default:
838 837 default = 'glob'
839 838 m = None
840 839 s = []
841 840 for r in subset:
842 841 c = repo[r]
843 842 if not m or (hasset and rev is None):
844 843 ctx = c
845 844 if rev is not None:
846 845 ctx = repo[rev or None]
847 846 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
848 847 exclude=exc, ctx=ctx, default=default)
849 848 for f in c.files():
850 849 if m(f):
851 850 s.append(r)
852 851 break
853 852 return s
854 853
855 854 def hasfile(repo, subset, x):
856 855 """``file(pattern)``
857 856 Changesets affecting files matched by pattern.
858 857
859 858 For a faster but less accurate result, consider using ``filelog()``
860 859 instead.
861 860 """
862 861 # i18n: "file" is a keyword
863 862 pat = getstring(x, _("file requires a pattern"))
864 863 return _matchfiles(repo, subset, ('string', 'p:' + pat))
865 864
866 865 def head(repo, subset, x):
867 866 """``head()``
868 867 Changeset is a named branch head.
869 868 """
870 869 # i18n: "head" is a keyword
871 870 getargs(x, 0, 0, _("head takes no arguments"))
872 871 hs = set()
873 872 for b, ls in repo.branchmap().iteritems():
874 873 hs.update(repo[h].rev() for h in ls)
875 874 return [r for r in subset if r in hs]
876 875
877 876 def heads(repo, subset, x):
878 877 """``heads(set)``
879 878 Members of set with no children in set.
880 879 """
881 880 s = getset(repo, subset, x)
882 881 ps = set(parents(repo, subset, x))
883 882 return [r for r in s if r not in ps]
884 883
885 884 def hidden(repo, subset, x):
886 885 """``hidden()``
887 886 Hidden changesets.
888 887 """
889 888 # i18n: "hidden" is a keyword
890 889 getargs(x, 0, 0, _("hidden takes no arguments"))
891 890 hiddenrevs = repoview.filterrevs(repo, 'visible')
892 891 return [r for r in subset if r in hiddenrevs]
893 892
894 893 def keyword(repo, subset, x):
895 894 """``keyword(string)``
896 895 Search commit message, user name, and names of changed files for
897 896 string. The match is case-insensitive.
898 897 """
899 898 # i18n: "keyword" is a keyword
900 899 kw = encoding.lower(getstring(x, _("keyword requires a string")))
901 900 l = []
902 901 for r in subset:
903 902 c = repo[r]
904 903 t = " ".join(c.files() + [c.user(), c.description()])
905 904 if kw in encoding.lower(t):
906 905 l.append(r)
907 906 return l
908 907
909 908 def limit(repo, subset, x):
910 909 """``limit(set, [n])``
911 910 First n members of set, defaulting to 1.
912 911 """
913 912 # i18n: "limit" is a keyword
914 913 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
915 914 try:
916 915 lim = 1
917 916 if len(l) == 2:
918 917 # i18n: "limit" is a keyword
919 918 lim = int(getstring(l[1], _("limit requires a number")))
920 919 except (TypeError, ValueError):
921 920 # i18n: "limit" is a keyword
922 921 raise error.ParseError(_("limit expects a number"))
923 922 ss = set(subset)
924 923 os = getset(repo, list(repo), l[0])[:lim]
925 924 return [r for r in os if r in ss]
926 925
927 926 def last(repo, subset, x):
928 927 """``last(set, [n])``
929 928 Last n members of set, defaulting to 1.
930 929 """
931 930 # i18n: "last" is a keyword
932 931 l = getargs(x, 1, 2, _("last requires one or two arguments"))
933 932 try:
934 933 lim = 1
935 934 if len(l) == 2:
936 935 # i18n: "last" is a keyword
937 936 lim = int(getstring(l[1], _("last requires a number")))
938 937 except (TypeError, ValueError):
939 938 # i18n: "last" is a keyword
940 939 raise error.ParseError(_("last expects a number"))
941 940 ss = set(subset)
942 941 os = getset(repo, list(repo), l[0])[-lim:]
943 942 return [r for r in os if r in ss]
944 943
945 944 def maxrev(repo, subset, x):
946 945 """``max(set)``
947 946 Changeset with highest revision number in set.
948 947 """
949 948 os = getset(repo, list(repo), x)
950 949 if os:
951 950 m = max(os)
952 951 if m in subset:
953 952 return [m]
954 953 return []
955 954
956 955 def merge(repo, subset, x):
957 956 """``merge()``
958 957 Changeset is a merge changeset.
959 958 """
960 959 # i18n: "merge" is a keyword
961 960 getargs(x, 0, 0, _("merge takes no arguments"))
962 961 cl = repo.changelog
963 962 return [r for r in subset if cl.parentrevs(r)[1] != -1]
964 963
965 964 def branchpoint(repo, subset, x):
966 965 """``branchpoint()``
967 966 Changesets with more than one child.
968 967 """
969 968 # i18n: "branchpoint" is a keyword
970 969 getargs(x, 0, 0, _("branchpoint takes no arguments"))
971 970 cl = repo.changelog
972 971 if not subset:
973 972 return []
974 973 baserev = min(subset)
975 974 parentscount = [0]*(len(repo) - baserev)
976 975 for r in cl.revs(start=baserev + 1):
977 976 for p in cl.parentrevs(r):
978 977 if p >= baserev:
979 978 parentscount[p - baserev] += 1
980 979 return [r for r in subset if (parentscount[r - baserev] > 1)]
981 980
982 981 def minrev(repo, subset, x):
983 982 """``min(set)``
984 983 Changeset with lowest revision number in set.
985 984 """
986 985 os = getset(repo, list(repo), x)
987 986 if os:
988 987 m = min(os)
989 988 if m in subset:
990 989 return [m]
991 990 return []
992 991
993 992 def modifies(repo, subset, x):
994 993 """``modifies(pattern)``
995 994 Changesets modifying files matched by pattern.
996 995 """
997 996 # i18n: "modifies" is a keyword
998 997 pat = getstring(x, _("modifies requires a pattern"))
999 998 return checkstatus(repo, subset, pat, 0)
1000 999
1001 1000 def node_(repo, subset, x):
1002 1001 """``id(string)``
1003 1002 Revision non-ambiguously specified by the given hex string prefix.
1004 1003 """
1005 1004 # i18n: "id" is a keyword
1006 1005 l = getargs(x, 1, 1, _("id requires one argument"))
1007 1006 # i18n: "id" is a keyword
1008 1007 n = getstring(l[0], _("id requires a string"))
1009 1008 if len(n) == 40:
1010 1009 rn = repo[n].rev()
1011 1010 else:
1012 1011 rn = None
1013 1012 pm = repo.changelog._partialmatch(n)
1014 1013 if pm is not None:
1015 1014 rn = repo.changelog.rev(pm)
1016 1015
1017 1016 return [r for r in subset if r == rn]
1018 1017
1019 1018 def obsolete(repo, subset, x):
1020 1019 """``obsolete()``
1021 1020 Mutable changeset with a newer version."""
1022 1021 # i18n: "obsolete" is a keyword
1023 1022 getargs(x, 0, 0, _("obsolete takes no arguments"))
1024 1023 obsoletes = obsmod.getrevs(repo, 'obsolete')
1025 1024 return [r for r in subset if r in obsoletes]
1026 1025
1027 1026 def origin(repo, subset, x):
1028 1027 """``origin([set])``
1029 1028 Changesets that were specified as a source for the grafts, transplants or
1030 1029 rebases that created the given revisions. Omitting the optional set is the
1031 1030 same as passing all(). If a changeset created by these operations is itself
1032 1031 specified as a source for one of these operations, only the source changeset
1033 1032 for the first operation is selected.
1034 1033 """
1035 1034 if x is not None:
1036 1035 args = set(getset(repo, list(repo), x))
1037 1036 else:
1038 1037 args = set(getall(repo, list(repo), x))
1039 1038
1040 1039 def _firstsrc(rev):
1041 1040 src = _getrevsource(repo, rev)
1042 1041 if src is None:
1043 1042 return None
1044 1043
1045 1044 while True:
1046 1045 prev = _getrevsource(repo, src)
1047 1046
1048 1047 if prev is None:
1049 1048 return src
1050 1049 src = prev
1051 1050
1052 1051 o = set([_firstsrc(r) for r in args])
1053 1052 return [r for r in subset if r in o]
1054 1053
1055 1054 def outgoing(repo, subset, x):
1056 1055 """``outgoing([path])``
1057 1056 Changesets not found in the specified destination repository, or the
1058 1057 default push location.
1059 1058 """
1060 1059 import hg # avoid start-up nasties
1061 1060 # i18n: "outgoing" is a keyword
1062 1061 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1063 1062 # i18n: "outgoing" is a keyword
1064 1063 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1065 1064 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1066 1065 dest, branches = hg.parseurl(dest)
1067 1066 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1068 1067 if revs:
1069 1068 revs = [repo.lookup(rev) for rev in revs]
1070 1069 other = hg.peer(repo, {}, dest)
1071 1070 repo.ui.pushbuffer()
1072 1071 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1073 1072 repo.ui.popbuffer()
1074 1073 cl = repo.changelog
1075 1074 o = set([cl.rev(r) for r in outgoing.missing])
1076 1075 return [r for r in subset if r in o]
1077 1076
1078 1077 def p1(repo, subset, x):
1079 1078 """``p1([set])``
1080 1079 First parent of changesets in set, or the working directory.
1081 1080 """
1082 1081 if x is None:
1083 1082 p = repo[x].p1().rev()
1084 1083 return [r for r in subset if r == p]
1085 1084
1086 1085 ps = set()
1087 1086 cl = repo.changelog
1088 1087 for r in getset(repo, list(repo), x):
1089 1088 ps.add(cl.parentrevs(r)[0])
1090 1089 return [r for r in subset if r in ps]
1091 1090
1092 1091 def p2(repo, subset, x):
1093 1092 """``p2([set])``
1094 1093 Second parent of changesets in set, or the working directory.
1095 1094 """
1096 1095 if x is None:
1097 1096 ps = repo[x].parents()
1098 1097 try:
1099 1098 p = ps[1].rev()
1100 1099 return [r for r in subset if r == p]
1101 1100 except IndexError:
1102 1101 return []
1103 1102
1104 1103 ps = set()
1105 1104 cl = repo.changelog
1106 1105 for r in getset(repo, list(repo), x):
1107 1106 ps.add(cl.parentrevs(r)[1])
1108 1107 return [r for r in subset if r in ps]
1109 1108
1110 1109 def parents(repo, subset, x):
1111 1110 """``parents([set])``
1112 1111 The set of all parents for all changesets in set, or the working directory.
1113 1112 """
1114 1113 if x is None:
1115 1114 ps = tuple(p.rev() for p in repo[x].parents())
1116 1115 return [r for r in subset if r in ps]
1117 1116
1118 1117 ps = set()
1119 1118 cl = repo.changelog
1120 1119 for r in getset(repo, list(repo), x):
1121 1120 ps.update(cl.parentrevs(r))
1122 1121 return [r for r in subset if r in ps]
1123 1122
1124 1123 def parentspec(repo, subset, x, n):
1125 1124 """``set^0``
1126 1125 The set.
1127 1126 ``set^1`` (or ``set^``), ``set^2``
1128 1127 First or second parent, respectively, of all changesets in set.
1129 1128 """
1130 1129 try:
1131 1130 n = int(n[1])
1132 1131 if n not in (0, 1, 2):
1133 1132 raise ValueError
1134 1133 except (TypeError, ValueError):
1135 1134 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1136 1135 ps = set()
1137 1136 cl = repo.changelog
1138 1137 for r in getset(repo, cl, x):
1139 1138 if n == 0:
1140 1139 ps.add(r)
1141 1140 elif n == 1:
1142 1141 ps.add(cl.parentrevs(r)[0])
1143 1142 elif n == 2:
1144 1143 parents = cl.parentrevs(r)
1145 1144 if len(parents) > 1:
1146 1145 ps.add(parents[1])
1147 1146 return [r for r in subset if r in ps]
1148 1147
1149 1148 def present(repo, subset, x):
1150 1149 """``present(set)``
1151 1150 An empty set, if any revision in set isn't found; otherwise,
1152 1151 all revisions in set.
1153 1152
1154 1153 If any of specified revisions is not present in the local repository,
1155 1154 the query is normally aborted. But this predicate allows the query
1156 1155 to continue even in such cases.
1157 1156 """
1158 1157 try:
1159 1158 return getset(repo, subset, x)
1160 1159 except error.RepoLookupError:
1161 1160 return []
1162 1161
1163 1162 def public(repo, subset, x):
1164 1163 """``public()``
1165 1164 Changeset in public phase."""
1166 1165 # i18n: "public" is a keyword
1167 1166 getargs(x, 0, 0, _("public takes no arguments"))
1168 1167 pc = repo._phasecache
1169 1168 return [r for r in subset if pc.phase(repo, r) == phases.public]
1170 1169
1171 1170 def remote(repo, subset, x):
1172 1171 """``remote([id [,path]])``
1173 1172 Local revision that corresponds to the given identifier in a
1174 1173 remote repository, if present. Here, the '.' identifier is a
1175 1174 synonym for the current local branch.
1176 1175 """
1177 1176
1178 1177 import hg # avoid start-up nasties
1179 1178 # i18n: "remote" is a keyword
1180 1179 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1181 1180
1182 1181 q = '.'
1183 1182 if len(l) > 0:
1184 1183 # i18n: "remote" is a keyword
1185 1184 q = getstring(l[0], _("remote requires a string id"))
1186 1185 if q == '.':
1187 1186 q = repo['.'].branch()
1188 1187
1189 1188 dest = ''
1190 1189 if len(l) > 1:
1191 1190 # i18n: "remote" is a keyword
1192 1191 dest = getstring(l[1], _("remote requires a repository path"))
1193 1192 dest = repo.ui.expandpath(dest or 'default')
1194 1193 dest, branches = hg.parseurl(dest)
1195 1194 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1196 1195 if revs:
1197 1196 revs = [repo.lookup(rev) for rev in revs]
1198 1197 other = hg.peer(repo, {}, dest)
1199 1198 n = other.lookup(q)
1200 1199 if n in repo:
1201 1200 r = repo[n].rev()
1202 1201 if r in subset:
1203 1202 return [r]
1204 1203 return []
1205 1204
1206 1205 def removes(repo, subset, x):
1207 1206 """``removes(pattern)``
1208 1207 Changesets which remove files matching pattern.
1209 1208 """
1210 1209 # i18n: "removes" is a keyword
1211 1210 pat = getstring(x, _("removes requires a pattern"))
1212 1211 return checkstatus(repo, subset, pat, 2)
1213 1212
1214 1213 def rev(repo, subset, x):
1215 1214 """``rev(number)``
1216 1215 Revision with the given numeric identifier.
1217 1216 """
1218 1217 # i18n: "rev" is a keyword
1219 1218 l = getargs(x, 1, 1, _("rev requires one argument"))
1220 1219 try:
1221 1220 # i18n: "rev" is a keyword
1222 1221 l = int(getstring(l[0], _("rev requires a number")))
1223 1222 except (TypeError, ValueError):
1224 1223 # i18n: "rev" is a keyword
1225 1224 raise error.ParseError(_("rev expects a number"))
1226 1225 return [r for r in subset if r == l]
1227 1226
1228 1227 def matching(repo, subset, x):
1229 1228 """``matching(revision [, field])``
1230 1229 Changesets in which a given set of fields match the set of fields in the
1231 1230 selected revision or set.
1232 1231
1233 1232 To match more than one field pass the list of fields to match separated
1234 1233 by spaces (e.g. ``author description``).
1235 1234
1236 1235 Valid fields are most regular revision fields and some special fields.
1237 1236
1238 1237 Regular revision fields are ``description``, ``author``, ``branch``,
1239 1238 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1240 1239 and ``diff``.
1241 1240 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1242 1241 contents of the revision. Two revisions matching their ``diff`` will
1243 1242 also match their ``files``.
1244 1243
1245 1244 Special fields are ``summary`` and ``metadata``:
1246 1245 ``summary`` matches the first line of the description.
1247 1246 ``metadata`` is equivalent to matching ``description user date``
1248 1247 (i.e. it matches the main metadata fields).
1249 1248
1250 1249 ``metadata`` is the default field which is used when no fields are
1251 1250 specified. You can match more than one field at a time.
1252 1251 """
1253 1252 # i18n: "matching" is a keyword
1254 1253 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1255 1254
1256 1255 revs = getset(repo, repo.changelog, l[0])
1257 1256
1258 1257 fieldlist = ['metadata']
1259 1258 if len(l) > 1:
1260 1259 fieldlist = getstring(l[1],
1261 1260 # i18n: "matching" is a keyword
1262 1261 _("matching requires a string "
1263 1262 "as its second argument")).split()
1264 1263
1265 1264 # Make sure that there are no repeated fields,
1266 1265 # expand the 'special' 'metadata' field type
1267 1266 # and check the 'files' whenever we check the 'diff'
1268 1267 fields = []
1269 1268 for field in fieldlist:
1270 1269 if field == 'metadata':
1271 1270 fields += ['user', 'description', 'date']
1272 1271 elif field == 'diff':
1273 1272 # a revision matching the diff must also match the files
1274 1273 # since matching the diff is very costly, make sure to
1275 1274 # also match the files first
1276 1275 fields += ['files', 'diff']
1277 1276 else:
1278 1277 if field == 'author':
1279 1278 field = 'user'
1280 1279 fields.append(field)
1281 1280 fields = set(fields)
1282 1281 if 'summary' in fields and 'description' in fields:
1283 1282 # If a revision matches its description it also matches its summary
1284 1283 fields.discard('summary')
1285 1284
1286 1285 # We may want to match more than one field
1287 1286 # Not all fields take the same amount of time to be matched
1288 1287 # Sort the selected fields in order of increasing matching cost
1289 1288 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1290 1289 'files', 'description', 'substate', 'diff']
1291 1290 def fieldkeyfunc(f):
1292 1291 try:
1293 1292 return fieldorder.index(f)
1294 1293 except ValueError:
1295 1294 # assume an unknown field is very costly
1296 1295 return len(fieldorder)
1297 1296 fields = list(fields)
1298 1297 fields.sort(key=fieldkeyfunc)
1299 1298
1300 1299 # Each field will be matched with its own "getfield" function
1301 1300 # which will be added to the getfieldfuncs array of functions
1302 1301 getfieldfuncs = []
1303 1302 _funcs = {
1304 1303 'user': lambda r: repo[r].user(),
1305 1304 'branch': lambda r: repo[r].branch(),
1306 1305 'date': lambda r: repo[r].date(),
1307 1306 'description': lambda r: repo[r].description(),
1308 1307 'files': lambda r: repo[r].files(),
1309 1308 'parents': lambda r: repo[r].parents(),
1310 1309 'phase': lambda r: repo[r].phase(),
1311 1310 'substate': lambda r: repo[r].substate,
1312 1311 'summary': lambda r: repo[r].description().splitlines()[0],
1313 1312 'diff': lambda r: list(repo[r].diff(git=True),)
1314 1313 }
1315 1314 for info in fields:
1316 1315 getfield = _funcs.get(info, None)
1317 1316 if getfield is None:
1318 1317 raise error.ParseError(
1319 1318 # i18n: "matching" is a keyword
1320 1319 _("unexpected field name passed to matching: %s") % info)
1321 1320 getfieldfuncs.append(getfield)
1322 1321 # convert the getfield array of functions into a "getinfo" function
1323 1322 # which returns an array of field values (or a single value if there
1324 1323 # is only one field to match)
1325 1324 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1326 1325
1327 1326 matches = set()
1328 1327 for rev in revs:
1329 1328 target = getinfo(rev)
1330 1329 for r in subset:
1331 1330 match = True
1332 1331 for n, f in enumerate(getfieldfuncs):
1333 1332 if target[n] != f(r):
1334 1333 match = False
1335 1334 break
1336 1335 if match:
1337 1336 matches.add(r)
1338 1337 return [r for r in subset if r in matches]
1339 1338
1340 1339 def reverse(repo, subset, x):
1341 1340 """``reverse(set)``
1342 1341 Reverse order of set.
1343 1342 """
1344 1343 l = getset(repo, subset, x)
1345 1344 if not isinstance(l, list):
1346 1345 l = list(l)
1347 1346 l.reverse()
1348 1347 return l
1349 1348
1350 1349 def roots(repo, subset, x):
1351 1350 """``roots(set)``
1352 1351 Changesets in set with no parent changeset in set.
1353 1352 """
1354 1353 s = set(getset(repo, repo.changelog, x))
1355 1354 subset = [r for r in subset if r in s]
1356 1355 cs = _children(repo, subset, s)
1357 1356 return [r for r in subset if r not in cs]
1358 1357
1359 1358 def secret(repo, subset, x):
1360 1359 """``secret()``
1361 1360 Changeset in secret phase."""
1362 1361 # i18n: "secret" is a keyword
1363 1362 getargs(x, 0, 0, _("secret takes no arguments"))
1364 1363 pc = repo._phasecache
1365 1364 return [r for r in subset if pc.phase(repo, r) == phases.secret]
1366 1365
1367 1366 def sort(repo, subset, x):
1368 1367 """``sort(set[, [-]key...])``
1369 1368 Sort set by keys. The default sort order is ascending, specify a key
1370 1369 as ``-key`` to sort in descending order.
1371 1370
1372 1371 The keys can be:
1373 1372
1374 1373 - ``rev`` for the revision number,
1375 1374 - ``branch`` for the branch name,
1376 1375 - ``desc`` for the commit message (description),
1377 1376 - ``user`` for user name (``author`` can be used as an alias),
1378 1377 - ``date`` for the commit date
1379 1378 """
1380 1379 # i18n: "sort" is a keyword
1381 1380 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1382 1381 keys = "rev"
1383 1382 if len(l) == 2:
1384 1383 # i18n: "sort" is a keyword
1385 1384 keys = getstring(l[1], _("sort spec must be a string"))
1386 1385
1387 1386 s = l[0]
1388 1387 keys = keys.split()
1389 1388 l = []
1390 1389 def invert(s):
1391 1390 return "".join(chr(255 - ord(c)) for c in s)
1392 1391 for r in getset(repo, subset, s):
1393 1392 c = repo[r]
1394 1393 e = []
1395 1394 for k in keys:
1396 1395 if k == 'rev':
1397 1396 e.append(r)
1398 1397 elif k == '-rev':
1399 1398 e.append(-r)
1400 1399 elif k == 'branch':
1401 1400 e.append(c.branch())
1402 1401 elif k == '-branch':
1403 1402 e.append(invert(c.branch()))
1404 1403 elif k == 'desc':
1405 1404 e.append(c.description())
1406 1405 elif k == '-desc':
1407 1406 e.append(invert(c.description()))
1408 1407 elif k in 'user author':
1409 1408 e.append(c.user())
1410 1409 elif k in '-user -author':
1411 1410 e.append(invert(c.user()))
1412 1411 elif k == 'date':
1413 1412 e.append(c.date()[0])
1414 1413 elif k == '-date':
1415 1414 e.append(-c.date()[0])
1416 1415 else:
1417 1416 raise error.ParseError(_("unknown sort key %r") % k)
1418 1417 e.append(r)
1419 1418 l.append(e)
1420 1419 l.sort()
1421 1420 return [e[-1] for e in l]
1422 1421
1423 1422 def _stringmatcher(pattern):
1424 1423 """
1425 1424 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1426 1425 returns the matcher name, pattern, and matcher function.
1427 1426 missing or unknown prefixes are treated as literal matches.
1428 1427
1429 1428 helper for tests:
1430 1429 >>> def test(pattern, *tests):
1431 1430 ... kind, pattern, matcher = _stringmatcher(pattern)
1432 1431 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1433 1432
1434 1433 exact matching (no prefix):
1435 1434 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1436 1435 ('literal', 'abcdefg', [False, False, True])
1437 1436
1438 1437 regex matching ('re:' prefix)
1439 1438 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1440 1439 ('re', 'a.+b', [False, False, True])
1441 1440
1442 1441 force exact matches ('literal:' prefix)
1443 1442 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1444 1443 ('literal', 're:foobar', [False, True])
1445 1444
1446 1445 unknown prefixes are ignored and treated as literals
1447 1446 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1448 1447 ('literal', 'foo:bar', [False, False, True])
1449 1448 """
1450 1449 if pattern.startswith('re:'):
1451 1450 pattern = pattern[3:]
1452 1451 try:
1453 1452 regex = re.compile(pattern)
1454 1453 except re.error, e:
1455 1454 raise error.ParseError(_('invalid regular expression: %s')
1456 1455 % e)
1457 1456 return 're', pattern, regex.search
1458 1457 elif pattern.startswith('literal:'):
1459 1458 pattern = pattern[8:]
1460 1459 return 'literal', pattern, pattern.__eq__
1461 1460
1462 1461 def _substringmatcher(pattern):
1463 1462 kind, pattern, matcher = _stringmatcher(pattern)
1464 1463 if kind == 'literal':
1465 1464 matcher = lambda s: pattern in s
1466 1465 return kind, pattern, matcher
1467 1466
1468 1467 def tag(repo, subset, x):
1469 1468 """``tag([name])``
1470 1469 The specified tag by name, or all tagged revisions if no name is given.
1471 1470 """
1472 1471 # i18n: "tag" is a keyword
1473 1472 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1474 1473 cl = repo.changelog
1475 1474 if args:
1476 1475 pattern = getstring(args[0],
1477 1476 # i18n: "tag" is a keyword
1478 1477 _('the argument to tag must be a string'))
1479 1478 kind, pattern, matcher = _stringmatcher(pattern)
1480 1479 if kind == 'literal':
1481 1480 # avoid resolving all tags
1482 1481 tn = repo._tagscache.tags.get(pattern, None)
1483 1482 if tn is None:
1484 1483 raise util.Abort(_("tag '%s' does not exist") % pattern)
1485 1484 s = set([repo[tn].rev()])
1486 1485 else:
1487 1486 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1488 1487 if not s:
1489 1488 raise util.Abort(_("no tags exist that match '%s'") % pattern)
1490 1489 else:
1491 1490 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1492 1491 return [r for r in subset if r in s]
1493 1492
1494 1493 def tagged(repo, subset, x):
1495 1494 return tag(repo, subset, x)
1496 1495
1497 1496 def unstable(repo, subset, x):
1498 1497 """``unstable()``
1499 1498 Non-obsolete changesets with obsolete ancestors.
1500 1499 """
1501 1500 # i18n: "unstable" is a keyword
1502 1501 getargs(x, 0, 0, _("unstable takes no arguments"))
1503 1502 unstables = obsmod.getrevs(repo, 'unstable')
1504 1503 return [r for r in subset if r in unstables]
1505 1504
1506 1505
1507 1506 def user(repo, subset, x):
1508 1507 """``user(string)``
1509 1508 User name contains string. The match is case-insensitive.
1510 1509
1511 1510 If `string` starts with `re:`, the remainder of the string is treated as
1512 1511 a regular expression. To match a user that actually contains `re:`, use
1513 1512 the prefix `literal:`.
1514 1513 """
1515 1514 return author(repo, subset, x)
1516 1515
1517 1516 # for internal use
1518 1517 def _list(repo, subset, x):
1519 1518 s = getstring(x, "internal error")
1520 1519 if not s:
1521 1520 return []
1522 1521 if not isinstance(subset, set):
1523 1522 subset = set(subset)
1524 1523 ls = [repo[r].rev() for r in s.split('\0')]
1525 1524 return [r for r in ls if r in subset]
1526 1525
1527 1526 symbols = {
1528 1527 "adds": adds,
1529 1528 "all": getall,
1530 1529 "ancestor": ancestor,
1531 1530 "ancestors": ancestors,
1532 1531 "_firstancestors": _firstancestors,
1533 1532 "author": author,
1534 1533 "bisect": bisect,
1535 1534 "bisected": bisected,
1536 1535 "bookmark": bookmark,
1537 1536 "branch": branch,
1538 1537 "branchpoint": branchpoint,
1539 1538 "bumped": bumped,
1540 1539 "bundle": bundle,
1541 1540 "children": children,
1542 1541 "closed": closed,
1543 1542 "contains": contains,
1544 1543 "converted": converted,
1545 1544 "date": date,
1546 1545 "desc": desc,
1547 1546 "descendants": descendants,
1548 1547 "_firstdescendants": _firstdescendants,
1549 1548 "destination": destination,
1550 1549 "divergent": divergent,
1551 1550 "draft": draft,
1552 1551 "extinct": extinct,
1553 1552 "extra": extra,
1554 1553 "file": hasfile,
1555 1554 "filelog": filelog,
1556 1555 "first": first,
1557 1556 "follow": follow,
1558 1557 "_followfirst": _followfirst,
1559 1558 "grep": grep,
1560 1559 "head": head,
1561 1560 "heads": heads,
1562 1561 "hidden": hidden,
1563 1562 "id": node_,
1564 1563 "keyword": keyword,
1565 1564 "last": last,
1566 1565 "limit": limit,
1567 1566 "_matchfiles": _matchfiles,
1568 1567 "max": maxrev,
1569 1568 "merge": merge,
1570 1569 "min": minrev,
1571 1570 "modifies": modifies,
1572 1571 "obsolete": obsolete,
1573 1572 "origin": origin,
1574 1573 "outgoing": outgoing,
1575 1574 "p1": p1,
1576 1575 "p2": p2,
1577 1576 "parents": parents,
1578 1577 "present": present,
1579 1578 "public": public,
1580 1579 "remote": remote,
1581 1580 "removes": removes,
1582 1581 "rev": rev,
1583 1582 "reverse": reverse,
1584 1583 "roots": roots,
1585 1584 "sort": sort,
1586 1585 "secret": secret,
1587 1586 "matching": matching,
1588 1587 "tag": tag,
1589 1588 "tagged": tagged,
1590 1589 "user": user,
1591 1590 "unstable": unstable,
1592 1591 "_list": _list,
1593 1592 }
1594 1593
1595 1594 methods = {
1596 1595 "range": rangeset,
1597 1596 "dagrange": dagrange,
1598 1597 "string": stringset,
1599 1598 "symbol": symbolset,
1600 1599 "and": andset,
1601 1600 "or": orset,
1602 1601 "not": notset,
1603 1602 "list": listset,
1604 1603 "func": func,
1605 1604 "ancestor": ancestorspec,
1606 1605 "parent": parentspec,
1607 1606 "parentpost": p1,
1608 1607 }
1609 1608
1610 1609 def optimize(x, small):
1611 1610 if x is None:
1612 1611 return 0, x
1613 1612
1614 1613 smallbonus = 1
1615 1614 if small:
1616 1615 smallbonus = .5
1617 1616
1618 1617 op = x[0]
1619 1618 if op == 'minus':
1620 1619 return optimize(('and', x[1], ('not', x[2])), small)
1621 1620 elif op == 'dagrangepre':
1622 1621 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1623 1622 elif op == 'dagrangepost':
1624 1623 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1625 1624 elif op == 'rangepre':
1626 1625 return optimize(('range', ('string', '0'), x[1]), small)
1627 1626 elif op == 'rangepost':
1628 1627 return optimize(('range', x[1], ('string', 'tip')), small)
1629 1628 elif op == 'negate':
1630 1629 return optimize(('string',
1631 1630 '-' + getstring(x[1], _("can't negate that"))), small)
1632 1631 elif op in 'string symbol negate':
1633 1632 return smallbonus, x # single revisions are small
1634 1633 elif op == 'and':
1635 1634 wa, ta = optimize(x[1], True)
1636 1635 wb, tb = optimize(x[2], True)
1637 1636 w = min(wa, wb)
1638 1637 if wa > wb:
1639 1638 return w, (op, tb, ta)
1640 1639 return w, (op, ta, tb)
1641 1640 elif op == 'or':
1642 1641 wa, ta = optimize(x[1], False)
1643 1642 wb, tb = optimize(x[2], False)
1644 1643 if wb < wa:
1645 1644 wb, wa = wa, wb
1646 1645 return max(wa, wb), (op, ta, tb)
1647 1646 elif op == 'not':
1648 1647 o = optimize(x[1], not small)
1649 1648 return o[0], (op, o[1])
1650 1649 elif op == 'parentpost':
1651 1650 o = optimize(x[1], small)
1652 1651 return o[0], (op, o[1])
1653 1652 elif op == 'group':
1654 1653 return optimize(x[1], small)
1655 1654 elif op in 'dagrange range list parent ancestorspec':
1656 1655 if op == 'parent':
1657 1656 # x^:y means (x^) : y, not x ^ (:y)
1658 1657 post = ('parentpost', x[1])
1659 1658 if x[2][0] == 'dagrangepre':
1660 1659 return optimize(('dagrange', post, x[2][1]), small)
1661 1660 elif x[2][0] == 'rangepre':
1662 1661 return optimize(('range', post, x[2][1]), small)
1663 1662
1664 1663 wa, ta = optimize(x[1], small)
1665 1664 wb, tb = optimize(x[2], small)
1666 1665 return wa + wb, (op, ta, tb)
1667 1666 elif op == 'func':
1668 1667 f = getstring(x[1], _("not a symbol"))
1669 1668 wa, ta = optimize(x[2], small)
1670 1669 if f in ("author branch closed date desc file grep keyword "
1671 1670 "outgoing user"):
1672 1671 w = 10 # slow
1673 1672 elif f in "modifies adds removes":
1674 1673 w = 30 # slower
1675 1674 elif f == "contains":
1676 1675 w = 100 # very slow
1677 1676 elif f == "ancestor":
1678 1677 w = 1 * smallbonus
1679 1678 elif f in "reverse limit first":
1680 1679 w = 0
1681 1680 elif f in "sort":
1682 1681 w = 10 # assume most sorts look at changelog
1683 1682 else:
1684 1683 w = 1
1685 1684 return w + wa, (op, x[1], ta)
1686 1685 return 1, x
1687 1686
1688 1687 _aliasarg = ('func', ('symbol', '_aliasarg'))
1689 1688 def _getaliasarg(tree):
1690 1689 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1691 1690 return X, None otherwise.
1692 1691 """
1693 1692 if (len(tree) == 3 and tree[:2] == _aliasarg
1694 1693 and tree[2][0] == 'string'):
1695 1694 return tree[2][1]
1696 1695 return None
1697 1696
1698 1697 def _checkaliasarg(tree, known=None):
1699 1698 """Check tree contains no _aliasarg construct or only ones which
1700 1699 value is in known. Used to avoid alias placeholders injection.
1701 1700 """
1702 1701 if isinstance(tree, tuple):
1703 1702 arg = _getaliasarg(tree)
1704 1703 if arg is not None and (not known or arg not in known):
1705 1704 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1706 1705 for t in tree:
1707 1706 _checkaliasarg(t, known)
1708 1707
1709 1708 class revsetalias(object):
1710 1709 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1711 1710 args = None
1712 1711
1713 1712 def __init__(self, name, value):
1714 1713 '''Aliases like:
1715 1714
1716 1715 h = heads(default)
1717 1716 b($1) = ancestors($1) - ancestors(default)
1718 1717 '''
1719 1718 m = self.funcre.search(name)
1720 1719 if m:
1721 1720 self.name = m.group(1)
1722 1721 self.tree = ('func', ('symbol', m.group(1)))
1723 1722 self.args = [x.strip() for x in m.group(2).split(',')]
1724 1723 for arg in self.args:
1725 1724 # _aliasarg() is an unknown symbol only used separate
1726 1725 # alias argument placeholders from regular strings.
1727 1726 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1728 1727 else:
1729 1728 self.name = name
1730 1729 self.tree = ('symbol', name)
1731 1730
1732 1731 self.replacement, pos = parse(value)
1733 1732 if pos != len(value):
1734 1733 raise error.ParseError(_('invalid token'), pos)
1735 1734 # Check for placeholder injection
1736 1735 _checkaliasarg(self.replacement, self.args)
1737 1736
1738 1737 def _getalias(aliases, tree):
1739 1738 """If tree looks like an unexpanded alias, return it. Return None
1740 1739 otherwise.
1741 1740 """
1742 1741 if isinstance(tree, tuple) and tree:
1743 1742 if tree[0] == 'symbol' and len(tree) == 2:
1744 1743 name = tree[1]
1745 1744 alias = aliases.get(name)
1746 1745 if alias and alias.args is None and alias.tree == tree:
1747 1746 return alias
1748 1747 if tree[0] == 'func' and len(tree) > 1:
1749 1748 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1750 1749 name = tree[1][1]
1751 1750 alias = aliases.get(name)
1752 1751 if alias and alias.args is not None and alias.tree == tree[:2]:
1753 1752 return alias
1754 1753 return None
1755 1754
1756 1755 def _expandargs(tree, args):
1757 1756 """Replace _aliasarg instances with the substitution value of the
1758 1757 same name in args, recursively.
1759 1758 """
1760 1759 if not tree or not isinstance(tree, tuple):
1761 1760 return tree
1762 1761 arg = _getaliasarg(tree)
1763 1762 if arg is not None:
1764 1763 return args[arg]
1765 1764 return tuple(_expandargs(t, args) for t in tree)
1766 1765
1767 1766 def _expandaliases(aliases, tree, expanding, cache):
1768 1767 """Expand aliases in tree, recursively.
1769 1768
1770 1769 'aliases' is a dictionary mapping user defined aliases to
1771 1770 revsetalias objects.
1772 1771 """
1773 1772 if not isinstance(tree, tuple):
1774 1773 # Do not expand raw strings
1775 1774 return tree
1776 1775 alias = _getalias(aliases, tree)
1777 1776 if alias is not None:
1778 1777 if alias in expanding:
1779 1778 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1780 1779 'detected') % alias.name)
1781 1780 expanding.append(alias)
1782 1781 if alias.name not in cache:
1783 1782 cache[alias.name] = _expandaliases(aliases, alias.replacement,
1784 1783 expanding, cache)
1785 1784 result = cache[alias.name]
1786 1785 expanding.pop()
1787 1786 if alias.args is not None:
1788 1787 l = getlist(tree[2])
1789 1788 if len(l) != len(alias.args):
1790 1789 raise error.ParseError(
1791 1790 _('invalid number of arguments: %s') % len(l))
1792 1791 l = [_expandaliases(aliases, a, [], cache) for a in l]
1793 1792 result = _expandargs(result, dict(zip(alias.args, l)))
1794 1793 else:
1795 1794 result = tuple(_expandaliases(aliases, t, expanding, cache)
1796 1795 for t in tree)
1797 1796 return result
1798 1797
1799 1798 def findaliases(ui, tree):
1800 1799 _checkaliasarg(tree)
1801 1800 aliases = {}
1802 1801 for k, v in ui.configitems('revsetalias'):
1803 1802 alias = revsetalias(k, v)
1804 1803 aliases[alias.name] = alias
1805 1804 return _expandaliases(aliases, tree, [], {})
1806 1805
1807 1806 parse = parser.parser(tokenize, elements).parse
1808 1807
1809 1808 def match(ui, spec):
1810 1809 if not spec:
1811 1810 raise error.ParseError(_("empty query"))
1812 1811 tree, pos = parse(spec)
1813 1812 if (pos != len(spec)):
1814 1813 raise error.ParseError(_("invalid token"), pos)
1815 1814 if ui:
1816 1815 tree = findaliases(ui, tree)
1817 1816 weight, tree = optimize(tree, True)
1818 1817 def mfunc(repo, subset):
1819 1818 return getset(repo, subset, tree)
1820 1819 return mfunc
1821 1820
1822 1821 def formatspec(expr, *args):
1823 1822 '''
1824 1823 This is a convenience function for using revsets internally, and
1825 1824 escapes arguments appropriately. Aliases are intentionally ignored
1826 1825 so that intended expression behavior isn't accidentally subverted.
1827 1826
1828 1827 Supported arguments:
1829 1828
1830 1829 %r = revset expression, parenthesized
1831 1830 %d = int(arg), no quoting
1832 1831 %s = string(arg), escaped and single-quoted
1833 1832 %b = arg.branch(), escaped and single-quoted
1834 1833 %n = hex(arg), single-quoted
1835 1834 %% = a literal '%'
1836 1835
1837 1836 Prefixing the type with 'l' specifies a parenthesized list of that type.
1838 1837
1839 1838 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1840 1839 '(10 or 11):: and ((this()) or (that()))'
1841 1840 >>> formatspec('%d:: and not %d::', 10, 20)
1842 1841 '10:: and not 20::'
1843 1842 >>> formatspec('%ld or %ld', [], [1])
1844 1843 "_list('') or 1"
1845 1844 >>> formatspec('keyword(%s)', 'foo\\xe9')
1846 1845 "keyword('foo\\\\xe9')"
1847 1846 >>> b = lambda: 'default'
1848 1847 >>> b.branch = b
1849 1848 >>> formatspec('branch(%b)', b)
1850 1849 "branch('default')"
1851 1850 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1852 1851 "root(_list('a\\x00b\\x00c\\x00d'))"
1853 1852 '''
1854 1853
1855 1854 def quote(s):
1856 1855 return repr(str(s))
1857 1856
1858 1857 def argtype(c, arg):
1859 1858 if c == 'd':
1860 1859 return str(int(arg))
1861 1860 elif c == 's':
1862 1861 return quote(arg)
1863 1862 elif c == 'r':
1864 1863 parse(arg) # make sure syntax errors are confined
1865 1864 return '(%s)' % arg
1866 1865 elif c == 'n':
1867 1866 return quote(node.hex(arg))
1868 1867 elif c == 'b':
1869 1868 return quote(arg.branch())
1870 1869
1871 1870 def listexp(s, t):
1872 1871 l = len(s)
1873 1872 if l == 0:
1874 1873 return "_list('')"
1875 1874 elif l == 1:
1876 1875 return argtype(t, s[0])
1877 1876 elif t == 'd':
1878 1877 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1879 1878 elif t == 's':
1880 1879 return "_list('%s')" % "\0".join(s)
1881 1880 elif t == 'n':
1882 1881 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1883 1882 elif t == 'b':
1884 1883 return "_list('%s')" % "\0".join(a.branch() for a in s)
1885 1884
1886 1885 m = l // 2
1887 1886 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1888 1887
1889 1888 ret = ''
1890 1889 pos = 0
1891 1890 arg = 0
1892 1891 while pos < len(expr):
1893 1892 c = expr[pos]
1894 1893 if c == '%':
1895 1894 pos += 1
1896 1895 d = expr[pos]
1897 1896 if d == '%':
1898 1897 ret += d
1899 1898 elif d in 'dsnbr':
1900 1899 ret += argtype(d, args[arg])
1901 1900 arg += 1
1902 1901 elif d == 'l':
1903 1902 # a list of some type
1904 1903 pos += 1
1905 1904 d = expr[pos]
1906 1905 ret += listexp(list(args[arg]), d)
1907 1906 arg += 1
1908 1907 else:
1909 1908 raise util.Abort('unexpected revspec format character %s' % d)
1910 1909 else:
1911 1910 ret += c
1912 1911 pos += 1
1913 1912
1914 1913 return ret
1915 1914
1916 1915 def prettyformat(tree):
1917 1916 def _prettyformat(tree, level, lines):
1918 1917 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1919 1918 lines.append((level, str(tree)))
1920 1919 else:
1921 1920 lines.append((level, '(%s' % tree[0]))
1922 1921 for s in tree[1:]:
1923 1922 _prettyformat(s, level + 1, lines)
1924 1923 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1925 1924
1926 1925 lines = []
1927 1926 _prettyformat(tree, 0, lines)
1928 1927 output = '\n'.join((' '*l + s) for l, s in lines)
1929 1928 return output
1930 1929
1931 1930 # tell hggettext to extract docstrings from these functions:
1932 1931 i18nfunctions = symbols.values()
@@ -1,383 +1,395 b''
1 1 $ "$TESTDIR/hghave" serve || exit 80
2 2
3 3 $ cat << EOF >> $HGRCPATH
4 4 > [ui]
5 5 > logtemplate={rev}:{node|short} {desc|firstline}
6 6 > [phases]
7 7 > publish=False
8 8 > [extensions]
9 9 > EOF
10 10 $ cat > obs.py << EOF
11 11 > import mercurial.obsolete
12 12 > mercurial.obsolete._enabled = True
13 13 > EOF
14 14 $ echo "obs=${TESTTMP}/obs.py" >> $HGRCPATH
15 15
16 16 initialize
17 17
18 18 $ hg init a
19 19 $ cd a
20 20 $ echo 'test' > test
21 21 $ hg commit -Am'test'
22 22 adding test
23 23
24 24 set bookmarks
25 25
26 26 $ hg bookmark X
27 27 $ hg bookmark Y
28 28 $ hg bookmark Z
29 29
30 30 import bookmark by name
31 31
32 32 $ hg init ../b
33 33 $ cd ../b
34 34 $ hg book Y
35 35 $ hg book
36 36 * Y -1:000000000000
37 37 $ hg pull ../a
38 38 pulling from ../a
39 39 requesting all changes
40 40 adding changesets
41 41 adding manifests
42 42 adding file changes
43 43 added 1 changesets with 1 changes to 1 files
44 44 adding remote bookmark X
45 45 updating bookmark Y
46 46 adding remote bookmark Z
47 47 (run 'hg update' to get a working copy)
48 48 $ hg bookmarks
49 49 X 0:4e3505fd9583
50 50 * Y 0:4e3505fd9583
51 51 Z 0:4e3505fd9583
52 52 $ hg debugpushkey ../a namespaces
53 53 bookmarks
54 54 namespaces
55 55 obsolete
56 56 phases
57 57 $ hg debugpushkey ../a bookmarks
58 58 X 4e3505fd95835d721066b76e75dbb8cc554d7f77
59 59 Y 4e3505fd95835d721066b76e75dbb8cc554d7f77
60 60 Z 4e3505fd95835d721066b76e75dbb8cc554d7f77
61 61 $ hg pull -B X ../a
62 62 pulling from ../a
63 63 no changes found
64 64 importing bookmark X
65 65 $ hg bookmark
66 66 X 0:4e3505fd9583
67 67 * Y 0:4e3505fd9583
68 68 Z 0:4e3505fd9583
69 69
70 70 export bookmark by name
71 71
72 72 $ hg bookmark W
73 73 $ hg bookmark foo
74 74 $ hg bookmark foobar
75 75 $ hg push -B W ../a
76 76 pushing to ../a
77 77 searching for changes
78 78 no changes found
79 79 exporting bookmark W
80 80 [1]
81 81 $ hg -R ../a bookmarks
82 82 W -1:000000000000
83 83 X 0:4e3505fd9583
84 84 Y 0:4e3505fd9583
85 85 * Z 0:4e3505fd9583
86 86
87 87 delete a remote bookmark
88 88
89 89 $ hg book -d W
90 90 $ hg push -B W ../a
91 91 pushing to ../a
92 92 searching for changes
93 93 no changes found
94 94 deleting remote bookmark W
95 95 [1]
96 96
97 97 push/pull name that doesn't exist
98 98
99 99 $ hg push -B badname ../a
100 100 pushing to ../a
101 101 searching for changes
102 102 no changes found
103 103 bookmark badname does not exist on the local or remote repository!
104 104 [2]
105 105 $ hg pull -B anotherbadname ../a
106 106 pulling from ../a
107 107 abort: remote bookmark anotherbadname not found!
108 108 [255]
109 109
110 110 divergent bookmarks
111 111
112 112 $ cd ../a
113 113 $ echo c1 > f1
114 114 $ hg ci -Am1
115 115 adding f1
116 116 $ hg book -f @
117 117 $ hg book -f X
118 118 $ hg book
119 119 @ 1:0d2164f0ce0d
120 120 * X 1:0d2164f0ce0d
121 121 Y 0:4e3505fd9583
122 122 Z 1:0d2164f0ce0d
123 123
124 124 $ cd ../b
125 125 $ hg up
126 126 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
127 127 updating bookmark foobar
128 128 $ echo c2 > f2
129 129 $ hg ci -Am2
130 130 adding f2
131 131 $ hg book -if @
132 132 $ hg book -if X
133 133 $ hg book
134 134 @ 1:9b140be10808
135 135 X 1:9b140be10808
136 136 Y 0:4e3505fd9583
137 137 Z 0:4e3505fd9583
138 138 foo -1:000000000000
139 139 * foobar 1:9b140be10808
140 140
141 141 $ hg pull --config paths.foo=../a foo
142 142 pulling from $TESTTMP/a (glob)
143 143 searching for changes
144 144 adding changesets
145 145 adding manifests
146 146 adding file changes
147 147 added 1 changesets with 1 changes to 1 files (+1 heads)
148 148 divergent bookmark @ stored as @foo
149 149 divergent bookmark X stored as X@foo
150 150 updating bookmark Z
151 151 (run 'hg heads' to see heads, 'hg merge' to merge)
152 152 $ hg book
153 153 @ 1:9b140be10808
154 154 @foo 2:0d2164f0ce0d
155 155 X 1:9b140be10808
156 156 X@foo 2:0d2164f0ce0d
157 157 Y 0:4e3505fd9583
158 158 Z 2:0d2164f0ce0d
159 159 foo -1:000000000000
160 160 * foobar 1:9b140be10808
161 161 $ hg push -f ../a
162 162 pushing to ../a
163 163 searching for changes
164 164 adding changesets
165 165 adding manifests
166 166 adding file changes
167 167 added 1 changesets with 1 changes to 1 files (+1 heads)
168 168 $ hg -R ../a book
169 169 @ 1:0d2164f0ce0d
170 170 * X 1:0d2164f0ce0d
171 171 Y 0:4e3505fd9583
172 172 Z 1:0d2164f0ce0d
173 173
174 revsets should not ignore divergent bookmarks
175
176 $ hg bookmark -fr 1 Z
177 $ hg log -r 'bookmark()' --template '{rev}:{node|short} {bookmarks}\n'
178 0:4e3505fd9583 Y
179 1:9b140be10808 @ X Z foobar
180 2:0d2164f0ce0d @foo X@foo
181 $ hg log -r 'bookmark("X@foo")' --template '{rev}:{node|short} {bookmarks}\n'
182 2:0d2164f0ce0d @foo X@foo
183 $ hg log -r 'bookmark("re:X@foo")' --template '{rev}:{node|short} {bookmarks}\n'
184 2:0d2164f0ce0d @foo X@foo
185
174 186 update a remote bookmark from a non-head to a head
175 187
176 188 $ hg up -q Y
177 189 $ echo c3 > f2
178 190 $ hg ci -Am3
179 191 adding f2
180 192 created new head
181 193 $ hg push ../a
182 194 pushing to ../a
183 195 searching for changes
184 196 adding changesets
185 197 adding manifests
186 198 adding file changes
187 199 added 1 changesets with 1 changes to 1 files (+1 heads)
188 200 updating bookmark Y
189 201 $ hg -R ../a book
190 202 @ 1:0d2164f0ce0d
191 203 * X 1:0d2164f0ce0d
192 204 Y 3:f6fc62dde3c0
193 205 Z 1:0d2164f0ce0d
194 206
195 207 diverging a remote bookmark fails
196 208
197 209 $ hg up -q 4e3505fd9583
198 210 $ echo c4 > f2
199 211 $ hg ci -Am4
200 212 adding f2
201 213 created new head
202 214 $ echo c5 > f2
203 215 $ hg ci -Am5
204 216 $ hg log -G
205 217 @ 5:c922c0139ca0 5
206 218 |
207 219 o 4:4efff6d98829 4
208 220 |
209 221 | o 3:f6fc62dde3c0 3
210 222 |/
211 223 | o 2:0d2164f0ce0d 1
212 224 |/
213 225 | o 1:9b140be10808 2
214 226 |/
215 227 o 0:4e3505fd9583 test
216 228
217 229
218 230 $ hg book -f Y
219 231
220 232 $ cat <<EOF > ../a/.hg/hgrc
221 233 > [web]
222 234 > push_ssl = false
223 235 > allow_push = *
224 236 > EOF
225 237
226 238 $ hg -R ../a serve -p $HGPORT2 -d --pid-file=../hg2.pid
227 239 $ cat ../hg2.pid >> $DAEMON_PIDS
228 240
229 241 $ hg push http://localhost:$HGPORT2/
230 242 pushing to http://localhost:$HGPORT2/
231 243 searching for changes
232 244 abort: push creates new remote head c922c0139ca0!
233 245 (did you forget to merge? use push -f to force)
234 246 [255]
235 247 $ hg -R ../a book
236 248 @ 1:0d2164f0ce0d
237 249 * X 1:0d2164f0ce0d
238 250 Y 3:f6fc62dde3c0
239 251 Z 1:0d2164f0ce0d
240 252
241 253
242 254 Unrelated marker does not alter the decision
243 255
244 256 $ hg debugobsolete aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
245 257 $ hg push http://localhost:$HGPORT2/
246 258 pushing to http://localhost:$HGPORT2/
247 259 searching for changes
248 260 abort: push creates new remote head c922c0139ca0!
249 261 (did you forget to merge? use push -f to force)
250 262 [255]
251 263 $ hg -R ../a book
252 264 @ 1:0d2164f0ce0d
253 265 * X 1:0d2164f0ce0d
254 266 Y 3:f6fc62dde3c0
255 267 Z 1:0d2164f0ce0d
256 268
257 269 Update to a successor works
258 270
259 271 $ hg id --debug -r 3
260 272 f6fc62dde3c0771e29704af56ba4d8af77abcc2f
261 273 $ hg id --debug -r 4
262 274 4efff6d98829d9c824c621afd6e3f01865f5439f
263 275 $ hg id --debug -r 5
264 276 c922c0139ca03858f655e4a2af4dd02796a63969 tip Y
265 277 $ hg debugobsolete f6fc62dde3c0771e29704af56ba4d8af77abcc2f cccccccccccccccccccccccccccccccccccccccc
266 278 $ hg debugobsolete cccccccccccccccccccccccccccccccccccccccc 4efff6d98829d9c824c621afd6e3f01865f5439f
267 279 $ hg push http://localhost:$HGPORT2/
268 280 pushing to http://localhost:$HGPORT2/
269 281 searching for changes
270 282 remote: adding changesets
271 283 remote: adding manifests
272 284 remote: adding file changes
273 285 remote: added 2 changesets with 2 changes to 1 files (+1 heads)
274 286 updating bookmark Y
275 287 $ hg -R ../a book
276 288 @ 1:0d2164f0ce0d
277 289 * X 1:0d2164f0ce0d
278 290 Y 5:c922c0139ca0
279 291 Z 1:0d2164f0ce0d
280 292
281 293 hgweb
282 294
283 295 $ cat <<EOF > .hg/hgrc
284 296 > [web]
285 297 > push_ssl = false
286 298 > allow_push = *
287 299 > EOF
288 300
289 301 $ hg serve -p $HGPORT -d --pid-file=../hg.pid -E errors.log
290 302 $ cat ../hg.pid >> $DAEMON_PIDS
291 303 $ cd ../a
292 304
293 305 $ hg debugpushkey http://localhost:$HGPORT/ namespaces
294 306 bookmarks
295 307 namespaces
296 308 obsolete
297 309 phases
298 310 $ hg debugpushkey http://localhost:$HGPORT/ bookmarks
299 311 @ 9b140be1080824d768c5a4691a564088eede71f9
300 312 X 9b140be1080824d768c5a4691a564088eede71f9
301 313 Y c922c0139ca03858f655e4a2af4dd02796a63969
302 Z 0d2164f0ce0d8f1d6f94351eba04b794909be66c
314 Z 9b140be1080824d768c5a4691a564088eede71f9
303 315 foo 0000000000000000000000000000000000000000
304 316 foobar 9b140be1080824d768c5a4691a564088eede71f9
305 317 $ hg out -B http://localhost:$HGPORT/
306 318 comparing with http://localhost:$HGPORT/
307 319 searching for changed bookmarks
308 320 no changed bookmarks found
309 321 [1]
310 322 $ hg push -B Z http://localhost:$HGPORT/
311 323 pushing to http://localhost:$HGPORT/
312 324 searching for changes
313 325 no changes found
314 326 exporting bookmark Z
315 327 [1]
316 328 $ hg book -d Z
317 329 $ hg in -B http://localhost:$HGPORT/
318 330 comparing with http://localhost:$HGPORT/
319 331 searching for changed bookmarks
320 332 Z 0d2164f0ce0d
321 333 foo 000000000000
322 334 foobar 9b140be10808
323 335 $ hg pull -B Z http://localhost:$HGPORT/
324 336 pulling from http://localhost:$HGPORT/
325 337 no changes found
326 338 divergent bookmark @ stored as @1
327 339 divergent bookmark X stored as X@1
328 340 adding remote bookmark Z
329 341 adding remote bookmark foo
330 342 adding remote bookmark foobar
331 343 importing bookmark Z
332 344 $ hg clone http://localhost:$HGPORT/ cloned-bookmarks
333 345 requesting all changes
334 346 adding changesets
335 347 adding manifests
336 348 adding file changes
337 349 added 5 changesets with 5 changes to 3 files (+2 heads)
338 350 updating to bookmark @
339 351 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
340 352 $ hg -R cloned-bookmarks bookmarks
341 353 * @ 1:9b140be10808
342 354 X 1:9b140be10808
343 355 Y 4:c922c0139ca0
344 356 Z 2:0d2164f0ce0d
345 357 foo -1:000000000000
346 358 foobar 1:9b140be10808
347 359
348 360 $ cd ..
349 361
350 362 Pushing a bookmark should only push the changes required by that
351 363 bookmark, not all outgoing changes:
352 364 $ hg clone http://localhost:$HGPORT/ addmarks
353 365 requesting all changes
354 366 adding changesets
355 367 adding manifests
356 368 adding file changes
357 369 added 5 changesets with 5 changes to 3 files (+2 heads)
358 370 updating to bookmark @
359 371 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
360 372 $ cd addmarks
361 373 $ echo foo > foo
362 374 $ hg add foo
363 375 $ hg commit -m 'add foo'
364 376 $ echo bar > bar
365 377 $ hg add bar
366 378 $ hg commit -m 'add bar'
367 379 $ hg co "tip^"
368 380 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
369 381 $ hg book add-foo
370 382 $ hg book -r tip add-bar
371 383 Note: this push *must* push only a single changeset, as that's the point
372 384 of this test.
373 385 $ hg push -B add-foo --traceback
374 386 pushing to http://localhost:$HGPORT/
375 387 searching for changes
376 388 remote: adding changesets
377 389 remote: adding manifests
378 390 remote: adding file changes
379 391 remote: added 1 changesets with 1 changes to 1 files
380 392 updating bookmark @ failed!
381 393 exporting bookmark add-foo
382 394
383 395 $ cd ..
@@ -1,1322 +1,1330 b''
1 1 The g is crafted to have 2 filelog topological heads in a linear
2 2 changeset graph
3 3
4 4 $ hg init a
5 5 $ cd a
6 6 $ echo a > a
7 7 $ echo f > f
8 8 $ hg ci -Ama -d '1 0'
9 9 adding a
10 10 adding f
11 11
12 12 $ hg cp a b
13 13 $ hg cp f g
14 14 $ hg ci -mb -d '2 0'
15 15
16 16 $ mkdir dir
17 17 $ hg mv b dir
18 18 $ echo g >> g
19 19 $ echo f >> f
20 20 $ hg ci -mc -d '3 0'
21 21
22 22 $ hg mv a b
23 23 $ hg cp -f f g
24 24 $ echo a > d
25 25 $ hg add d
26 26 $ hg ci -md -d '4 0'
27 27
28 28 $ hg mv dir/b e
29 29 $ hg ci -me -d '5 0'
30 30
31 31 $ hg log a
32 32 changeset: 0:9161b9aeaf16
33 33 user: test
34 34 date: Thu Jan 01 00:00:01 1970 +0000
35 35 summary: a
36 36
37 37 log on directory
38 38
39 39 $ hg log dir
40 40 changeset: 4:7e4639b4691b
41 41 tag: tip
42 42 user: test
43 43 date: Thu Jan 01 00:00:05 1970 +0000
44 44 summary: e
45 45
46 46 changeset: 2:f8954cd4dc1f
47 47 user: test
48 48 date: Thu Jan 01 00:00:03 1970 +0000
49 49 summary: c
50 50
51 51 $ hg log somethingthatdoesntexist dir
52 52 changeset: 4:7e4639b4691b
53 53 tag: tip
54 54 user: test
55 55 date: Thu Jan 01 00:00:05 1970 +0000
56 56 summary: e
57 57
58 58 changeset: 2:f8954cd4dc1f
59 59 user: test
60 60 date: Thu Jan 01 00:00:03 1970 +0000
61 61 summary: c
62 62
63 63
64 64 -f, directory
65 65
66 66 $ hg log -f dir
67 67 abort: cannot follow file not in parent revision: "dir"
68 68 [255]
69 69
70 70 -f, but no args
71 71
72 72 $ hg log -f
73 73 changeset: 4:7e4639b4691b
74 74 tag: tip
75 75 user: test
76 76 date: Thu Jan 01 00:00:05 1970 +0000
77 77 summary: e
78 78
79 79 changeset: 3:2ca5ba701980
80 80 user: test
81 81 date: Thu Jan 01 00:00:04 1970 +0000
82 82 summary: d
83 83
84 84 changeset: 2:f8954cd4dc1f
85 85 user: test
86 86 date: Thu Jan 01 00:00:03 1970 +0000
87 87 summary: c
88 88
89 89 changeset: 1:d89b0a12d229
90 90 user: test
91 91 date: Thu Jan 01 00:00:02 1970 +0000
92 92 summary: b
93 93
94 94 changeset: 0:9161b9aeaf16
95 95 user: test
96 96 date: Thu Jan 01 00:00:01 1970 +0000
97 97 summary: a
98 98
99 99
100 100 one rename
101 101
102 102 $ hg up -q 2
103 103 $ hg log -vf a
104 104 changeset: 0:9161b9aeaf16
105 105 user: test
106 106 date: Thu Jan 01 00:00:01 1970 +0000
107 107 files: a f
108 108 description:
109 109 a
110 110
111 111
112 112
113 113 many renames
114 114
115 115 $ hg up -q tip
116 116 $ hg log -vf e
117 117 changeset: 4:7e4639b4691b
118 118 tag: tip
119 119 user: test
120 120 date: Thu Jan 01 00:00:05 1970 +0000
121 121 files: dir/b e
122 122 description:
123 123 e
124 124
125 125
126 126 changeset: 2:f8954cd4dc1f
127 127 user: test
128 128 date: Thu Jan 01 00:00:03 1970 +0000
129 129 files: b dir/b f g
130 130 description:
131 131 c
132 132
133 133
134 134 changeset: 1:d89b0a12d229
135 135 user: test
136 136 date: Thu Jan 01 00:00:02 1970 +0000
137 137 files: b g
138 138 description:
139 139 b
140 140
141 141
142 142 changeset: 0:9161b9aeaf16
143 143 user: test
144 144 date: Thu Jan 01 00:00:01 1970 +0000
145 145 files: a f
146 146 description:
147 147 a
148 148
149 149
150 150
151 151
152 152 log -pf dir/b
153 153
154 154 $ hg up -q 3
155 155 $ hg log -pf dir/b
156 156 changeset: 2:f8954cd4dc1f
157 157 user: test
158 158 date: Thu Jan 01 00:00:03 1970 +0000
159 159 summary: c
160 160
161 161 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
162 162 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
163 163 +++ b/dir/b Thu Jan 01 00:00:03 1970 +0000
164 164 @@ -0,0 +1,1 @@
165 165 +a
166 166
167 167 changeset: 1:d89b0a12d229
168 168 user: test
169 169 date: Thu Jan 01 00:00:02 1970 +0000
170 170 summary: b
171 171
172 172 diff -r 9161b9aeaf16 -r d89b0a12d229 b
173 173 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
174 174 +++ b/b Thu Jan 01 00:00:02 1970 +0000
175 175 @@ -0,0 +1,1 @@
176 176 +a
177 177
178 178 changeset: 0:9161b9aeaf16
179 179 user: test
180 180 date: Thu Jan 01 00:00:01 1970 +0000
181 181 summary: a
182 182
183 183 diff -r 000000000000 -r 9161b9aeaf16 a
184 184 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
185 185 +++ b/a Thu Jan 01 00:00:01 1970 +0000
186 186 @@ -0,0 +1,1 @@
187 187 +a
188 188
189 189
190 190 log -vf dir/b
191 191
192 192 $ hg log -vf dir/b
193 193 changeset: 2:f8954cd4dc1f
194 194 user: test
195 195 date: Thu Jan 01 00:00:03 1970 +0000
196 196 files: b dir/b f g
197 197 description:
198 198 c
199 199
200 200
201 201 changeset: 1:d89b0a12d229
202 202 user: test
203 203 date: Thu Jan 01 00:00:02 1970 +0000
204 204 files: b g
205 205 description:
206 206 b
207 207
208 208
209 209 changeset: 0:9161b9aeaf16
210 210 user: test
211 211 date: Thu Jan 01 00:00:01 1970 +0000
212 212 files: a f
213 213 description:
214 214 a
215 215
216 216
217 217
218 218
219 219 -f and multiple filelog heads
220 220
221 221 $ hg up -q 2
222 222 $ hg log -f g --template '{rev}\n'
223 223 2
224 224 1
225 225 0
226 226 $ hg up -q tip
227 227 $ hg log -f g --template '{rev}\n'
228 228 3
229 229 2
230 230 0
231 231
232 232
233 233 log copies with --copies
234 234
235 235 $ hg log -vC --template '{rev} {file_copies}\n'
236 236 4 e (dir/b)
237 237 3 b (a)g (f)
238 238 2 dir/b (b)
239 239 1 b (a)g (f)
240 240 0
241 241
242 242 log copies switch without --copies, with old filecopy template
243 243
244 244 $ hg log -v --template '{rev} {file_copies_switch%filecopy}\n'
245 245 4
246 246 3
247 247 2
248 248 1
249 249 0
250 250
251 251 log copies switch with --copies
252 252
253 253 $ hg log -vC --template '{rev} {file_copies_switch}\n'
254 254 4 e (dir/b)
255 255 3 b (a)g (f)
256 256 2 dir/b (b)
257 257 1 b (a)g (f)
258 258 0
259 259
260 260
261 261 log copies with hardcoded style and with --style=default
262 262
263 263 $ hg log -vC -r4
264 264 changeset: 4:7e4639b4691b
265 265 tag: tip
266 266 user: test
267 267 date: Thu Jan 01 00:00:05 1970 +0000
268 268 files: dir/b e
269 269 copies: e (dir/b)
270 270 description:
271 271 e
272 272
273 273
274 274 $ hg log -vC -r4 --style=default
275 275 changeset: 4:7e4639b4691b
276 276 tag: tip
277 277 user: test
278 278 date: Thu Jan 01 00:00:05 1970 +0000
279 279 files: dir/b e
280 280 copies: e (dir/b)
281 281 description:
282 282 e
283 283
284 284
285 285
286 286
287 287 log copies, non-linear manifest
288 288
289 289 $ hg up -C 3
290 290 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
291 291 $ hg mv dir/b e
292 292 $ echo foo > foo
293 293 $ hg ci -Ame2 -d '6 0'
294 294 adding foo
295 295 created new head
296 296 $ hg log -v --template '{rev} {file_copies}\n' -r 5
297 297 5 e (dir/b)
298 298
299 299
300 300 log copies, execute bit set
301 301
302 302 #if execbit
303 303 $ chmod +x e
304 304 $ hg ci -me3 -d '7 0'
305 305 $ hg log -v --template '{rev} {file_copies}\n' -r 6
306 306 6
307 307 #endif
308 308
309 309
310 310 log -p d
311 311
312 312 $ hg log -pv d
313 313 changeset: 3:2ca5ba701980
314 314 user: test
315 315 date: Thu Jan 01 00:00:04 1970 +0000
316 316 files: a b d g
317 317 description:
318 318 d
319 319
320 320
321 321 diff -r f8954cd4dc1f -r 2ca5ba701980 d
322 322 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
323 323 +++ b/d Thu Jan 01 00:00:04 1970 +0000
324 324 @@ -0,0 +1,1 @@
325 325 +a
326 326
327 327
328 328
329 329 log --removed file
330 330
331 331 $ hg log --removed -v a
332 332 changeset: 3:2ca5ba701980
333 333 user: test
334 334 date: Thu Jan 01 00:00:04 1970 +0000
335 335 files: a b d g
336 336 description:
337 337 d
338 338
339 339
340 340 changeset: 0:9161b9aeaf16
341 341 user: test
342 342 date: Thu Jan 01 00:00:01 1970 +0000
343 343 files: a f
344 344 description:
345 345 a
346 346
347 347
348 348
349 349 log --removed revrange file
350 350
351 351 $ hg log --removed -v -r0:2 a
352 352 changeset: 0:9161b9aeaf16
353 353 user: test
354 354 date: Thu Jan 01 00:00:01 1970 +0000
355 355 files: a f
356 356 description:
357 357 a
358 358
359 359
360 360 $ cd ..
361 361
362 362 log --follow tests
363 363
364 364 $ hg init follow
365 365 $ cd follow
366 366
367 367 $ echo base > base
368 368 $ hg ci -Ambase -d '1 0'
369 369 adding base
370 370
371 371 $ echo r1 >> base
372 372 $ hg ci -Amr1 -d '1 0'
373 373 $ echo r2 >> base
374 374 $ hg ci -Amr2 -d '1 0'
375 375
376 376 $ hg up -C 1
377 377 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
378 378 $ echo b1 > b1
379 379 $ hg ci -Amb1 -d '1 0'
380 380 adding b1
381 381 created new head
382 382
383 383
384 384 log -f
385 385
386 386 $ hg log -f
387 387 changeset: 3:e62f78d544b4
388 388 tag: tip
389 389 parent: 1:3d5bf5654eda
390 390 user: test
391 391 date: Thu Jan 01 00:00:01 1970 +0000
392 392 summary: b1
393 393
394 394 changeset: 1:3d5bf5654eda
395 395 user: test
396 396 date: Thu Jan 01 00:00:01 1970 +0000
397 397 summary: r1
398 398
399 399 changeset: 0:67e992f2c4f3
400 400 user: test
401 401 date: Thu Jan 01 00:00:01 1970 +0000
402 402 summary: base
403 403
404 404
405 405
406 406 log -f -r 1:tip
407 407
408 408 $ hg up -C 0
409 409 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
410 410 $ echo b2 > b2
411 411 $ hg ci -Amb2 -d '1 0'
412 412 adding b2
413 413 created new head
414 414 $ hg log -f -r 1:tip
415 415 changeset: 1:3d5bf5654eda
416 416 user: test
417 417 date: Thu Jan 01 00:00:01 1970 +0000
418 418 summary: r1
419 419
420 420 changeset: 2:60c670bf5b30
421 421 user: test
422 422 date: Thu Jan 01 00:00:01 1970 +0000
423 423 summary: r2
424 424
425 425 changeset: 3:e62f78d544b4
426 426 parent: 1:3d5bf5654eda
427 427 user: test
428 428 date: Thu Jan 01 00:00:01 1970 +0000
429 429 summary: b1
430 430
431 431
432 432
433 433 log -r . with two parents
434 434
435 435 $ hg up -C 3
436 436 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
437 437 $ hg merge tip
438 438 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
439 439 (branch merge, don't forget to commit)
440 440 $ hg log -r .
441 441 changeset: 3:e62f78d544b4
442 442 parent: 1:3d5bf5654eda
443 443 user: test
444 444 date: Thu Jan 01 00:00:01 1970 +0000
445 445 summary: b1
446 446
447 447
448 448
449 449 log -r . with one parent
450 450
451 451 $ hg ci -mm12 -d '1 0'
452 452 $ hg log -r .
453 453 changeset: 5:302e9dd6890d
454 454 tag: tip
455 455 parent: 3:e62f78d544b4
456 456 parent: 4:ddb82e70d1a1
457 457 user: test
458 458 date: Thu Jan 01 00:00:01 1970 +0000
459 459 summary: m12
460 460
461 461
462 462 $ echo postm >> b1
463 463 $ hg ci -Amb1.1 -d'1 0'
464 464
465 465
466 466 log --follow-first
467 467
468 468 $ hg log --follow-first
469 469 changeset: 6:2404bbcab562
470 470 tag: tip
471 471 user: test
472 472 date: Thu Jan 01 00:00:01 1970 +0000
473 473 summary: b1.1
474 474
475 475 changeset: 5:302e9dd6890d
476 476 parent: 3:e62f78d544b4
477 477 parent: 4:ddb82e70d1a1
478 478 user: test
479 479 date: Thu Jan 01 00:00:01 1970 +0000
480 480 summary: m12
481 481
482 482 changeset: 3:e62f78d544b4
483 483 parent: 1:3d5bf5654eda
484 484 user: test
485 485 date: Thu Jan 01 00:00:01 1970 +0000
486 486 summary: b1
487 487
488 488 changeset: 1:3d5bf5654eda
489 489 user: test
490 490 date: Thu Jan 01 00:00:01 1970 +0000
491 491 summary: r1
492 492
493 493 changeset: 0:67e992f2c4f3
494 494 user: test
495 495 date: Thu Jan 01 00:00:01 1970 +0000
496 496 summary: base
497 497
498 498
499 499
500 500 log -P 2
501 501
502 502 $ hg log -P 2
503 503 changeset: 6:2404bbcab562
504 504 tag: tip
505 505 user: test
506 506 date: Thu Jan 01 00:00:01 1970 +0000
507 507 summary: b1.1
508 508
509 509 changeset: 5:302e9dd6890d
510 510 parent: 3:e62f78d544b4
511 511 parent: 4:ddb82e70d1a1
512 512 user: test
513 513 date: Thu Jan 01 00:00:01 1970 +0000
514 514 summary: m12
515 515
516 516 changeset: 4:ddb82e70d1a1
517 517 parent: 0:67e992f2c4f3
518 518 user: test
519 519 date: Thu Jan 01 00:00:01 1970 +0000
520 520 summary: b2
521 521
522 522 changeset: 3:e62f78d544b4
523 523 parent: 1:3d5bf5654eda
524 524 user: test
525 525 date: Thu Jan 01 00:00:01 1970 +0000
526 526 summary: b1
527 527
528 528
529 529
530 530 log -r tip -p --git
531 531
532 532 $ hg log -r tip -p --git
533 533 changeset: 6:2404bbcab562
534 534 tag: tip
535 535 user: test
536 536 date: Thu Jan 01 00:00:01 1970 +0000
537 537 summary: b1.1
538 538
539 539 diff --git a/b1 b/b1
540 540 --- a/b1
541 541 +++ b/b1
542 542 @@ -1,1 +1,2 @@
543 543 b1
544 544 +postm
545 545
546 546
547 547
548 548 log -r ""
549 549
550 550 $ hg log -r ''
551 551 hg: parse error: empty query
552 552 [255]
553 553
554 554 log -r <some unknown node id>
555 555
556 556 $ hg log -r 1000000000000000000000000000000000000000
557 557 abort: unknown revision '1000000000000000000000000000000000000000'!
558 558 [255]
559 559
560 560 log -k r1
561 561
562 562 $ hg log -k r1
563 563 changeset: 1:3d5bf5654eda
564 564 user: test
565 565 date: Thu Jan 01 00:00:01 1970 +0000
566 566 summary: r1
567 567
568 568 log -p -l2 --color=always
569 569
570 570 $ hg --config extensions.color= --config color.mode=ansi \
571 571 > log -p -l2 --color=always
572 572 \x1b[0;33mchangeset: 6:2404bbcab562\x1b[0m (esc)
573 573 tag: tip
574 574 user: test
575 575 date: Thu Jan 01 00:00:01 1970 +0000
576 576 summary: b1.1
577 577
578 578 \x1b[0;1mdiff -r 302e9dd6890d -r 2404bbcab562 b1\x1b[0m (esc)
579 579 \x1b[0;31;1m--- a/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
580 580 \x1b[0;32;1m+++ b/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
581 581 \x1b[0;35m@@ -1,1 +1,2 @@\x1b[0m (esc)
582 582 b1
583 583 \x1b[0;32m+postm\x1b[0m (esc)
584 584
585 585 \x1b[0;33mchangeset: 5:302e9dd6890d\x1b[0m (esc)
586 586 parent: 3:e62f78d544b4
587 587 parent: 4:ddb82e70d1a1
588 588 user: test
589 589 date: Thu Jan 01 00:00:01 1970 +0000
590 590 summary: m12
591 591
592 592 \x1b[0;1mdiff -r e62f78d544b4 -r 302e9dd6890d b2\x1b[0m (esc)
593 593 \x1b[0;31;1m--- /dev/null Thu Jan 01 00:00:00 1970 +0000\x1b[0m (esc)
594 594 \x1b[0;32;1m+++ b/b2 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
595 595 \x1b[0;35m@@ -0,0 +1,1 @@\x1b[0m (esc)
596 596 \x1b[0;32m+b2\x1b[0m (esc)
597 597
598 598
599 599
600 600 log -r tip --stat
601 601
602 602 $ hg log -r tip --stat
603 603 changeset: 6:2404bbcab562
604 604 tag: tip
605 605 user: test
606 606 date: Thu Jan 01 00:00:01 1970 +0000
607 607 summary: b1.1
608 608
609 609 b1 | 1 +
610 610 1 files changed, 1 insertions(+), 0 deletions(-)
611 611
612 612
613 613 $ cd ..
614 614
615 615
616 616 User
617 617
618 618 $ hg init usertest
619 619 $ cd usertest
620 620
621 621 $ echo a > a
622 622 $ hg ci -A -m "a" -u "User One <user1@example.org>"
623 623 adding a
624 624 $ echo b > b
625 625 $ hg ci -A -m "b" -u "User Two <user2@example.org>"
626 626 adding b
627 627
628 628 $ hg log -u "User One <user1@example.org>"
629 629 changeset: 0:29a4c94f1924
630 630 user: User One <user1@example.org>
631 631 date: Thu Jan 01 00:00:00 1970 +0000
632 632 summary: a
633 633
634 634 $ hg log -u "user1" -u "user2"
635 635 changeset: 1:e834b5e69c0e
636 636 tag: tip
637 637 user: User Two <user2@example.org>
638 638 date: Thu Jan 01 00:00:00 1970 +0000
639 639 summary: b
640 640
641 641 changeset: 0:29a4c94f1924
642 642 user: User One <user1@example.org>
643 643 date: Thu Jan 01 00:00:00 1970 +0000
644 644 summary: a
645 645
646 646 $ hg log -u "user3"
647 647
648 648 $ cd ..
649 649
650 650 $ hg init branches
651 651 $ cd branches
652 652
653 653 $ echo a > a
654 654 $ hg ci -A -m "commit on default"
655 655 adding a
656 656 $ hg branch test
657 657 marked working directory as branch test
658 658 (branches are permanent and global, did you want a bookmark?)
659 659 $ echo b > b
660 660 $ hg ci -A -m "commit on test"
661 661 adding b
662 662
663 663 $ hg up default
664 664 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
665 665 $ echo c > c
666 666 $ hg ci -A -m "commit on default"
667 667 adding c
668 668 $ hg up test
669 669 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
670 670 $ echo c > c
671 671 $ hg ci -A -m "commit on test"
672 672 adding c
673 673
674 674
675 675 log -b default
676 676
677 677 $ hg log -b default
678 678 changeset: 2:c3a4f03cc9a7
679 679 parent: 0:24427303d56f
680 680 user: test
681 681 date: Thu Jan 01 00:00:00 1970 +0000
682 682 summary: commit on default
683 683
684 684 changeset: 0:24427303d56f
685 685 user: test
686 686 date: Thu Jan 01 00:00:00 1970 +0000
687 687 summary: commit on default
688 688
689 689
690 690
691 691 log -b test
692 692
693 693 $ hg log -b test
694 694 changeset: 3:f5d8de11c2e2
695 695 branch: test
696 696 tag: tip
697 697 parent: 1:d32277701ccb
698 698 user: test
699 699 date: Thu Jan 01 00:00:00 1970 +0000
700 700 summary: commit on test
701 701
702 702 changeset: 1:d32277701ccb
703 703 branch: test
704 704 user: test
705 705 date: Thu Jan 01 00:00:00 1970 +0000
706 706 summary: commit on test
707 707
708 708
709 709
710 710 log -b dummy
711 711
712 712 $ hg log -b dummy
713 713 abort: unknown revision 'dummy'!
714 714 [255]
715 715
716 716
717 717 log -b .
718 718
719 719 $ hg log -b .
720 720 changeset: 3:f5d8de11c2e2
721 721 branch: test
722 722 tag: tip
723 723 parent: 1:d32277701ccb
724 724 user: test
725 725 date: Thu Jan 01 00:00:00 1970 +0000
726 726 summary: commit on test
727 727
728 728 changeset: 1:d32277701ccb
729 729 branch: test
730 730 user: test
731 731 date: Thu Jan 01 00:00:00 1970 +0000
732 732 summary: commit on test
733 733
734 734
735 735
736 736 log -b default -b test
737 737
738 738 $ hg log -b default -b test
739 739 changeset: 3:f5d8de11c2e2
740 740 branch: test
741 741 tag: tip
742 742 parent: 1:d32277701ccb
743 743 user: test
744 744 date: Thu Jan 01 00:00:00 1970 +0000
745 745 summary: commit on test
746 746
747 747 changeset: 2:c3a4f03cc9a7
748 748 parent: 0:24427303d56f
749 749 user: test
750 750 date: Thu Jan 01 00:00:00 1970 +0000
751 751 summary: commit on default
752 752
753 753 changeset: 1:d32277701ccb
754 754 branch: test
755 755 user: test
756 756 date: Thu Jan 01 00:00:00 1970 +0000
757 757 summary: commit on test
758 758
759 759 changeset: 0:24427303d56f
760 760 user: test
761 761 date: Thu Jan 01 00:00:00 1970 +0000
762 762 summary: commit on default
763 763
764 764
765 765
766 766 log -b default -b .
767 767
768 768 $ hg log -b default -b .
769 769 changeset: 3:f5d8de11c2e2
770 770 branch: test
771 771 tag: tip
772 772 parent: 1:d32277701ccb
773 773 user: test
774 774 date: Thu Jan 01 00:00:00 1970 +0000
775 775 summary: commit on test
776 776
777 777 changeset: 2:c3a4f03cc9a7
778 778 parent: 0:24427303d56f
779 779 user: test
780 780 date: Thu Jan 01 00:00:00 1970 +0000
781 781 summary: commit on default
782 782
783 783 changeset: 1:d32277701ccb
784 784 branch: test
785 785 user: test
786 786 date: Thu Jan 01 00:00:00 1970 +0000
787 787 summary: commit on test
788 788
789 789 changeset: 0:24427303d56f
790 790 user: test
791 791 date: Thu Jan 01 00:00:00 1970 +0000
792 792 summary: commit on default
793 793
794 794
795 795
796 796 log -b . -b test
797 797
798 798 $ hg log -b . -b test
799 799 changeset: 3:f5d8de11c2e2
800 800 branch: test
801 801 tag: tip
802 802 parent: 1:d32277701ccb
803 803 user: test
804 804 date: Thu Jan 01 00:00:00 1970 +0000
805 805 summary: commit on test
806 806
807 807 changeset: 1:d32277701ccb
808 808 branch: test
809 809 user: test
810 810 date: Thu Jan 01 00:00:00 1970 +0000
811 811 summary: commit on test
812 812
813 813
814 814
815 815 log -b 2
816 816
817 817 $ hg log -b 2
818 818 changeset: 2:c3a4f03cc9a7
819 819 parent: 0:24427303d56f
820 820 user: test
821 821 date: Thu Jan 01 00:00:00 1970 +0000
822 822 summary: commit on default
823 823
824 824 changeset: 0:24427303d56f
825 825 user: test
826 826 date: Thu Jan 01 00:00:00 1970 +0000
827 827 summary: commit on default
828 828
829 829
830 830
831 831 log -p --cwd dir (in subdir)
832 832
833 833 $ mkdir dir
834 834 $ hg log -p --cwd dir
835 835 changeset: 3:f5d8de11c2e2
836 836 branch: test
837 837 tag: tip
838 838 parent: 1:d32277701ccb
839 839 user: test
840 840 date: Thu Jan 01 00:00:00 1970 +0000
841 841 summary: commit on test
842 842
843 843 diff -r d32277701ccb -r f5d8de11c2e2 c
844 844 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
845 845 +++ b/c Thu Jan 01 00:00:00 1970 +0000
846 846 @@ -0,0 +1,1 @@
847 847 +c
848 848
849 849 changeset: 2:c3a4f03cc9a7
850 850 parent: 0:24427303d56f
851 851 user: test
852 852 date: Thu Jan 01 00:00:00 1970 +0000
853 853 summary: commit on default
854 854
855 855 diff -r 24427303d56f -r c3a4f03cc9a7 c
856 856 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
857 857 +++ b/c Thu Jan 01 00:00:00 1970 +0000
858 858 @@ -0,0 +1,1 @@
859 859 +c
860 860
861 861 changeset: 1:d32277701ccb
862 862 branch: test
863 863 user: test
864 864 date: Thu Jan 01 00:00:00 1970 +0000
865 865 summary: commit on test
866 866
867 867 diff -r 24427303d56f -r d32277701ccb b
868 868 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
869 869 +++ b/b Thu Jan 01 00:00:00 1970 +0000
870 870 @@ -0,0 +1,1 @@
871 871 +b
872 872
873 873 changeset: 0:24427303d56f
874 874 user: test
875 875 date: Thu Jan 01 00:00:00 1970 +0000
876 876 summary: commit on default
877 877
878 878 diff -r 000000000000 -r 24427303d56f a
879 879 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
880 880 +++ b/a Thu Jan 01 00:00:00 1970 +0000
881 881 @@ -0,0 +1,1 @@
882 882 +a
883 883
884 884
885 885
886 886 log -p -R repo
887 887
888 888 $ cd dir
889 889 $ hg log -p -R .. ../a
890 890 changeset: 0:24427303d56f
891 891 user: test
892 892 date: Thu Jan 01 00:00:00 1970 +0000
893 893 summary: commit on default
894 894
895 895 diff -r 000000000000 -r 24427303d56f a
896 896 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
897 897 +++ b/a Thu Jan 01 00:00:00 1970 +0000
898 898 @@ -0,0 +1,1 @@
899 899 +a
900 900
901 901
902 902 $ cd ../..
903 903
904 904 $ hg init follow2
905 905 $ cd follow2
906 906
907 907 # Build the following history:
908 908 # tip - o - x - o - x - x
909 909 # \ /
910 910 # o - o - o - x
911 911 # \ /
912 912 # o
913 913 #
914 914 # Where "o" is a revision containing "foo" and
915 915 # "x" is a revision without "foo"
916 916
917 917 $ touch init
918 918 $ hg ci -A -m "init, unrelated"
919 919 adding init
920 920 $ echo 'foo' > init
921 921 $ hg ci -m "change, unrelated"
922 922 $ echo 'foo' > foo
923 923 $ hg ci -A -m "add unrelated old foo"
924 924 adding foo
925 925 $ hg rm foo
926 926 $ hg ci -m "delete foo, unrelated"
927 927 $ echo 'related' > foo
928 928 $ hg ci -A -m "add foo, related"
929 929 adding foo
930 930
931 931 $ hg up 0
932 932 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
933 933 $ touch branch
934 934 $ hg ci -A -m "first branch, unrelated"
935 935 adding branch
936 936 created new head
937 937 $ touch foo
938 938 $ hg ci -A -m "create foo, related"
939 939 adding foo
940 940 $ echo 'change' > foo
941 941 $ hg ci -m "change foo, related"
942 942
943 943 $ hg up 6
944 944 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
945 945 $ echo 'change foo in branch' > foo
946 946 $ hg ci -m "change foo in branch, related"
947 947 created new head
948 948 $ hg merge 7
949 949 merging foo
950 950 warning: conflicts during merge.
951 951 merging foo incomplete! (edit conflicts, then use 'hg resolve --mark')
952 952 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
953 953 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
954 954 [1]
955 955 $ echo 'merge 1' > foo
956 956 $ hg resolve -m foo
957 957 $ hg ci -m "First merge, related"
958 958
959 959 $ hg merge 4
960 960 merging foo
961 961 warning: conflicts during merge.
962 962 merging foo incomplete! (edit conflicts, then use 'hg resolve --mark')
963 963 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
964 964 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
965 965 [1]
966 966 $ echo 'merge 2' > foo
967 967 $ hg resolve -m foo
968 968 $ hg ci -m "Last merge, related"
969 969
970 970 $ hg log --graph
971 971 @ changeset: 10:4dae8563d2c5
972 972 |\ tag: tip
973 973 | | parent: 9:7b35701b003e
974 974 | | parent: 4:88176d361b69
975 975 | | user: test
976 976 | | date: Thu Jan 01 00:00:00 1970 +0000
977 977 | | summary: Last merge, related
978 978 | |
979 979 | o changeset: 9:7b35701b003e
980 980 | |\ parent: 8:e5416ad8a855
981 981 | | | parent: 7:87fe3144dcfa
982 982 | | | user: test
983 983 | | | date: Thu Jan 01 00:00:00 1970 +0000
984 984 | | | summary: First merge, related
985 985 | | |
986 986 | | o changeset: 8:e5416ad8a855
987 987 | | | parent: 6:dc6c325fe5ee
988 988 | | | user: test
989 989 | | | date: Thu Jan 01 00:00:00 1970 +0000
990 990 | | | summary: change foo in branch, related
991 991 | | |
992 992 | o | changeset: 7:87fe3144dcfa
993 993 | |/ user: test
994 994 | | date: Thu Jan 01 00:00:00 1970 +0000
995 995 | | summary: change foo, related
996 996 | |
997 997 | o changeset: 6:dc6c325fe5ee
998 998 | | user: test
999 999 | | date: Thu Jan 01 00:00:00 1970 +0000
1000 1000 | | summary: create foo, related
1001 1001 | |
1002 1002 | o changeset: 5:73db34516eb9
1003 1003 | | parent: 0:e87515fd044a
1004 1004 | | user: test
1005 1005 | | date: Thu Jan 01 00:00:00 1970 +0000
1006 1006 | | summary: first branch, unrelated
1007 1007 | |
1008 1008 o | changeset: 4:88176d361b69
1009 1009 | | user: test
1010 1010 | | date: Thu Jan 01 00:00:00 1970 +0000
1011 1011 | | summary: add foo, related
1012 1012 | |
1013 1013 o | changeset: 3:dd78ae4afb56
1014 1014 | | user: test
1015 1015 | | date: Thu Jan 01 00:00:00 1970 +0000
1016 1016 | | summary: delete foo, unrelated
1017 1017 | |
1018 1018 o | changeset: 2:c4c64aedf0f7
1019 1019 | | user: test
1020 1020 | | date: Thu Jan 01 00:00:00 1970 +0000
1021 1021 | | summary: add unrelated old foo
1022 1022 | |
1023 1023 o | changeset: 1:e5faa7440653
1024 1024 |/ user: test
1025 1025 | date: Thu Jan 01 00:00:00 1970 +0000
1026 1026 | summary: change, unrelated
1027 1027 |
1028 1028 o changeset: 0:e87515fd044a
1029 1029 user: test
1030 1030 date: Thu Jan 01 00:00:00 1970 +0000
1031 1031 summary: init, unrelated
1032 1032
1033 1033
1034 1034 $ hg --traceback log -f foo
1035 1035 changeset: 10:4dae8563d2c5
1036 1036 tag: tip
1037 1037 parent: 9:7b35701b003e
1038 1038 parent: 4:88176d361b69
1039 1039 user: test
1040 1040 date: Thu Jan 01 00:00:00 1970 +0000
1041 1041 summary: Last merge, related
1042 1042
1043 1043 changeset: 9:7b35701b003e
1044 1044 parent: 8:e5416ad8a855
1045 1045 parent: 7:87fe3144dcfa
1046 1046 user: test
1047 1047 date: Thu Jan 01 00:00:00 1970 +0000
1048 1048 summary: First merge, related
1049 1049
1050 1050 changeset: 8:e5416ad8a855
1051 1051 parent: 6:dc6c325fe5ee
1052 1052 user: test
1053 1053 date: Thu Jan 01 00:00:00 1970 +0000
1054 1054 summary: change foo in branch, related
1055 1055
1056 1056 changeset: 7:87fe3144dcfa
1057 1057 user: test
1058 1058 date: Thu Jan 01 00:00:00 1970 +0000
1059 1059 summary: change foo, related
1060 1060
1061 1061 changeset: 6:dc6c325fe5ee
1062 1062 user: test
1063 1063 date: Thu Jan 01 00:00:00 1970 +0000
1064 1064 summary: create foo, related
1065 1065
1066 1066 changeset: 4:88176d361b69
1067 1067 user: test
1068 1068 date: Thu Jan 01 00:00:00 1970 +0000
1069 1069 summary: add foo, related
1070 1070
1071 1071
1072 1072 Also check when maxrev < lastrevfilelog
1073 1073
1074 1074 $ hg --traceback log -f -r4 foo
1075 1075 changeset: 4:88176d361b69
1076 1076 user: test
1077 1077 date: Thu Jan 01 00:00:00 1970 +0000
1078 1078 summary: add foo, related
1079 1079
1080 1080 $ cd ..
1081 1081
1082 1082 Issue2383: hg log showing _less_ differences than hg diff
1083 1083
1084 1084 $ hg init issue2383
1085 1085 $ cd issue2383
1086 1086
1087 1087 Create a test repo:
1088 1088
1089 1089 $ echo a > a
1090 1090 $ hg ci -Am0
1091 1091 adding a
1092 1092 $ echo b > b
1093 1093 $ hg ci -Am1
1094 1094 adding b
1095 1095 $ hg co 0
1096 1096 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1097 1097 $ echo b > a
1098 1098 $ hg ci -m2
1099 1099 created new head
1100 1100
1101 1101 Merge:
1102 1102
1103 1103 $ hg merge
1104 1104 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1105 1105 (branch merge, don't forget to commit)
1106 1106
1107 1107 Make sure there's a file listed in the merge to trigger the bug:
1108 1108
1109 1109 $ echo c > a
1110 1110 $ hg ci -m3
1111 1111
1112 1112 Two files shown here in diff:
1113 1113
1114 1114 $ hg diff --rev 2:3
1115 1115 diff -r b09be438c43a -r 8e07aafe1edc a
1116 1116 --- a/a Thu Jan 01 00:00:00 1970 +0000
1117 1117 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1118 1118 @@ -1,1 +1,1 @@
1119 1119 -b
1120 1120 +c
1121 1121 diff -r b09be438c43a -r 8e07aafe1edc b
1122 1122 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1123 1123 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1124 1124 @@ -0,0 +1,1 @@
1125 1125 +b
1126 1126
1127 1127 Diff here should be the same:
1128 1128
1129 1129 $ hg log -vpr 3
1130 1130 changeset: 3:8e07aafe1edc
1131 1131 tag: tip
1132 1132 parent: 2:b09be438c43a
1133 1133 parent: 1:925d80f479bb
1134 1134 user: test
1135 1135 date: Thu Jan 01 00:00:00 1970 +0000
1136 1136 files: a
1137 1137 description:
1138 1138 3
1139 1139
1140 1140
1141 1141 diff -r b09be438c43a -r 8e07aafe1edc a
1142 1142 --- a/a Thu Jan 01 00:00:00 1970 +0000
1143 1143 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1144 1144 @@ -1,1 +1,1 @@
1145 1145 -b
1146 1146 +c
1147 1147 diff -r b09be438c43a -r 8e07aafe1edc b
1148 1148 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1149 1149 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1150 1150 @@ -0,0 +1,1 @@
1151 1151 +b
1152 1152
1153 1153 $ cd ..
1154 1154
1155 1155 'hg log -r rev fn' when last(filelog(fn)) != rev
1156 1156
1157 1157 $ hg init simplelog
1158 1158 $ cd simplelog
1159 1159 $ echo f > a
1160 1160 $ hg ci -Am'a' -d '0 0'
1161 1161 adding a
1162 1162 $ echo f >> a
1163 1163 $ hg ci -Am'a bis' -d '1 0'
1164 1164
1165 1165 $ hg log -r0 a
1166 1166 changeset: 0:9f758d63dcde
1167 1167 user: test
1168 1168 date: Thu Jan 01 00:00:00 1970 +0000
1169 1169 summary: a
1170 1170
1171 1171 enable obsolete to test hidden feature
1172 1172
1173 1173 $ cat > ${TESTTMP}/obs.py << EOF
1174 1174 > import mercurial.obsolete
1175 1175 > mercurial.obsolete._enabled = True
1176 1176 > EOF
1177 1177 $ echo '[extensions]' >> $HGRCPATH
1178 1178 $ echo "obs=${TESTTMP}/obs.py" >> $HGRCPATH
1179 1179
1180 1180 $ hg log --template='{rev}:{node}\n'
1181 1181 1:a765632148dc55d38c35c4f247c618701886cb2f
1182 1182 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1183 1183 $ hg debugobsolete a765632148dc55d38c35c4f247c618701886cb2f
1184 1184 $ hg up null -q
1185 1185 $ hg log --template='{rev}:{node}\n'
1186 1186 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1187 1187 $ hg log --template='{rev}:{node}\n' --hidden
1188 1188 1:a765632148dc55d38c35c4f247c618701886cb2f
1189 1189 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1190 1190
1191 1191 test that parent prevent a changeset to be hidden
1192 1192
1193 1193 $ hg up 1 -q --hidden
1194 1194 $ hg log --template='{rev}:{node}\n'
1195 1195 1:a765632148dc55d38c35c4f247c618701886cb2f
1196 1196 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1197 1197
1198 1198 test that second parent prevent a changeset to be hidden too
1199 1199
1200 1200 $ hg debugsetparents 0 1 # nothing suitable to merge here
1201 1201 $ hg log --template='{rev}:{node}\n'
1202 1202 1:a765632148dc55d38c35c4f247c618701886cb2f
1203 1203 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1204 1204 $ hg debugsetparents 1
1205 1205 $ hg up -q null
1206 1206
1207 1207 bookmarks prevent a changeset being hidden
1208 1208
1209 1209 $ hg bookmark --hidden -r 1 X
1210 1210 $ hg log --template '{rev}:{node}\n'
1211 1211 1:a765632148dc55d38c35c4f247c618701886cb2f
1212 1212 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1213 $ hg bookmark -d X
1214
1215 divergent bookmarks are not hidden
1216
1217 $ hg bookmark --hidden -r 1 X@foo
1218 $ hg log --template '{rev}:{node}\n'
1219 1:a765632148dc55d38c35c4f247c618701886cb2f
1220 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1213 1221
1214 1222 clear extensions configuration
1215 1223 $ echo '[extensions]' >> $HGRCPATH
1216 1224 $ echo "obs=!" >> $HGRCPATH
1217 1225 $ cd ..
1218 1226
1219 1227 test -u/-k for problematic encoding
1220 1228 # unicode: cp932:
1221 1229 # u30A2 0x83 0x41(= 'A')
1222 1230 # u30C2 0x83 0x61(= 'a')
1223 1231
1224 1232 $ hg init problematicencoding
1225 1233 $ cd problematicencoding
1226 1234
1227 1235 $ python > setup.sh <<EOF
1228 1236 > print u'''
1229 1237 > echo a > text
1230 1238 > hg add text
1231 1239 > hg --encoding utf-8 commit -u '\u30A2' -m none
1232 1240 > echo b > text
1233 1241 > hg --encoding utf-8 commit -u '\u30C2' -m none
1234 1242 > echo c > text
1235 1243 > hg --encoding utf-8 commit -u none -m '\u30A2'
1236 1244 > echo d > text
1237 1245 > hg --encoding utf-8 commit -u none -m '\u30C2'
1238 1246 > '''.encode('utf-8')
1239 1247 > EOF
1240 1248 $ sh < setup.sh
1241 1249
1242 1250 test in problematic encoding
1243 1251 $ python > test.sh <<EOF
1244 1252 > print u'''
1245 1253 > hg --encoding cp932 log --template '{rev}\\n' -u '\u30A2'
1246 1254 > echo ====
1247 1255 > hg --encoding cp932 log --template '{rev}\\n' -u '\u30C2'
1248 1256 > echo ====
1249 1257 > hg --encoding cp932 log --template '{rev}\\n' -k '\u30A2'
1250 1258 > echo ====
1251 1259 > hg --encoding cp932 log --template '{rev}\\n' -k '\u30C2'
1252 1260 > '''.encode('cp932')
1253 1261 > EOF
1254 1262 $ sh < test.sh
1255 1263 0
1256 1264 ====
1257 1265 1
1258 1266 ====
1259 1267 2
1260 1268 0
1261 1269 ====
1262 1270 3
1263 1271 1
1264 1272
1265 1273 $ cd ..
1266 1274
1267 1275 test hg log on non-existent files and on directories
1268 1276 $ hg init issue1340
1269 1277 $ cd issue1340
1270 1278 $ mkdir d1; mkdir D2; mkdir D3.i; mkdir d4.hg; mkdir d5.d; mkdir .d6
1271 1279 $ echo 1 > d1/f1
1272 1280 $ echo 1 > D2/f1
1273 1281 $ echo 1 > D3.i/f1
1274 1282 $ echo 1 > d4.hg/f1
1275 1283 $ echo 1 > d5.d/f1
1276 1284 $ echo 1 > .d6/f1
1277 1285 $ hg -q add .
1278 1286 $ hg commit -m "a bunch of weird directories"
1279 1287 $ hg log -l1 d1/f1 | grep changeset
1280 1288 changeset: 0:65624cd9070a
1281 1289 $ hg log -l1 f1
1282 1290 $ hg log -l1 . | grep changeset
1283 1291 changeset: 0:65624cd9070a
1284 1292 $ hg log -l1 ./ | grep changeset
1285 1293 changeset: 0:65624cd9070a
1286 1294 $ hg log -l1 d1 | grep changeset
1287 1295 changeset: 0:65624cd9070a
1288 1296 $ hg log -l1 D2 | grep changeset
1289 1297 changeset: 0:65624cd9070a
1290 1298 $ hg log -l1 D2/f1 | grep changeset
1291 1299 changeset: 0:65624cd9070a
1292 1300 $ hg log -l1 D3.i | grep changeset
1293 1301 changeset: 0:65624cd9070a
1294 1302 $ hg log -l1 D3.i/f1 | grep changeset
1295 1303 changeset: 0:65624cd9070a
1296 1304 $ hg log -l1 d4.hg | grep changeset
1297 1305 changeset: 0:65624cd9070a
1298 1306 $ hg log -l1 d4.hg/f1 | grep changeset
1299 1307 changeset: 0:65624cd9070a
1300 1308 $ hg log -l1 d5.d | grep changeset
1301 1309 changeset: 0:65624cd9070a
1302 1310 $ hg log -l1 d5.d/f1 | grep changeset
1303 1311 changeset: 0:65624cd9070a
1304 1312 $ hg log -l1 .d6 | grep changeset
1305 1313 changeset: 0:65624cd9070a
1306 1314 $ hg log -l1 .d6/f1 | grep changeset
1307 1315 changeset: 0:65624cd9070a
1308 1316
1309 1317 issue3772: hg log -r :null showing revision 0 as well
1310 1318
1311 1319 $ hg log -r :null
1312 1320 changeset: -1:000000000000
1313 1321 user:
1314 1322 date: Thu Jan 01 00:00:00 1970 +0000
1315 1323
1316 1324 $ hg log -r null:null
1317 1325 changeset: -1:000000000000
1318 1326 user:
1319 1327 date: Thu Jan 01 00:00:00 1970 +0000
1320 1328
1321 1329
1322 1330 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now