##// END OF EJS Templates
vfs: always use / as file separator (issue6546)...
marmoute -
r48617:98c3fa6a stable
parent child Browse files
Show More
@@ -1,754 +1,774 b''
1 # vfs.py - Mercurial 'vfs' classes
1 # vfs.py - Mercurial 'vfs' classes
2 #
2 #
3 # Copyright Olivia Mackall <olivia@selenic.com>
3 # Copyright Olivia Mackall <olivia@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import contextlib
9 import contextlib
10 import errno
10 import errno
11 import os
11 import os
12 import shutil
12 import shutil
13 import stat
13 import stat
14 import threading
14 import threading
15
15
16 from .i18n import _
16 from .i18n import _
17 from .pycompat import (
17 from .pycompat import (
18 delattr,
18 delattr,
19 getattr,
19 getattr,
20 setattr,
20 setattr,
21 )
21 )
22 from . import (
22 from . import (
23 encoding,
23 encoding,
24 error,
24 error,
25 pathutil,
25 pathutil,
26 pycompat,
26 pycompat,
27 util,
27 util,
28 )
28 )
29
29
30
30
31 def _avoidambig(path, oldstat):
31 def _avoidambig(path, oldstat):
32 """Avoid file stat ambiguity forcibly
32 """Avoid file stat ambiguity forcibly
33
33
34 This function causes copying ``path`` file, if it is owned by
34 This function causes copying ``path`` file, if it is owned by
35 another (see issue5418 and issue5584 for detail).
35 another (see issue5418 and issue5584 for detail).
36 """
36 """
37
37
38 def checkandavoid():
38 def checkandavoid():
39 newstat = util.filestat.frompath(path)
39 newstat = util.filestat.frompath(path)
40 # return whether file stat ambiguity is (already) avoided
40 # return whether file stat ambiguity is (already) avoided
41 return not newstat.isambig(oldstat) or newstat.avoidambig(path, oldstat)
41 return not newstat.isambig(oldstat) or newstat.avoidambig(path, oldstat)
42
42
43 if not checkandavoid():
43 if not checkandavoid():
44 # simply copy to change owner of path to get privilege to
44 # simply copy to change owner of path to get privilege to
45 # advance mtime (see issue5418)
45 # advance mtime (see issue5418)
46 util.rename(util.mktempcopy(path), path)
46 util.rename(util.mktempcopy(path), path)
47 checkandavoid()
47 checkandavoid()
48
48
49
49
50 class abstractvfs(object):
50 class abstractvfs(object):
51 """Abstract base class; cannot be instantiated"""
51 """Abstract base class; cannot be instantiated"""
52
52
53 # default directory separator for vfs
54 #
55 # Other vfs code always use `/` and this works fine because python file API
56 # abstract the use of `/` and make it work transparently. For consistency
57 # vfs will always use `/` when joining. This avoid some confusion in
58 # encoded vfs (see issue6546)
59 _dir_sep = b'/'
60
53 def __init__(self, *args, **kwargs):
61 def __init__(self, *args, **kwargs):
54 '''Prevent instantiation; don't call this from subclasses.'''
62 '''Prevent instantiation; don't call this from subclasses.'''
55 raise NotImplementedError('attempted instantiating ' + str(type(self)))
63 raise NotImplementedError('attempted instantiating ' + str(type(self)))
56
64
57 def __call__(self, path, mode=b'rb', **kwargs):
65 def __call__(self, path, mode=b'rb', **kwargs):
58 raise NotImplementedError
66 raise NotImplementedError
59
67
60 def _auditpath(self, path, mode):
68 def _auditpath(self, path, mode):
61 raise NotImplementedError
69 raise NotImplementedError
62
70
63 def join(self, path, *insidef):
71 def join(self, path, *insidef):
64 raise NotImplementedError
72 raise NotImplementedError
65
73
66 def tryread(self, path):
74 def tryread(self, path):
67 '''gracefully return an empty string for missing files'''
75 '''gracefully return an empty string for missing files'''
68 try:
76 try:
69 return self.read(path)
77 return self.read(path)
70 except IOError as inst:
78 except IOError as inst:
71 if inst.errno != errno.ENOENT:
79 if inst.errno != errno.ENOENT:
72 raise
80 raise
73 return b""
81 return b""
74
82
75 def tryreadlines(self, path, mode=b'rb'):
83 def tryreadlines(self, path, mode=b'rb'):
76 '''gracefully return an empty array for missing files'''
84 '''gracefully return an empty array for missing files'''
77 try:
85 try:
78 return self.readlines(path, mode=mode)
86 return self.readlines(path, mode=mode)
79 except IOError as inst:
87 except IOError as inst:
80 if inst.errno != errno.ENOENT:
88 if inst.errno != errno.ENOENT:
81 raise
89 raise
82 return []
90 return []
83
91
84 @util.propertycache
92 @util.propertycache
85 def open(self):
93 def open(self):
86 """Open ``path`` file, which is relative to vfs root.
94 """Open ``path`` file, which is relative to vfs root.
87
95
88 Newly created directories are marked as "not to be indexed by
96 Newly created directories are marked as "not to be indexed by
89 the content indexing service", if ``notindexed`` is specified
97 the content indexing service", if ``notindexed`` is specified
90 for "write" mode access.
98 for "write" mode access.
91 """
99 """
92 return self.__call__
100 return self.__call__
93
101
94 def read(self, path):
102 def read(self, path):
95 with self(path, b'rb') as fp:
103 with self(path, b'rb') as fp:
96 return fp.read()
104 return fp.read()
97
105
98 def readlines(self, path, mode=b'rb'):
106 def readlines(self, path, mode=b'rb'):
99 with self(path, mode=mode) as fp:
107 with self(path, mode=mode) as fp:
100 return fp.readlines()
108 return fp.readlines()
101
109
102 def write(self, path, data, backgroundclose=False, **kwargs):
110 def write(self, path, data, backgroundclose=False, **kwargs):
103 with self(path, b'wb', backgroundclose=backgroundclose, **kwargs) as fp:
111 with self(path, b'wb', backgroundclose=backgroundclose, **kwargs) as fp:
104 return fp.write(data)
112 return fp.write(data)
105
113
106 def writelines(self, path, data, mode=b'wb', notindexed=False):
114 def writelines(self, path, data, mode=b'wb', notindexed=False):
107 with self(path, mode=mode, notindexed=notindexed) as fp:
115 with self(path, mode=mode, notindexed=notindexed) as fp:
108 return fp.writelines(data)
116 return fp.writelines(data)
109
117
110 def append(self, path, data):
118 def append(self, path, data):
111 with self(path, b'ab') as fp:
119 with self(path, b'ab') as fp:
112 return fp.write(data)
120 return fp.write(data)
113
121
114 def basename(self, path):
122 def basename(self, path):
115 """return base element of a path (as os.path.basename would do)
123 """return base element of a path (as os.path.basename would do)
116
124
117 This exists to allow handling of strange encoding if needed."""
125 This exists to allow handling of strange encoding if needed."""
118 return os.path.basename(path)
126 return os.path.basename(path)
119
127
120 def chmod(self, path, mode):
128 def chmod(self, path, mode):
121 return os.chmod(self.join(path), mode)
129 return os.chmod(self.join(path), mode)
122
130
123 def dirname(self, path):
131 def dirname(self, path):
124 """return dirname element of a path (as os.path.dirname would do)
132 """return dirname element of a path (as os.path.dirname would do)
125
133
126 This exists to allow handling of strange encoding if needed."""
134 This exists to allow handling of strange encoding if needed."""
127 return os.path.dirname(path)
135 return os.path.dirname(path)
128
136
129 def exists(self, path=None):
137 def exists(self, path=None):
130 return os.path.exists(self.join(path))
138 return os.path.exists(self.join(path))
131
139
132 def fstat(self, fp):
140 def fstat(self, fp):
133 return util.fstat(fp)
141 return util.fstat(fp)
134
142
135 def isdir(self, path=None):
143 def isdir(self, path=None):
136 return os.path.isdir(self.join(path))
144 return os.path.isdir(self.join(path))
137
145
138 def isfile(self, path=None):
146 def isfile(self, path=None):
139 return os.path.isfile(self.join(path))
147 return os.path.isfile(self.join(path))
140
148
141 def islink(self, path=None):
149 def islink(self, path=None):
142 return os.path.islink(self.join(path))
150 return os.path.islink(self.join(path))
143
151
144 def isfileorlink(self, path=None):
152 def isfileorlink(self, path=None):
145 """return whether path is a regular file or a symlink
153 """return whether path is a regular file or a symlink
146
154
147 Unlike isfile, this doesn't follow symlinks."""
155 Unlike isfile, this doesn't follow symlinks."""
148 try:
156 try:
149 st = self.lstat(path)
157 st = self.lstat(path)
150 except OSError:
158 except OSError:
151 return False
159 return False
152 mode = st.st_mode
160 mode = st.st_mode
153 return stat.S_ISREG(mode) or stat.S_ISLNK(mode)
161 return stat.S_ISREG(mode) or stat.S_ISLNK(mode)
154
162
163 def _join(self, *paths):
164 root_idx = 0
165 for idx, p in enumerate(paths):
166 if os.path.isabs(p) or p.startswith(self._dir_sep):
167 root_idx = idx
168 if root_idx != 0:
169 paths = paths[root_idx:]
170 paths = [p for p in paths if p]
171 return self._dir_sep.join(paths)
172
155 def reljoin(self, *paths):
173 def reljoin(self, *paths):
156 """join various elements of a path together (as os.path.join would do)
174 """join various elements of a path together (as os.path.join would do)
157
175
158 The vfs base is not injected so that path stay relative. This exists
176 The vfs base is not injected so that path stay relative. This exists
159 to allow handling of strange encoding if needed."""
177 to allow handling of strange encoding if needed."""
160 return os.path.join(*paths)
178 return self._join(*paths)
161
179
162 def split(self, path):
180 def split(self, path):
163 """split top-most element of a path (as os.path.split would do)
181 """split top-most element of a path (as os.path.split would do)
164
182
165 This exists to allow handling of strange encoding if needed."""
183 This exists to allow handling of strange encoding if needed."""
166 return os.path.split(path)
184 return os.path.split(path)
167
185
168 def lexists(self, path=None):
186 def lexists(self, path=None):
169 return os.path.lexists(self.join(path))
187 return os.path.lexists(self.join(path))
170
188
171 def lstat(self, path=None):
189 def lstat(self, path=None):
172 return os.lstat(self.join(path))
190 return os.lstat(self.join(path))
173
191
174 def listdir(self, path=None):
192 def listdir(self, path=None):
175 return os.listdir(self.join(path))
193 return os.listdir(self.join(path))
176
194
177 def makedir(self, path=None, notindexed=True):
195 def makedir(self, path=None, notindexed=True):
178 return util.makedir(self.join(path), notindexed)
196 return util.makedir(self.join(path), notindexed)
179
197
180 def makedirs(self, path=None, mode=None):
198 def makedirs(self, path=None, mode=None):
181 return util.makedirs(self.join(path), mode)
199 return util.makedirs(self.join(path), mode)
182
200
183 def makelock(self, info, path):
201 def makelock(self, info, path):
184 return util.makelock(info, self.join(path))
202 return util.makelock(info, self.join(path))
185
203
186 def mkdir(self, path=None):
204 def mkdir(self, path=None):
187 return os.mkdir(self.join(path))
205 return os.mkdir(self.join(path))
188
206
189 def mkstemp(self, suffix=b'', prefix=b'tmp', dir=None):
207 def mkstemp(self, suffix=b'', prefix=b'tmp', dir=None):
190 fd, name = pycompat.mkstemp(
208 fd, name = pycompat.mkstemp(
191 suffix=suffix, prefix=prefix, dir=self.join(dir)
209 suffix=suffix, prefix=prefix, dir=self.join(dir)
192 )
210 )
193 dname, fname = util.split(name)
211 dname, fname = util.split(name)
194 if dir:
212 if dir:
195 return fd, os.path.join(dir, fname)
213 return fd, os.path.join(dir, fname)
196 else:
214 else:
197 return fd, fname
215 return fd, fname
198
216
199 def readdir(self, path=None, stat=None, skip=None):
217 def readdir(self, path=None, stat=None, skip=None):
200 return util.listdir(self.join(path), stat, skip)
218 return util.listdir(self.join(path), stat, skip)
201
219
202 def readlock(self, path):
220 def readlock(self, path):
203 return util.readlock(self.join(path))
221 return util.readlock(self.join(path))
204
222
205 def rename(self, src, dst, checkambig=False):
223 def rename(self, src, dst, checkambig=False):
206 """Rename from src to dst
224 """Rename from src to dst
207
225
208 checkambig argument is used with util.filestat, and is useful
226 checkambig argument is used with util.filestat, and is useful
209 only if destination file is guarded by any lock
227 only if destination file is guarded by any lock
210 (e.g. repo.lock or repo.wlock).
228 (e.g. repo.lock or repo.wlock).
211
229
212 To avoid file stat ambiguity forcibly, checkambig=True involves
230 To avoid file stat ambiguity forcibly, checkambig=True involves
213 copying ``src`` file, if it is owned by another. Therefore, use
231 copying ``src`` file, if it is owned by another. Therefore, use
214 checkambig=True only in limited cases (see also issue5418 and
232 checkambig=True only in limited cases (see also issue5418 and
215 issue5584 for detail).
233 issue5584 for detail).
216 """
234 """
217 self._auditpath(dst, b'w')
235 self._auditpath(dst, b'w')
218 srcpath = self.join(src)
236 srcpath = self.join(src)
219 dstpath = self.join(dst)
237 dstpath = self.join(dst)
220 oldstat = checkambig and util.filestat.frompath(dstpath)
238 oldstat = checkambig and util.filestat.frompath(dstpath)
221 if oldstat and oldstat.stat:
239 if oldstat and oldstat.stat:
222 ret = util.rename(srcpath, dstpath)
240 ret = util.rename(srcpath, dstpath)
223 _avoidambig(dstpath, oldstat)
241 _avoidambig(dstpath, oldstat)
224 return ret
242 return ret
225 return util.rename(srcpath, dstpath)
243 return util.rename(srcpath, dstpath)
226
244
227 def readlink(self, path):
245 def readlink(self, path):
228 return util.readlink(self.join(path))
246 return util.readlink(self.join(path))
229
247
230 def removedirs(self, path=None):
248 def removedirs(self, path=None):
231 """Remove a leaf directory and all empty intermediate ones"""
249 """Remove a leaf directory and all empty intermediate ones"""
232 return util.removedirs(self.join(path))
250 return util.removedirs(self.join(path))
233
251
234 def rmdir(self, path=None):
252 def rmdir(self, path=None):
235 """Remove an empty directory."""
253 """Remove an empty directory."""
236 return os.rmdir(self.join(path))
254 return os.rmdir(self.join(path))
237
255
238 def rmtree(self, path=None, ignore_errors=False, forcibly=False):
256 def rmtree(self, path=None, ignore_errors=False, forcibly=False):
239 """Remove a directory tree recursively
257 """Remove a directory tree recursively
240
258
241 If ``forcibly``, this tries to remove READ-ONLY files, too.
259 If ``forcibly``, this tries to remove READ-ONLY files, too.
242 """
260 """
243 if forcibly:
261 if forcibly:
244
262
245 def onerror(function, path, excinfo):
263 def onerror(function, path, excinfo):
246 if function is not os.remove:
264 if function is not os.remove:
247 raise
265 raise
248 # read-only files cannot be unlinked under Windows
266 # read-only files cannot be unlinked under Windows
249 s = os.stat(path)
267 s = os.stat(path)
250 if (s.st_mode & stat.S_IWRITE) != 0:
268 if (s.st_mode & stat.S_IWRITE) != 0:
251 raise
269 raise
252 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
270 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
253 os.remove(path)
271 os.remove(path)
254
272
255 else:
273 else:
256 onerror = None
274 onerror = None
257 return shutil.rmtree(
275 return shutil.rmtree(
258 self.join(path), ignore_errors=ignore_errors, onerror=onerror
276 self.join(path), ignore_errors=ignore_errors, onerror=onerror
259 )
277 )
260
278
261 def setflags(self, path, l, x):
279 def setflags(self, path, l, x):
262 return util.setflags(self.join(path), l, x)
280 return util.setflags(self.join(path), l, x)
263
281
264 def stat(self, path=None):
282 def stat(self, path=None):
265 return os.stat(self.join(path))
283 return os.stat(self.join(path))
266
284
267 def unlink(self, path=None):
285 def unlink(self, path=None):
268 return util.unlink(self.join(path))
286 return util.unlink(self.join(path))
269
287
270 def tryunlink(self, path=None):
288 def tryunlink(self, path=None):
271 """Attempt to remove a file, ignoring missing file errors."""
289 """Attempt to remove a file, ignoring missing file errors."""
272 util.tryunlink(self.join(path))
290 util.tryunlink(self.join(path))
273
291
274 def unlinkpath(self, path=None, ignoremissing=False, rmdir=True):
292 def unlinkpath(self, path=None, ignoremissing=False, rmdir=True):
275 return util.unlinkpath(
293 return util.unlinkpath(
276 self.join(path), ignoremissing=ignoremissing, rmdir=rmdir
294 self.join(path), ignoremissing=ignoremissing, rmdir=rmdir
277 )
295 )
278
296
279 def utime(self, path=None, t=None):
297 def utime(self, path=None, t=None):
280 return os.utime(self.join(path), t)
298 return os.utime(self.join(path), t)
281
299
282 def walk(self, path=None, onerror=None):
300 def walk(self, path=None, onerror=None):
283 """Yield (dirpath, dirs, files) tuple for each directories under path
301 """Yield (dirpath, dirs, files) tuple for each directories under path
284
302
285 ``dirpath`` is relative one from the root of this vfs. This
303 ``dirpath`` is relative one from the root of this vfs. This
286 uses ``os.sep`` as path separator, even you specify POSIX
304 uses ``os.sep`` as path separator, even you specify POSIX
287 style ``path``.
305 style ``path``.
288
306
289 "The root of this vfs" is represented as empty ``dirpath``.
307 "The root of this vfs" is represented as empty ``dirpath``.
290 """
308 """
291 root = os.path.normpath(self.join(None))
309 root = os.path.normpath(self.join(None))
292 # when dirpath == root, dirpath[prefixlen:] becomes empty
310 # when dirpath == root, dirpath[prefixlen:] becomes empty
293 # because len(dirpath) < prefixlen.
311 # because len(dirpath) < prefixlen.
294 prefixlen = len(pathutil.normasprefix(root))
312 prefixlen = len(pathutil.normasprefix(root))
295 for dirpath, dirs, files in os.walk(self.join(path), onerror=onerror):
313 for dirpath, dirs, files in os.walk(self.join(path), onerror=onerror):
296 yield (dirpath[prefixlen:], dirs, files)
314 yield (dirpath[prefixlen:], dirs, files)
297
315
298 @contextlib.contextmanager
316 @contextlib.contextmanager
299 def backgroundclosing(self, ui, expectedcount=-1):
317 def backgroundclosing(self, ui, expectedcount=-1):
300 """Allow files to be closed asynchronously.
318 """Allow files to be closed asynchronously.
301
319
302 When this context manager is active, ``backgroundclose`` can be passed
320 When this context manager is active, ``backgroundclose`` can be passed
303 to ``__call__``/``open`` to result in the file possibly being closed
321 to ``__call__``/``open`` to result in the file possibly being closed
304 asynchronously, on a background thread.
322 asynchronously, on a background thread.
305 """
323 """
306 # Sharing backgroundfilecloser between threads is complex and using
324 # Sharing backgroundfilecloser between threads is complex and using
307 # multiple instances puts us at risk of running out of file descriptors
325 # multiple instances puts us at risk of running out of file descriptors
308 # only allow to use backgroundfilecloser when in main thread.
326 # only allow to use backgroundfilecloser when in main thread.
309 if not isinstance(
327 if not isinstance(
310 threading.current_thread(),
328 threading.current_thread(),
311 threading._MainThread, # pytype: disable=module-attr
329 threading._MainThread, # pytype: disable=module-attr
312 ):
330 ):
313 yield
331 yield
314 return
332 return
315 vfs = getattr(self, 'vfs', self)
333 vfs = getattr(self, 'vfs', self)
316 if getattr(vfs, '_backgroundfilecloser', None):
334 if getattr(vfs, '_backgroundfilecloser', None):
317 raise error.Abort(
335 raise error.Abort(
318 _(b'can only have 1 active background file closer')
336 _(b'can only have 1 active background file closer')
319 )
337 )
320
338
321 with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
339 with backgroundfilecloser(ui, expectedcount=expectedcount) as bfc:
322 try:
340 try:
323 vfs._backgroundfilecloser = (
341 vfs._backgroundfilecloser = (
324 bfc # pytype: disable=attribute-error
342 bfc # pytype: disable=attribute-error
325 )
343 )
326 yield bfc
344 yield bfc
327 finally:
345 finally:
328 vfs._backgroundfilecloser = (
346 vfs._backgroundfilecloser = (
329 None # pytype: disable=attribute-error
347 None # pytype: disable=attribute-error
330 )
348 )
331
349
332 def register_file(self, path):
350 def register_file(self, path):
333 """generic hook point to lets fncache steer its stew"""
351 """generic hook point to lets fncache steer its stew"""
334
352
335
353
336 class vfs(abstractvfs):
354 class vfs(abstractvfs):
337 """Operate files relative to a base directory
355 """Operate files relative to a base directory
338
356
339 This class is used to hide the details of COW semantics and
357 This class is used to hide the details of COW semantics and
340 remote file access from higher level code.
358 remote file access from higher level code.
341
359
342 'cacheaudited' should be enabled only if (a) vfs object is short-lived, or
360 'cacheaudited' should be enabled only if (a) vfs object is short-lived, or
343 (b) the base directory is managed by hg and considered sort-of append-only.
361 (b) the base directory is managed by hg and considered sort-of append-only.
344 See pathutil.pathauditor() for details.
362 See pathutil.pathauditor() for details.
345 """
363 """
346
364
347 def __init__(
365 def __init__(
348 self,
366 self,
349 base,
367 base,
350 audit=True,
368 audit=True,
351 cacheaudited=False,
369 cacheaudited=False,
352 expandpath=False,
370 expandpath=False,
353 realpath=False,
371 realpath=False,
354 ):
372 ):
355 if expandpath:
373 if expandpath:
356 base = util.expandpath(base)
374 base = util.expandpath(base)
357 if realpath:
375 if realpath:
358 base = os.path.realpath(base)
376 base = os.path.realpath(base)
359 self.base = base
377 self.base = base
360 self._audit = audit
378 self._audit = audit
361 if audit:
379 if audit:
362 self.audit = pathutil.pathauditor(self.base, cached=cacheaudited)
380 self.audit = pathutil.pathauditor(self.base, cached=cacheaudited)
363 else:
381 else:
364 self.audit = lambda path, mode=None: True
382 self.audit = lambda path, mode=None: True
365 self.createmode = None
383 self.createmode = None
366 self._trustnlink = None
384 self._trustnlink = None
367 self.options = {}
385 self.options = {}
368
386
369 @util.propertycache
387 @util.propertycache
370 def _cansymlink(self):
388 def _cansymlink(self):
371 return util.checklink(self.base)
389 return util.checklink(self.base)
372
390
373 @util.propertycache
391 @util.propertycache
374 def _chmod(self):
392 def _chmod(self):
375 return util.checkexec(self.base)
393 return util.checkexec(self.base)
376
394
377 def _fixfilemode(self, name):
395 def _fixfilemode(self, name):
378 if self.createmode is None or not self._chmod:
396 if self.createmode is None or not self._chmod:
379 return
397 return
380 os.chmod(name, self.createmode & 0o666)
398 os.chmod(name, self.createmode & 0o666)
381
399
382 def _auditpath(self, path, mode):
400 def _auditpath(self, path, mode):
383 if self._audit:
401 if self._audit:
384 if os.path.isabs(path) and path.startswith(self.base):
402 if os.path.isabs(path) and path.startswith(self.base):
385 path = os.path.relpath(path, self.base)
403 path = os.path.relpath(path, self.base)
386 r = util.checkosfilename(path)
404 r = util.checkosfilename(path)
387 if r:
405 if r:
388 raise error.Abort(b"%s: %r" % (r, path))
406 raise error.Abort(b"%s: %r" % (r, path))
389 self.audit(path, mode=mode)
407 self.audit(path, mode=mode)
390
408
391 def __call__(
409 def __call__(
392 self,
410 self,
393 path,
411 path,
394 mode=b"r",
412 mode=b"r",
395 atomictemp=False,
413 atomictemp=False,
396 notindexed=False,
414 notindexed=False,
397 backgroundclose=False,
415 backgroundclose=False,
398 checkambig=False,
416 checkambig=False,
399 auditpath=True,
417 auditpath=True,
400 makeparentdirs=True,
418 makeparentdirs=True,
401 ):
419 ):
402 """Open ``path`` file, which is relative to vfs root.
420 """Open ``path`` file, which is relative to vfs root.
403
421
404 By default, parent directories are created as needed. Newly created
422 By default, parent directories are created as needed. Newly created
405 directories are marked as "not to be indexed by the content indexing
423 directories are marked as "not to be indexed by the content indexing
406 service", if ``notindexed`` is specified for "write" mode access.
424 service", if ``notindexed`` is specified for "write" mode access.
407 Set ``makeparentdirs=False`` to not create directories implicitly.
425 Set ``makeparentdirs=False`` to not create directories implicitly.
408
426
409 If ``backgroundclose`` is passed, the file may be closed asynchronously.
427 If ``backgroundclose`` is passed, the file may be closed asynchronously.
410 It can only be used if the ``self.backgroundclosing()`` context manager
428 It can only be used if the ``self.backgroundclosing()`` context manager
411 is active. This should only be specified if the following criteria hold:
429 is active. This should only be specified if the following criteria hold:
412
430
413 1. There is a potential for writing thousands of files. Unless you
431 1. There is a potential for writing thousands of files. Unless you
414 are writing thousands of files, the performance benefits of
432 are writing thousands of files, the performance benefits of
415 asynchronously closing files is not realized.
433 asynchronously closing files is not realized.
416 2. Files are opened exactly once for the ``backgroundclosing``
434 2. Files are opened exactly once for the ``backgroundclosing``
417 active duration and are therefore free of race conditions between
435 active duration and are therefore free of race conditions between
418 closing a file on a background thread and reopening it. (If the
436 closing a file on a background thread and reopening it. (If the
419 file were opened multiple times, there could be unflushed data
437 file were opened multiple times, there could be unflushed data
420 because the original file handle hasn't been flushed/closed yet.)
438 because the original file handle hasn't been flushed/closed yet.)
421
439
422 ``checkambig`` argument is passed to atomictempfile (valid
440 ``checkambig`` argument is passed to atomictempfile (valid
423 only for writing), and is useful only if target file is
441 only for writing), and is useful only if target file is
424 guarded by any lock (e.g. repo.lock or repo.wlock).
442 guarded by any lock (e.g. repo.lock or repo.wlock).
425
443
426 To avoid file stat ambiguity forcibly, checkambig=True involves
444 To avoid file stat ambiguity forcibly, checkambig=True involves
427 copying ``path`` file opened in "append" mode (e.g. for
445 copying ``path`` file opened in "append" mode (e.g. for
428 truncation), if it is owned by another. Therefore, use
446 truncation), if it is owned by another. Therefore, use
429 combination of append mode and checkambig=True only in limited
447 combination of append mode and checkambig=True only in limited
430 cases (see also issue5418 and issue5584 for detail).
448 cases (see also issue5418 and issue5584 for detail).
431 """
449 """
432 if auditpath:
450 if auditpath:
433 self._auditpath(path, mode)
451 self._auditpath(path, mode)
434 f = self.join(path)
452 f = self.join(path)
435
453
436 if b"b" not in mode:
454 if b"b" not in mode:
437 mode += b"b" # for that other OS
455 mode += b"b" # for that other OS
438
456
439 nlink = -1
457 nlink = -1
440 if mode not in (b'r', b'rb'):
458 if mode not in (b'r', b'rb'):
441 dirname, basename = util.split(f)
459 dirname, basename = util.split(f)
442 # If basename is empty, then the path is malformed because it points
460 # If basename is empty, then the path is malformed because it points
443 # to a directory. Let the posixfile() call below raise IOError.
461 # to a directory. Let the posixfile() call below raise IOError.
444 if basename:
462 if basename:
445 if atomictemp:
463 if atomictemp:
446 if makeparentdirs:
464 if makeparentdirs:
447 util.makedirs(dirname, self.createmode, notindexed)
465 util.makedirs(dirname, self.createmode, notindexed)
448 return util.atomictempfile(
466 return util.atomictempfile(
449 f, mode, self.createmode, checkambig=checkambig
467 f, mode, self.createmode, checkambig=checkambig
450 )
468 )
451 try:
469 try:
452 if b'w' in mode:
470 if b'w' in mode:
453 util.unlink(f)
471 util.unlink(f)
454 nlink = 0
472 nlink = 0
455 else:
473 else:
456 # nlinks() may behave differently for files on Windows
474 # nlinks() may behave differently for files on Windows
457 # shares if the file is open.
475 # shares if the file is open.
458 with util.posixfile(f):
476 with util.posixfile(f):
459 nlink = util.nlinks(f)
477 nlink = util.nlinks(f)
460 if nlink < 1:
478 if nlink < 1:
461 nlink = 2 # force mktempcopy (issue1922)
479 nlink = 2 # force mktempcopy (issue1922)
462 except (OSError, IOError) as e:
480 except (OSError, IOError) as e:
463 if e.errno != errno.ENOENT:
481 if e.errno != errno.ENOENT:
464 raise
482 raise
465 nlink = 0
483 nlink = 0
466 if makeparentdirs:
484 if makeparentdirs:
467 util.makedirs(dirname, self.createmode, notindexed)
485 util.makedirs(dirname, self.createmode, notindexed)
468 if nlink > 0:
486 if nlink > 0:
469 if self._trustnlink is None:
487 if self._trustnlink is None:
470 self._trustnlink = nlink > 1 or util.checknlink(f)
488 self._trustnlink = nlink > 1 or util.checknlink(f)
471 if nlink > 1 or not self._trustnlink:
489 if nlink > 1 or not self._trustnlink:
472 util.rename(util.mktempcopy(f), f)
490 util.rename(util.mktempcopy(f), f)
473 fp = util.posixfile(f, mode)
491 fp = util.posixfile(f, mode)
474 if nlink == 0:
492 if nlink == 0:
475 self._fixfilemode(f)
493 self._fixfilemode(f)
476
494
477 if checkambig:
495 if checkambig:
478 if mode in (b'r', b'rb'):
496 if mode in (b'r', b'rb'):
479 raise error.Abort(
497 raise error.Abort(
480 _(
498 _(
481 b'implementation error: mode %s is not'
499 b'implementation error: mode %s is not'
482 b' valid for checkambig=True'
500 b' valid for checkambig=True'
483 )
501 )
484 % mode
502 % mode
485 )
503 )
486 fp = checkambigatclosing(fp)
504 fp = checkambigatclosing(fp)
487
505
488 if backgroundclose and isinstance(
506 if backgroundclose and isinstance(
489 threading.current_thread(),
507 threading.current_thread(),
490 threading._MainThread, # pytype: disable=module-attr
508 threading._MainThread, # pytype: disable=module-attr
491 ):
509 ):
492 if (
510 if (
493 not self._backgroundfilecloser # pytype: disable=attribute-error
511 not self._backgroundfilecloser # pytype: disable=attribute-error
494 ):
512 ):
495 raise error.Abort(
513 raise error.Abort(
496 _(
514 _(
497 b'backgroundclose can only be used when a '
515 b'backgroundclose can only be used when a '
498 b'backgroundclosing context manager is active'
516 b'backgroundclosing context manager is active'
499 )
517 )
500 )
518 )
501
519
502 fp = delayclosedfile(
520 fp = delayclosedfile(
503 fp,
521 fp,
504 self._backgroundfilecloser, # pytype: disable=attribute-error
522 self._backgroundfilecloser, # pytype: disable=attribute-error
505 )
523 )
506
524
507 return fp
525 return fp
508
526
509 def symlink(self, src, dst):
527 def symlink(self, src, dst):
510 self.audit(dst)
528 self.audit(dst)
511 linkname = self.join(dst)
529 linkname = self.join(dst)
512 util.tryunlink(linkname)
530 util.tryunlink(linkname)
513
531
514 util.makedirs(os.path.dirname(linkname), self.createmode)
532 util.makedirs(os.path.dirname(linkname), self.createmode)
515
533
516 if self._cansymlink:
534 if self._cansymlink:
517 try:
535 try:
518 os.symlink(src, linkname)
536 os.symlink(src, linkname)
519 except OSError as err:
537 except OSError as err:
520 raise OSError(
538 raise OSError(
521 err.errno,
539 err.errno,
522 _(b'could not symlink to %r: %s')
540 _(b'could not symlink to %r: %s')
523 % (src, encoding.strtolocal(err.strerror)),
541 % (src, encoding.strtolocal(err.strerror)),
524 linkname,
542 linkname,
525 )
543 )
526 else:
544 else:
527 self.write(dst, src)
545 self.write(dst, src)
528
546
529 def join(self, path, *insidef):
547 def join(self, path, *insidef):
530 if path:
548 if path:
531 return os.path.join(self.base, path, *insidef)
549 parts = [self.base, path]
550 parts.extend(insidef)
551 return self._join(*parts)
532 else:
552 else:
533 return self.base
553 return self.base
534
554
535
555
536 opener = vfs
556 opener = vfs
537
557
538
558
539 class proxyvfs(abstractvfs):
559 class proxyvfs(abstractvfs):
540 def __init__(self, vfs):
560 def __init__(self, vfs):
541 self.vfs = vfs
561 self.vfs = vfs
542
562
543 def _auditpath(self, path, mode):
563 def _auditpath(self, path, mode):
544 return self.vfs._auditpath(path, mode)
564 return self.vfs._auditpath(path, mode)
545
565
546 @property
566 @property
547 def options(self):
567 def options(self):
548 return self.vfs.options
568 return self.vfs.options
549
569
550 @options.setter
570 @options.setter
551 def options(self, value):
571 def options(self, value):
552 self.vfs.options = value
572 self.vfs.options = value
553
573
554
574
555 class filtervfs(proxyvfs, abstractvfs):
575 class filtervfs(proxyvfs, abstractvfs):
556 '''Wrapper vfs for filtering filenames with a function.'''
576 '''Wrapper vfs for filtering filenames with a function.'''
557
577
558 def __init__(self, vfs, filter):
578 def __init__(self, vfs, filter):
559 proxyvfs.__init__(self, vfs)
579 proxyvfs.__init__(self, vfs)
560 self._filter = filter
580 self._filter = filter
561
581
562 def __call__(self, path, *args, **kwargs):
582 def __call__(self, path, *args, **kwargs):
563 return self.vfs(self._filter(path), *args, **kwargs)
583 return self.vfs(self._filter(path), *args, **kwargs)
564
584
565 def join(self, path, *insidef):
585 def join(self, path, *insidef):
566 if path:
586 if path:
567 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
587 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
568 else:
588 else:
569 return self.vfs.join(path)
589 return self.vfs.join(path)
570
590
571
591
572 filteropener = filtervfs
592 filteropener = filtervfs
573
593
574
594
575 class readonlyvfs(proxyvfs):
595 class readonlyvfs(proxyvfs):
576 '''Wrapper vfs preventing any writing.'''
596 '''Wrapper vfs preventing any writing.'''
577
597
578 def __init__(self, vfs):
598 def __init__(self, vfs):
579 proxyvfs.__init__(self, vfs)
599 proxyvfs.__init__(self, vfs)
580
600
581 def __call__(self, path, mode=b'r', *args, **kw):
601 def __call__(self, path, mode=b'r', *args, **kw):
582 if mode not in (b'r', b'rb'):
602 if mode not in (b'r', b'rb'):
583 raise error.Abort(_(b'this vfs is read only'))
603 raise error.Abort(_(b'this vfs is read only'))
584 return self.vfs(path, mode, *args, **kw)
604 return self.vfs(path, mode, *args, **kw)
585
605
586 def join(self, path, *insidef):
606 def join(self, path, *insidef):
587 return self.vfs.join(path, *insidef)
607 return self.vfs.join(path, *insidef)
588
608
589
609
590 class closewrapbase(object):
610 class closewrapbase(object):
591 """Base class of wrapper, which hooks closing
611 """Base class of wrapper, which hooks closing
592
612
593 Do not instantiate outside of the vfs layer.
613 Do not instantiate outside of the vfs layer.
594 """
614 """
595
615
596 def __init__(self, fh):
616 def __init__(self, fh):
597 object.__setattr__(self, '_origfh', fh)
617 object.__setattr__(self, '_origfh', fh)
598
618
599 def __getattr__(self, attr):
619 def __getattr__(self, attr):
600 return getattr(self._origfh, attr)
620 return getattr(self._origfh, attr)
601
621
602 def __setattr__(self, attr, value):
622 def __setattr__(self, attr, value):
603 return setattr(self._origfh, attr, value)
623 return setattr(self._origfh, attr, value)
604
624
605 def __delattr__(self, attr):
625 def __delattr__(self, attr):
606 return delattr(self._origfh, attr)
626 return delattr(self._origfh, attr)
607
627
608 def __enter__(self):
628 def __enter__(self):
609 self._origfh.__enter__()
629 self._origfh.__enter__()
610 return self
630 return self
611
631
612 def __exit__(self, exc_type, exc_value, exc_tb):
632 def __exit__(self, exc_type, exc_value, exc_tb):
613 raise NotImplementedError('attempted instantiating ' + str(type(self)))
633 raise NotImplementedError('attempted instantiating ' + str(type(self)))
614
634
615 def close(self):
635 def close(self):
616 raise NotImplementedError('attempted instantiating ' + str(type(self)))
636 raise NotImplementedError('attempted instantiating ' + str(type(self)))
617
637
618
638
619 class delayclosedfile(closewrapbase):
639 class delayclosedfile(closewrapbase):
620 """Proxy for a file object whose close is delayed.
640 """Proxy for a file object whose close is delayed.
621
641
622 Do not instantiate outside of the vfs layer.
642 Do not instantiate outside of the vfs layer.
623 """
643 """
624
644
625 def __init__(self, fh, closer):
645 def __init__(self, fh, closer):
626 super(delayclosedfile, self).__init__(fh)
646 super(delayclosedfile, self).__init__(fh)
627 object.__setattr__(self, '_closer', closer)
647 object.__setattr__(self, '_closer', closer)
628
648
629 def __exit__(self, exc_type, exc_value, exc_tb):
649 def __exit__(self, exc_type, exc_value, exc_tb):
630 self._closer.close(self._origfh)
650 self._closer.close(self._origfh)
631
651
632 def close(self):
652 def close(self):
633 self._closer.close(self._origfh)
653 self._closer.close(self._origfh)
634
654
635
655
636 class backgroundfilecloser(object):
656 class backgroundfilecloser(object):
637 """Coordinates background closing of file handles on multiple threads."""
657 """Coordinates background closing of file handles on multiple threads."""
638
658
639 def __init__(self, ui, expectedcount=-1):
659 def __init__(self, ui, expectedcount=-1):
640 self._running = False
660 self._running = False
641 self._entered = False
661 self._entered = False
642 self._threads = []
662 self._threads = []
643 self._threadexception = None
663 self._threadexception = None
644
664
645 # Only Windows/NTFS has slow file closing. So only enable by default
665 # Only Windows/NTFS has slow file closing. So only enable by default
646 # on that platform. But allow to be enabled elsewhere for testing.
666 # on that platform. But allow to be enabled elsewhere for testing.
647 defaultenabled = pycompat.iswindows
667 defaultenabled = pycompat.iswindows
648 enabled = ui.configbool(b'worker', b'backgroundclose', defaultenabled)
668 enabled = ui.configbool(b'worker', b'backgroundclose', defaultenabled)
649
669
650 if not enabled:
670 if not enabled:
651 return
671 return
652
672
653 # There is overhead to starting and stopping the background threads.
673 # There is overhead to starting and stopping the background threads.
654 # Don't do background processing unless the file count is large enough
674 # Don't do background processing unless the file count is large enough
655 # to justify it.
675 # to justify it.
656 minfilecount = ui.configint(b'worker', b'backgroundcloseminfilecount')
676 minfilecount = ui.configint(b'worker', b'backgroundcloseminfilecount')
657 # FUTURE dynamically start background threads after minfilecount closes.
677 # FUTURE dynamically start background threads after minfilecount closes.
658 # (We don't currently have any callers that don't know their file count)
678 # (We don't currently have any callers that don't know their file count)
659 if expectedcount > 0 and expectedcount < minfilecount:
679 if expectedcount > 0 and expectedcount < minfilecount:
660 return
680 return
661
681
662 maxqueue = ui.configint(b'worker', b'backgroundclosemaxqueue')
682 maxqueue = ui.configint(b'worker', b'backgroundclosemaxqueue')
663 threadcount = ui.configint(b'worker', b'backgroundclosethreadcount')
683 threadcount = ui.configint(b'worker', b'backgroundclosethreadcount')
664
684
665 ui.debug(
685 ui.debug(
666 b'starting %d threads for background file closing\n' % threadcount
686 b'starting %d threads for background file closing\n' % threadcount
667 )
687 )
668
688
669 self._queue = pycompat.queue.Queue(maxsize=maxqueue)
689 self._queue = pycompat.queue.Queue(maxsize=maxqueue)
670 self._running = True
690 self._running = True
671
691
672 for i in range(threadcount):
692 for i in range(threadcount):
673 t = threading.Thread(target=self._worker, name='backgroundcloser')
693 t = threading.Thread(target=self._worker, name='backgroundcloser')
674 self._threads.append(t)
694 self._threads.append(t)
675 t.start()
695 t.start()
676
696
677 def __enter__(self):
697 def __enter__(self):
678 self._entered = True
698 self._entered = True
679 return self
699 return self
680
700
681 def __exit__(self, exc_type, exc_value, exc_tb):
701 def __exit__(self, exc_type, exc_value, exc_tb):
682 self._running = False
702 self._running = False
683
703
684 # Wait for threads to finish closing so open files don't linger for
704 # Wait for threads to finish closing so open files don't linger for
685 # longer than lifetime of context manager.
705 # longer than lifetime of context manager.
686 for t in self._threads:
706 for t in self._threads:
687 t.join()
707 t.join()
688
708
689 def _worker(self):
709 def _worker(self):
690 """Main routine for worker thread."""
710 """Main routine for worker thread."""
691 while True:
711 while True:
692 try:
712 try:
693 fh = self._queue.get(block=True, timeout=0.100)
713 fh = self._queue.get(block=True, timeout=0.100)
694 # Need to catch or the thread will terminate and
714 # Need to catch or the thread will terminate and
695 # we could orphan file descriptors.
715 # we could orphan file descriptors.
696 try:
716 try:
697 fh.close()
717 fh.close()
698 except Exception as e:
718 except Exception as e:
699 # Stash so can re-raise from main thread later.
719 # Stash so can re-raise from main thread later.
700 self._threadexception = e
720 self._threadexception = e
701 except pycompat.queue.Empty:
721 except pycompat.queue.Empty:
702 if not self._running:
722 if not self._running:
703 break
723 break
704
724
705 def close(self, fh):
725 def close(self, fh):
706 """Schedule a file for closing."""
726 """Schedule a file for closing."""
707 if not self._entered:
727 if not self._entered:
708 raise error.Abort(
728 raise error.Abort(
709 _(b'can only call close() when context manager active')
729 _(b'can only call close() when context manager active')
710 )
730 )
711
731
712 # If a background thread encountered an exception, raise now so we fail
732 # If a background thread encountered an exception, raise now so we fail
713 # fast. Otherwise we may potentially go on for minutes until the error
733 # fast. Otherwise we may potentially go on for minutes until the error
714 # is acted on.
734 # is acted on.
715 if self._threadexception:
735 if self._threadexception:
716 e = self._threadexception
736 e = self._threadexception
717 self._threadexception = None
737 self._threadexception = None
718 raise e
738 raise e
719
739
720 # If we're not actively running, close synchronously.
740 # If we're not actively running, close synchronously.
721 if not self._running:
741 if not self._running:
722 fh.close()
742 fh.close()
723 return
743 return
724
744
725 self._queue.put(fh, block=True, timeout=None)
745 self._queue.put(fh, block=True, timeout=None)
726
746
727
747
728 class checkambigatclosing(closewrapbase):
748 class checkambigatclosing(closewrapbase):
729 """Proxy for a file object, to avoid ambiguity of file stat
749 """Proxy for a file object, to avoid ambiguity of file stat
730
750
731 See also util.filestat for detail about "ambiguity of file stat".
751 See also util.filestat for detail about "ambiguity of file stat".
732
752
733 This proxy is useful only if the target file is guarded by any
753 This proxy is useful only if the target file is guarded by any
734 lock (e.g. repo.lock or repo.wlock)
754 lock (e.g. repo.lock or repo.wlock)
735
755
736 Do not instantiate outside of the vfs layer.
756 Do not instantiate outside of the vfs layer.
737 """
757 """
738
758
739 def __init__(self, fh):
759 def __init__(self, fh):
740 super(checkambigatclosing, self).__init__(fh)
760 super(checkambigatclosing, self).__init__(fh)
741 object.__setattr__(self, '_oldstat', util.filestat.frompath(fh.name))
761 object.__setattr__(self, '_oldstat', util.filestat.frompath(fh.name))
742
762
743 def _checkambig(self):
763 def _checkambig(self):
744 oldstat = self._oldstat
764 oldstat = self._oldstat
745 if oldstat.stat:
765 if oldstat.stat:
746 _avoidambig(self._origfh.name, oldstat)
766 _avoidambig(self._origfh.name, oldstat)
747
767
748 def __exit__(self, exc_type, exc_value, exc_tb):
768 def __exit__(self, exc_type, exc_value, exc_tb):
749 self._origfh.__exit__(exc_type, exc_value, exc_tb)
769 self._origfh.__exit__(exc_type, exc_value, exc_tb)
750 self._checkambig()
770 self._checkambig()
751
771
752 def close(self):
772 def close(self):
753 self._origfh.close()
773 self._origfh.close()
754 self._checkambig()
774 self._checkambig()
@@ -1,1202 +1,1202 b''
1 $ cat >> "$HGRCPATH" << EOF
1 $ cat >> "$HGRCPATH" << EOF
2 > [ui]
2 > [ui]
3 > merge = :merge3
3 > merge = :merge3
4 > EOF
4 > EOF
5
5
6 init
6 init
7
7
8 $ hg init repo
8 $ hg init repo
9 $ cd repo
9 $ cd repo
10
10
11 commit
11 commit
12
12
13 $ echo 'a' > a
13 $ echo 'a' > a
14 $ hg ci -A -m test -u nobody -d '1 0'
14 $ hg ci -A -m test -u nobody -d '1 0'
15 adding a
15 adding a
16
16
17 annotate -c
17 annotate -c
18
18
19 $ hg annotate -c a
19 $ hg annotate -c a
20 8435f90966e4: a
20 8435f90966e4: a
21
21
22 annotate -cl
22 annotate -cl
23
23
24 $ hg annotate -cl a
24 $ hg annotate -cl a
25 8435f90966e4:1: a
25 8435f90966e4:1: a
26
26
27 annotate -d
27 annotate -d
28
28
29 $ hg annotate -d a
29 $ hg annotate -d a
30 Thu Jan 01 00:00:01 1970 +0000: a
30 Thu Jan 01 00:00:01 1970 +0000: a
31
31
32 annotate -n
32 annotate -n
33
33
34 $ hg annotate -n a
34 $ hg annotate -n a
35 0: a
35 0: a
36
36
37 annotate -nl
37 annotate -nl
38
38
39 $ hg annotate -nl a
39 $ hg annotate -nl a
40 0:1: a
40 0:1: a
41
41
42 annotate -u
42 annotate -u
43
43
44 $ hg annotate -u a
44 $ hg annotate -u a
45 nobody: a
45 nobody: a
46
46
47 annotate -cdnu
47 annotate -cdnu
48
48
49 $ hg annotate -cdnu a
49 $ hg annotate -cdnu a
50 nobody 0 8435f90966e4 Thu Jan 01 00:00:01 1970 +0000: a
50 nobody 0 8435f90966e4 Thu Jan 01 00:00:01 1970 +0000: a
51
51
52 annotate -cdnul
52 annotate -cdnul
53
53
54 $ hg annotate -cdnul a
54 $ hg annotate -cdnul a
55 nobody 0 8435f90966e4 Thu Jan 01 00:00:01 1970 +0000:1: a
55 nobody 0 8435f90966e4 Thu Jan 01 00:00:01 1970 +0000:1: a
56
56
57 annotate (JSON)
57 annotate (JSON)
58
58
59 $ hg annotate -Tjson a
59 $ hg annotate -Tjson a
60 [
60 [
61 {
61 {
62 "lines": [{"line": "a\n", "rev": 0}],
62 "lines": [{"line": "a\n", "rev": 0}],
63 "path": "a"
63 "path": "a"
64 }
64 }
65 ]
65 ]
66
66
67 $ hg annotate -Tjson -cdfnul a
67 $ hg annotate -Tjson -cdfnul a
68 [
68 [
69 {
69 {
70 "lines": [{"date": [1.0, 0], "line": "a\n", "lineno": 1, "node": "8435f90966e442695d2ded29fdade2bac5ad8065", "path": "a", "rev": 0, "user": "nobody"}],
70 "lines": [{"date": [1.0, 0], "line": "a\n", "lineno": 1, "node": "8435f90966e442695d2ded29fdade2bac5ad8065", "path": "a", "rev": 0, "user": "nobody"}],
71 "path": "a"
71 "path": "a"
72 }
72 }
73 ]
73 ]
74
74
75 log-like templating
75 log-like templating
76
76
77 $ hg annotate -T'{lines % "{rev} {node|shortest}: {line}"}' a
77 $ hg annotate -T'{lines % "{rev} {node|shortest}: {line}"}' a
78 0 8435: a
78 0 8435: a
79
79
80 '{lineno}' field should be populated as necessary
80 '{lineno}' field should be populated as necessary
81
81
82 $ hg annotate -T'{lines % "{rev}:{lineno}: {line}"}' a
82 $ hg annotate -T'{lines % "{rev}:{lineno}: {line}"}' a
83 0:1: a
83 0:1: a
84 $ hg annotate -Ta a \
84 $ hg annotate -Ta a \
85 > --config templates.a='"{lines % "{rev}:{lineno}: {line}"}"'
85 > --config templates.a='"{lines % "{rev}:{lineno}: {line}"}"'
86 0:1: a
86 0:1: a
87
87
88 $ cat <<EOF >>a
88 $ cat <<EOF >>a
89 > a
89 > a
90 > a
90 > a
91 > EOF
91 > EOF
92 $ hg ci -ma1 -d '1 0'
92 $ hg ci -ma1 -d '1 0'
93 $ hg cp a b
93 $ hg cp a b
94 $ hg ci -mb -d '1 0'
94 $ hg ci -mb -d '1 0'
95 $ cat <<EOF >> b
95 $ cat <<EOF >> b
96 > b4
96 > b4
97 > b5
97 > b5
98 > b6
98 > b6
99 > EOF
99 > EOF
100 $ hg ci -mb2 -d '2 0'
100 $ hg ci -mb2 -d '2 0'
101
101
102 default output of '{lines}' should be readable
102 default output of '{lines}' should be readable
103
103
104 $ hg annotate -T'{lines}' a
104 $ hg annotate -T'{lines}' a
105 0: a
105 0: a
106 1: a
106 1: a
107 1: a
107 1: a
108 $ hg annotate -T'{join(lines, "\n")}' a
108 $ hg annotate -T'{join(lines, "\n")}' a
109 0: a
109 0: a
110
110
111 1: a
111 1: a
112
112
113 1: a
113 1: a
114
114
115 several filters can be applied to '{lines}'
115 several filters can be applied to '{lines}'
116
116
117 $ hg annotate -T'{lines|json}\n' a
117 $ hg annotate -T'{lines|json}\n' a
118 [{"line": "a\n", "rev": 0}, {"line": "a\n", "rev": 1}, {"line": "a\n", "rev": 1}]
118 [{"line": "a\n", "rev": 0}, {"line": "a\n", "rev": 1}, {"line": "a\n", "rev": 1}]
119 $ hg annotate -T'{lines|stringify}' a
119 $ hg annotate -T'{lines|stringify}' a
120 0: a
120 0: a
121 1: a
121 1: a
122 1: a
122 1: a
123 $ hg annotate -T'{lines|count}\n' a
123 $ hg annotate -T'{lines|count}\n' a
124 3
124 3
125
125
126 annotate multiple files (JSON)
126 annotate multiple files (JSON)
127
127
128 $ hg annotate -Tjson a b
128 $ hg annotate -Tjson a b
129 [
129 [
130 {
130 {
131 "lines": [{"line": "a\n", "rev": 0}, {"line": "a\n", "rev": 1}, {"line": "a\n", "rev": 1}],
131 "lines": [{"line": "a\n", "rev": 0}, {"line": "a\n", "rev": 1}, {"line": "a\n", "rev": 1}],
132 "path": "a"
132 "path": "a"
133 },
133 },
134 {
134 {
135 "lines": [{"line": "a\n", "rev": 0}, {"line": "a\n", "rev": 1}, {"line": "a\n", "rev": 1}, {"line": "b4\n", "rev": 3}, {"line": "b5\n", "rev": 3}, {"line": "b6\n", "rev": 3}],
135 "lines": [{"line": "a\n", "rev": 0}, {"line": "a\n", "rev": 1}, {"line": "a\n", "rev": 1}, {"line": "b4\n", "rev": 3}, {"line": "b5\n", "rev": 3}, {"line": "b6\n", "rev": 3}],
136 "path": "b"
136 "path": "b"
137 }
137 }
138 ]
138 ]
139
139
140 annotate multiple files (template)
140 annotate multiple files (template)
141
141
142 $ hg annotate -T'== {path} ==\n{lines % "{rev}: {line}"}' a b
142 $ hg annotate -T'== {path} ==\n{lines % "{rev}: {line}"}' a b
143 == a ==
143 == a ==
144 0: a
144 0: a
145 1: a
145 1: a
146 1: a
146 1: a
147 == b ==
147 == b ==
148 0: a
148 0: a
149 1: a
149 1: a
150 1: a
150 1: a
151 3: b4
151 3: b4
152 3: b5
152 3: b5
153 3: b6
153 3: b6
154
154
155 annotate -n b
155 annotate -n b
156
156
157 $ hg annotate -n b
157 $ hg annotate -n b
158 0: a
158 0: a
159 1: a
159 1: a
160 1: a
160 1: a
161 3: b4
161 3: b4
162 3: b5
162 3: b5
163 3: b6
163 3: b6
164
164
165 annotate --no-follow b
165 annotate --no-follow b
166
166
167 $ hg annotate --no-follow b
167 $ hg annotate --no-follow b
168 2: a
168 2: a
169 2: a
169 2: a
170 2: a
170 2: a
171 3: b4
171 3: b4
172 3: b5
172 3: b5
173 3: b6
173 3: b6
174
174
175 annotate -nl b
175 annotate -nl b
176
176
177 $ hg annotate -nl b
177 $ hg annotate -nl b
178 0:1: a
178 0:1: a
179 1:2: a
179 1:2: a
180 1:3: a
180 1:3: a
181 3:4: b4
181 3:4: b4
182 3:5: b5
182 3:5: b5
183 3:6: b6
183 3:6: b6
184
184
185 annotate -nf b
185 annotate -nf b
186
186
187 $ hg annotate -nf b
187 $ hg annotate -nf b
188 0 a: a
188 0 a: a
189 1 a: a
189 1 a: a
190 1 a: a
190 1 a: a
191 3 b: b4
191 3 b: b4
192 3 b: b5
192 3 b: b5
193 3 b: b6
193 3 b: b6
194
194
195 annotate -nlf b
195 annotate -nlf b
196
196
197 $ hg annotate -nlf b
197 $ hg annotate -nlf b
198 0 a:1: a
198 0 a:1: a
199 1 a:2: a
199 1 a:2: a
200 1 a:3: a
200 1 a:3: a
201 3 b:4: b4
201 3 b:4: b4
202 3 b:5: b5
202 3 b:5: b5
203 3 b:6: b6
203 3 b:6: b6
204
204
205 $ hg up -C 2
205 $ hg up -C 2
206 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
206 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
207 $ cat <<EOF >> b
207 $ cat <<EOF >> b
208 > b4
208 > b4
209 > c
209 > c
210 > b5
210 > b5
211 > EOF
211 > EOF
212 $ hg ci -mb2.1 -d '2 0'
212 $ hg ci -mb2.1 -d '2 0'
213 created new head
213 created new head
214 $ hg merge
214 $ hg merge
215 merging b
215 merging b
216 warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
216 warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
217 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
217 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
218 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
218 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
219 [1]
219 [1]
220 $ cat b
220 $ cat b
221 a
221 a
222 a
222 a
223 a
223 a
224 <<<<<<< working copy: 5fbdc1152d97 - test: b2.1
224 <<<<<<< working copy: 5fbdc1152d97 - test: b2.1
225 b4
225 b4
226 c
226 c
227 b5
227 b5
228 ||||||| base
228 ||||||| base
229 =======
229 =======
230 b4
230 b4
231 b5
231 b5
232 b6
232 b6
233 >>>>>>> merge rev: 37ec9f5c3d1f - test: b2
233 >>>>>>> merge rev: 37ec9f5c3d1f - test: b2
234 $ cat <<EOF > b
234 $ cat <<EOF > b
235 > a
235 > a
236 > a
236 > a
237 > a
237 > a
238 > b4
238 > b4
239 > c
239 > c
240 > b5
240 > b5
241 > EOF
241 > EOF
242 $ hg resolve --mark -q
242 $ hg resolve --mark -q
243 $ rm b.orig
243 $ rm b.orig
244 $ hg ci -mmergeb -d '3 0'
244 $ hg ci -mmergeb -d '3 0'
245
245
246 annotate after merge
246 annotate after merge
247
247
248 $ hg annotate -nf b
248 $ hg annotate -nf b
249 0 a: a
249 0 a: a
250 1 a: a
250 1 a: a
251 1 a: a
251 1 a: a
252 3 b: b4
252 3 b: b4
253 4 b: c
253 4 b: c
254 3 b: b5
254 3 b: b5
255
255
256 annotate after merge with -l
256 annotate after merge with -l
257
257
258 $ hg annotate -nlf b
258 $ hg annotate -nlf b
259 0 a:1: a
259 0 a:1: a
260 1 a:2: a
260 1 a:2: a
261 1 a:3: a
261 1 a:3: a
262 3 b:4: b4
262 3 b:4: b4
263 4 b:5: c
263 4 b:5: c
264 3 b:5: b5
264 3 b:5: b5
265
265
266 $ hg up -C 1
266 $ hg up -C 1
267 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
267 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
268 $ hg cp a b
268 $ hg cp a b
269 $ cat <<EOF > b
269 $ cat <<EOF > b
270 > a
270 > a
271 > z
271 > z
272 > a
272 > a
273 > EOF
273 > EOF
274 $ hg ci -mc -d '3 0'
274 $ hg ci -mc -d '3 0'
275 created new head
275 created new head
276 Work around the pure version not resolving the conflict like native code
276 Work around the pure version not resolving the conflict like native code
277 #if pure
277 #if pure
278 $ hg merge
278 $ hg merge
279 merging b
279 merging b
280 warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
280 warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
281 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
281 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
282 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
282 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
283 [1]
283 [1]
284 $ cat <<EOF > b
284 $ cat <<EOF > b
285 > a
285 > a
286 > z
286 > z
287 > a
287 > a
288 > b4
288 > b4
289 > c
289 > c
290 > b5
290 > b5
291 > EOF
291 > EOF
292 $ hg resolve -m b
292 $ hg resolve -m b
293 (no more unresolved files)
293 (no more unresolved files)
294 $ rm b.orig
294 $ rm b.orig
295 #else
295 #else
296 $ hg merge
296 $ hg merge
297 merging b
297 merging b
298 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
298 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
299 (branch merge, don't forget to commit)
299 (branch merge, don't forget to commit)
300 #endif
300 #endif
301 $ echo d >> b
301 $ echo d >> b
302 $ hg ci -mmerge2 -d '4 0'
302 $ hg ci -mmerge2 -d '4 0'
303
303
304 annotate after rename merge
304 annotate after rename merge
305
305
306 $ hg annotate -nf b
306 $ hg annotate -nf b
307 0 a: a
307 0 a: a
308 6 b: z
308 6 b: z
309 1 a: a
309 1 a: a
310 3 b: b4
310 3 b: b4
311 4 b: c
311 4 b: c
312 3 b: b5
312 3 b: b5
313 7 b: d
313 7 b: d
314
314
315 annotate after rename merge with -l
315 annotate after rename merge with -l
316
316
317 $ hg annotate -nlf b
317 $ hg annotate -nlf b
318 0 a:1: a
318 0 a:1: a
319 6 b:2: z
319 6 b:2: z
320 1 a:3: a
320 1 a:3: a
321 3 b:4: b4
321 3 b:4: b4
322 4 b:5: c
322 4 b:5: c
323 3 b:5: b5
323 3 b:5: b5
324 7 b:7: d
324 7 b:7: d
325
325
326 --skip nothing (should be the same as no --skip at all)
326 --skip nothing (should be the same as no --skip at all)
327
327
328 $ hg annotate -nlf b --skip '1::0'
328 $ hg annotate -nlf b --skip '1::0'
329 0 a:1: a
329 0 a:1: a
330 6 b:2: z
330 6 b:2: z
331 1 a:3: a
331 1 a:3: a
332 3 b:4: b4
332 3 b:4: b4
333 4 b:5: c
333 4 b:5: c
334 3 b:5: b5
334 3 b:5: b5
335 7 b:7: d
335 7 b:7: d
336
336
337 --skip a modified line. Note a slight behavior difference in pure - this is
337 --skip a modified line. Note a slight behavior difference in pure - this is
338 because the pure code comes up with slightly different deltas internally.
338 because the pure code comes up with slightly different deltas internally.
339
339
340 $ hg annotate -nlf b --skip 6
340 $ hg annotate -nlf b --skip 6
341 0 a:1: a
341 0 a:1: a
342 1 a:2* z (no-pure !)
342 1 a:2* z (no-pure !)
343 0 a:1* z (pure !)
343 0 a:1* z (pure !)
344 1 a:3: a
344 1 a:3: a
345 3 b:4: b4
345 3 b:4: b4
346 4 b:5: c
346 4 b:5: c
347 3 b:5: b5
347 3 b:5: b5
348 7 b:7: d
348 7 b:7: d
349
349
350 --skip added lines (and test multiple skip)
350 --skip added lines (and test multiple skip)
351
351
352 $ hg annotate -nlf b --skip 3
352 $ hg annotate -nlf b --skip 3
353 0 a:1: a
353 0 a:1: a
354 6 b:2: z
354 6 b:2: z
355 1 a:3: a
355 1 a:3: a
356 1 a:3* b4
356 1 a:3* b4
357 4 b:5: c
357 4 b:5: c
358 1 a:3* b5
358 1 a:3* b5
359 7 b:7: d
359 7 b:7: d
360
360
361 $ hg annotate -nlf b --skip 4
361 $ hg annotate -nlf b --skip 4
362 0 a:1: a
362 0 a:1: a
363 6 b:2: z
363 6 b:2: z
364 1 a:3: a
364 1 a:3: a
365 3 b:4: b4
365 3 b:4: b4
366 1 a:3* c
366 1 a:3* c
367 3 b:5: b5
367 3 b:5: b5
368 7 b:7: d
368 7 b:7: d
369
369
370 $ hg annotate -nlf b --skip 3 --skip 4
370 $ hg annotate -nlf b --skip 3 --skip 4
371 0 a:1: a
371 0 a:1: a
372 6 b:2: z
372 6 b:2: z
373 1 a:3: a
373 1 a:3: a
374 1 a:3* b4
374 1 a:3* b4
375 1 a:3* c
375 1 a:3* c
376 1 a:3* b5
376 1 a:3* b5
377 7 b:7: d
377 7 b:7: d
378
378
379 $ hg annotate -nlf b --skip 'merge()'
379 $ hg annotate -nlf b --skip 'merge()'
380 0 a:1: a
380 0 a:1: a
381 6 b:2: z
381 6 b:2: z
382 1 a:3: a
382 1 a:3: a
383 3 b:4: b4
383 3 b:4: b4
384 4 b:5: c
384 4 b:5: c
385 3 b:5: b5
385 3 b:5: b5
386 3 b:5* d
386 3 b:5* d
387
387
388 --skip everything -- use the revision the file was introduced in
388 --skip everything -- use the revision the file was introduced in
389
389
390 $ hg annotate -nlf b --skip 'all()'
390 $ hg annotate -nlf b --skip 'all()'
391 0 a:1: a
391 0 a:1: a
392 0 a:1* z
392 0 a:1* z
393 0 a:1* a
393 0 a:1* a
394 0 a:1* b4
394 0 a:1* b4
395 0 a:1* c
395 0 a:1* c
396 0 a:1* b5
396 0 a:1* b5
397 0 a:1* d
397 0 a:1* d
398
398
399 Issue2807: alignment of line numbers with -l
399 Issue2807: alignment of line numbers with -l
400
400
401 $ echo more >> b
401 $ echo more >> b
402 $ hg ci -mmore -d '5 0'
402 $ hg ci -mmore -d '5 0'
403 $ echo more >> b
403 $ echo more >> b
404 $ hg ci -mmore -d '6 0'
404 $ hg ci -mmore -d '6 0'
405 $ echo more >> b
405 $ echo more >> b
406 $ hg ci -mmore -d '7 0'
406 $ hg ci -mmore -d '7 0'
407 $ hg annotate -nlf b
407 $ hg annotate -nlf b
408 0 a: 1: a
408 0 a: 1: a
409 6 b: 2: z
409 6 b: 2: z
410 1 a: 3: a
410 1 a: 3: a
411 3 b: 4: b4
411 3 b: 4: b4
412 4 b: 5: c
412 4 b: 5: c
413 3 b: 5: b5
413 3 b: 5: b5
414 7 b: 7: d
414 7 b: 7: d
415 8 b: 8: more
415 8 b: 8: more
416 9 b: 9: more
416 9 b: 9: more
417 10 b:10: more
417 10 b:10: more
418
418
419 linkrev vs rev
419 linkrev vs rev
420
420
421 $ hg annotate -r tip -n a
421 $ hg annotate -r tip -n a
422 0: a
422 0: a
423 1: a
423 1: a
424 1: a
424 1: a
425
425
426 linkrev vs rev with -l
426 linkrev vs rev with -l
427
427
428 $ hg annotate -r tip -nl a
428 $ hg annotate -r tip -nl a
429 0:1: a
429 0:1: a
430 1:2: a
430 1:2: a
431 1:3: a
431 1:3: a
432
432
433 Issue589: "undelete" sequence leads to crash
433 Issue589: "undelete" sequence leads to crash
434
434
435 annotate was crashing when trying to --follow something
435 annotate was crashing when trying to --follow something
436
436
437 like A -> B -> A
437 like A -> B -> A
438
438
439 generate ABA rename configuration
439 generate ABA rename configuration
440
440
441 $ echo foo > foo
441 $ echo foo > foo
442 $ hg add foo
442 $ hg add foo
443 $ hg ci -m addfoo
443 $ hg ci -m addfoo
444 $ hg rename foo bar
444 $ hg rename foo bar
445 $ hg ci -m renamefoo
445 $ hg ci -m renamefoo
446 $ hg rename bar foo
446 $ hg rename bar foo
447 $ hg ci -m renamebar
447 $ hg ci -m renamebar
448
448
449 annotate after ABA with follow
449 annotate after ABA with follow
450
450
451 $ hg annotate --follow foo
451 $ hg annotate --follow foo
452 foo: foo
452 foo: foo
453
453
454 missing file
454 missing file
455
455
456 $ hg ann nosuchfile
456 $ hg ann nosuchfile
457 abort: nosuchfile: no such file in rev e9e6b4fa872f
457 abort: nosuchfile: no such file in rev e9e6b4fa872f
458 [255]
458 [255]
459
459
460 annotate file without '\n' on last line
460 annotate file without '\n' on last line
461
461
462 $ printf "" > c
462 $ printf "" > c
463 $ hg ci -A -m test -u nobody -d '1 0'
463 $ hg ci -A -m test -u nobody -d '1 0'
464 adding c
464 adding c
465 $ hg annotate c
465 $ hg annotate c
466 $ printf "a\nb" > c
466 $ printf "a\nb" > c
467 $ hg ci -m test
467 $ hg ci -m test
468 $ hg annotate c
468 $ hg annotate c
469 [0-9]+: a (re)
469 [0-9]+: a (re)
470 [0-9]+: b (re)
470 [0-9]+: b (re)
471
471
472 Issue3841: check annotation of the file of which filelog includes
472 Issue3841: check annotation of the file of which filelog includes
473 merging between the revision and its ancestor
473 merging between the revision and its ancestor
474
474
475 to reproduce the situation with recent Mercurial, this script uses (1)
475 to reproduce the situation with recent Mercurial, this script uses (1)
476 "hg debugsetparents" to merge without ancestor check by "hg merge",
476 "hg debugsetparents" to merge without ancestor check by "hg merge",
477 and (2) the extension to allow filelog merging between the revision
477 and (2) the extension to allow filelog merging between the revision
478 and its ancestor by overriding "repo._filecommit".
478 and its ancestor by overriding "repo._filecommit".
479
479
480 $ cat > ../legacyrepo.py <<EOF
480 $ cat > ../legacyrepo.py <<EOF
481 > from __future__ import absolute_import
481 > from __future__ import absolute_import
482 > from mercurial import commit, error, extensions
482 > from mercurial import commit, error, extensions
483 > def _filecommit(orig, repo, fctx, manifest1, manifest2,
483 > def _filecommit(orig, repo, fctx, manifest1, manifest2,
484 > linkrev, tr, includecopymeta, ms):
484 > linkrev, tr, includecopymeta, ms):
485 > fname = fctx.path()
485 > fname = fctx.path()
486 > text = fctx.data()
486 > text = fctx.data()
487 > flog = repo.file(fname)
487 > flog = repo.file(fname)
488 > fparent1 = manifest1.get(fname, repo.nullid)
488 > fparent1 = manifest1.get(fname, repo.nullid)
489 > fparent2 = manifest2.get(fname, repo.nullid)
489 > fparent2 = manifest2.get(fname, repo.nullid)
490 > meta = {}
490 > meta = {}
491 > copy = fctx.copysource()
491 > copy = fctx.copysource()
492 > if copy and copy != fname:
492 > if copy and copy != fname:
493 > raise error.Abort('copying is not supported')
493 > raise error.Abort('copying is not supported')
494 > if fparent2 != repo.nullid:
494 > if fparent2 != repo.nullid:
495 > return flog.add(text, meta, tr, linkrev,
495 > return flog.add(text, meta, tr, linkrev,
496 > fparent1, fparent2), 'modified'
496 > fparent1, fparent2), 'modified'
497 > raise error.Abort('only merging is supported')
497 > raise error.Abort('only merging is supported')
498 > def uisetup(ui):
498 > def uisetup(ui):
499 > extensions.wrapfunction(commit, '_filecommit', _filecommit)
499 > extensions.wrapfunction(commit, '_filecommit', _filecommit)
500 > EOF
500 > EOF
501
501
502 $ cat > baz <<EOF
502 $ cat > baz <<EOF
503 > 1
503 > 1
504 > 2
504 > 2
505 > 3
505 > 3
506 > 4
506 > 4
507 > 5
507 > 5
508 > EOF
508 > EOF
509 $ hg add baz
509 $ hg add baz
510 $ hg commit -m "baz:0"
510 $ hg commit -m "baz:0"
511
511
512 $ cat > baz <<EOF
512 $ cat > baz <<EOF
513 > 1 baz:1
513 > 1 baz:1
514 > 2
514 > 2
515 > 3
515 > 3
516 > 4
516 > 4
517 > 5
517 > 5
518 > EOF
518 > EOF
519 $ hg commit -m "baz:1"
519 $ hg commit -m "baz:1"
520
520
521 $ cat > baz <<EOF
521 $ cat > baz <<EOF
522 > 1 baz:1
522 > 1 baz:1
523 > 2 baz:2
523 > 2 baz:2
524 > 3
524 > 3
525 > 4
525 > 4
526 > 5
526 > 5
527 > EOF
527 > EOF
528 $ hg debugsetparents 17 17
528 $ hg debugsetparents 17 17
529 $ hg --config extensions.legacyrepo=../legacyrepo.py commit -m "baz:2"
529 $ hg --config extensions.legacyrepo=../legacyrepo.py commit -m "baz:2"
530 $ hg debugindexdot baz
530 $ hg debugindexdot baz
531 digraph G {
531 digraph G {
532 -1 -> 0
532 -1 -> 0
533 0 -> 1
533 0 -> 1
534 1 -> 2
534 1 -> 2
535 1 -> 2
535 1 -> 2
536 }
536 }
537 $ hg annotate baz
537 $ hg annotate baz
538 17: 1 baz:1
538 17: 1 baz:1
539 18: 2 baz:2
539 18: 2 baz:2
540 16: 3
540 16: 3
541 16: 4
541 16: 4
542 16: 5
542 16: 5
543
543
544 $ cat > baz <<EOF
544 $ cat > baz <<EOF
545 > 1 baz:1
545 > 1 baz:1
546 > 2 baz:2
546 > 2 baz:2
547 > 3 baz:3
547 > 3 baz:3
548 > 4
548 > 4
549 > 5
549 > 5
550 > EOF
550 > EOF
551 $ hg commit -m "baz:3"
551 $ hg commit -m "baz:3"
552
552
553 $ cat > baz <<EOF
553 $ cat > baz <<EOF
554 > 1 baz:1
554 > 1 baz:1
555 > 2 baz:2
555 > 2 baz:2
556 > 3 baz:3
556 > 3 baz:3
557 > 4 baz:4
557 > 4 baz:4
558 > 5
558 > 5
559 > EOF
559 > EOF
560 $ hg debugsetparents 19 18
560 $ hg debugsetparents 19 18
561 $ hg --config extensions.legacyrepo=../legacyrepo.py commit -m "baz:4"
561 $ hg --config extensions.legacyrepo=../legacyrepo.py commit -m "baz:4"
562 $ hg debugindexdot baz
562 $ hg debugindexdot baz
563 digraph G {
563 digraph G {
564 -1 -> 0
564 -1 -> 0
565 0 -> 1
565 0 -> 1
566 1 -> 2
566 1 -> 2
567 1 -> 2
567 1 -> 2
568 2 -> 3
568 2 -> 3
569 3 -> 4
569 3 -> 4
570 2 -> 4
570 2 -> 4
571 }
571 }
572 $ hg annotate baz
572 $ hg annotate baz
573 17: 1 baz:1
573 17: 1 baz:1
574 18: 2 baz:2
574 18: 2 baz:2
575 19: 3 baz:3
575 19: 3 baz:3
576 20: 4 baz:4
576 20: 4 baz:4
577 16: 5
577 16: 5
578
578
579 annotate clean file
579 annotate clean file
580
580
581 $ hg annotate -ncr "wdir()" foo
581 $ hg annotate -ncr "wdir()" foo
582 11 472b18db256d : foo
582 11 472b18db256d : foo
583
583
584 annotate modified file
584 annotate modified file
585
585
586 $ echo foofoo >> foo
586 $ echo foofoo >> foo
587 $ hg annotate -r "wdir()" foo
587 $ hg annotate -r "wdir()" foo
588 11 : foo
588 11 : foo
589 20+: foofoo
589 20+: foofoo
590
590
591 $ hg annotate -cr "wdir()" foo
591 $ hg annotate -cr "wdir()" foo
592 472b18db256d : foo
592 472b18db256d : foo
593 b6bedd5477e7+: foofoo
593 b6bedd5477e7+: foofoo
594
594
595 $ hg annotate -ncr "wdir()" foo
595 $ hg annotate -ncr "wdir()" foo
596 11 472b18db256d : foo
596 11 472b18db256d : foo
597 20 b6bedd5477e7+: foofoo
597 20 b6bedd5477e7+: foofoo
598
598
599 $ hg annotate --debug -ncr "wdir()" foo
599 $ hg annotate --debug -ncr "wdir()" foo
600 11 472b18db256d1e8282064eab4bfdaf48cbfe83cd : foo
600 11 472b18db256d1e8282064eab4bfdaf48cbfe83cd : foo
601 20 b6bedd5477e797f25e568a6402d4697f3f895a72+: foofoo
601 20 b6bedd5477e797f25e568a6402d4697f3f895a72+: foofoo
602
602
603 $ hg annotate -udr "wdir()" foo
603 $ hg annotate -udr "wdir()" foo
604 test Thu Jan 01 00:00:00 1970 +0000: foo
604 test Thu Jan 01 00:00:00 1970 +0000: foo
605 test [A-Za-z0-9:+ ]+: foofoo (re)
605 test [A-Za-z0-9:+ ]+: foofoo (re)
606
606
607 $ hg annotate -ncr "wdir()" -Tjson foo
607 $ hg annotate -ncr "wdir()" -Tjson foo
608 [
608 [
609 {
609 {
610 "lines": [{"line": "foo\n", "node": "472b18db256d1e8282064eab4bfdaf48cbfe83cd", "rev": 11}, {"line": "foofoo\n", "node": "ffffffffffffffffffffffffffffffffffffffff", "rev": 2147483647}],
610 "lines": [{"line": "foo\n", "node": "472b18db256d1e8282064eab4bfdaf48cbfe83cd", "rev": 11}, {"line": "foofoo\n", "node": "ffffffffffffffffffffffffffffffffffffffff", "rev": 2147483647}],
611 "path": "foo"
611 "path": "foo"
612 }
612 }
613 ]
613 ]
614
614
615 annotate added file
615 annotate added file
616
616
617 $ echo bar > bar
617 $ echo bar > bar
618 $ hg add bar
618 $ hg add bar
619 $ hg annotate -ncr "wdir()" bar
619 $ hg annotate -ncr "wdir()" bar
620 20 b6bedd5477e7+: bar
620 20 b6bedd5477e7+: bar
621
621
622 annotate renamed file
622 annotate renamed file
623
623
624 $ hg rename foo renamefoo2
624 $ hg rename foo renamefoo2
625 $ hg annotate -ncr "wdir()" renamefoo2
625 $ hg annotate -ncr "wdir()" renamefoo2
626 11 472b18db256d : foo
626 11 472b18db256d : foo
627 20 b6bedd5477e7+: foofoo
627 20 b6bedd5477e7+: foofoo
628
628
629 annotate missing file
629 annotate missing file
630
630
631 $ rm baz
631 $ rm baz
632
632
633 $ hg annotate -ncr "wdir()" baz
633 $ hg annotate -ncr "wdir()" baz
634 abort: $TESTTMP\repo\baz: $ENOENT$ (windows !)
634 abort: $TESTTMP\repo/baz: $ENOENT$ (windows !)
635 abort: $ENOENT$: '$TESTTMP/repo/baz' (no-windows !)
635 abort: $ENOENT$: '$TESTTMP/repo/baz' (no-windows !)
636 [255]
636 [255]
637
637
638 annotate removed file
638 annotate removed file
639
639
640 $ hg rm baz
640 $ hg rm baz
641
641
642 $ hg annotate -ncr "wdir()" baz
642 $ hg annotate -ncr "wdir()" baz
643 abort: $TESTTMP\repo\baz: $ENOENT$ (windows !)
643 abort: $TESTTMP\repo/baz: $ENOENT$ (windows !)
644 abort: $ENOENT$: '$TESTTMP/repo/baz' (no-windows !)
644 abort: $ENOENT$: '$TESTTMP/repo/baz' (no-windows !)
645 [255]
645 [255]
646
646
647 $ hg revert --all --no-backup --quiet
647 $ hg revert --all --no-backup --quiet
648 $ hg id -n
648 $ hg id -n
649 20
649 20
650
650
651 Test followlines() revset; we usually check both followlines(pat, range) and
651 Test followlines() revset; we usually check both followlines(pat, range) and
652 followlines(pat, range, descend=True) to make sure both give the same result
652 followlines(pat, range, descend=True) to make sure both give the same result
653 when they should.
653 when they should.
654
654
655 $ echo a >> foo
655 $ echo a >> foo
656 $ hg ci -m 'foo: add a'
656 $ hg ci -m 'foo: add a'
657 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5)'
657 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5)'
658 16: baz:0
658 16: baz:0
659 19: baz:3
659 19: baz:3
660 20: baz:4
660 20: baz:4
661 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=20)'
661 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=20)'
662 16: baz:0
662 16: baz:0
663 19: baz:3
663 19: baz:3
664 20: baz:4
664 20: baz:4
665 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=19)'
665 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=19)'
666 16: baz:0
666 16: baz:0
667 19: baz:3
667 19: baz:3
668 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=19, descend=True)'
668 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=19, descend=True)'
669 19: baz:3
669 19: baz:3
670 20: baz:4
670 20: baz:4
671 $ printf "0\n0\n" | cat - baz > baz1
671 $ printf "0\n0\n" | cat - baz > baz1
672 $ mv baz1 baz
672 $ mv baz1 baz
673 $ hg ci -m 'added two lines with 0'
673 $ hg ci -m 'added two lines with 0'
674 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7)'
674 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7)'
675 16: baz:0
675 16: baz:0
676 19: baz:3
676 19: baz:3
677 20: baz:4
677 20: baz:4
678 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, descend=true, startrev=19)'
678 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, descend=true, startrev=19)'
679 19: baz:3
679 19: baz:3
680 20: baz:4
680 20: baz:4
681 $ echo 6 >> baz
681 $ echo 6 >> baz
682 $ hg ci -m 'added line 8'
682 $ hg ci -m 'added line 8'
683 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7)'
683 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7)'
684 16: baz:0
684 16: baz:0
685 19: baz:3
685 19: baz:3
686 20: baz:4
686 20: baz:4
687 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=19, descend=1)'
687 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=19, descend=1)'
688 19: baz:3
688 19: baz:3
689 20: baz:4
689 20: baz:4
690 $ sed 's/3/3+/' baz > baz.new
690 $ sed 's/3/3+/' baz > baz.new
691 $ mv baz.new baz
691 $ mv baz.new baz
692 $ hg ci -m 'baz:3->3+'
692 $ hg ci -m 'baz:3->3+'
693 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7, descend=0)'
693 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7, descend=0)'
694 16: baz:0
694 16: baz:0
695 19: baz:3
695 19: baz:3
696 20: baz:4
696 20: baz:4
697 24: baz:3->3+
697 24: baz:3->3+
698 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=17, descend=True)'
698 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:5, startrev=17, descend=True)'
699 19: baz:3
699 19: baz:3
700 20: baz:4
700 20: baz:4
701 24: baz:3->3+
701 24: baz:3->3+
702 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 1:2, descend=false)'
702 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 1:2, descend=false)'
703 22: added two lines with 0
703 22: added two lines with 0
704
704
705 file patterns are okay
705 file patterns are okay
706 $ hg log -T '{rev}: {desc}\n' -r 'followlines("path:baz", 1:2)'
706 $ hg log -T '{rev}: {desc}\n' -r 'followlines("path:baz", 1:2)'
707 22: added two lines with 0
707 22: added two lines with 0
708
708
709 renames are followed
709 renames are followed
710 $ hg mv baz qux
710 $ hg mv baz qux
711 $ sed 's/4/4+/' qux > qux.new
711 $ sed 's/4/4+/' qux > qux.new
712 $ mv qux.new qux
712 $ mv qux.new qux
713 $ hg ci -m 'qux:4->4+'
713 $ hg ci -m 'qux:4->4+'
714 $ hg log -T '{rev}: {desc}\n' -r 'followlines(qux, 5:7)'
714 $ hg log -T '{rev}: {desc}\n' -r 'followlines(qux, 5:7)'
715 16: baz:0
715 16: baz:0
716 19: baz:3
716 19: baz:3
717 20: baz:4
717 20: baz:4
718 24: baz:3->3+
718 24: baz:3->3+
719 25: qux:4->4+
719 25: qux:4->4+
720
720
721 but are missed when following children
721 but are missed when following children
722 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7, startrev=22, descend=True)'
722 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7, startrev=22, descend=True)'
723 24: baz:3->3+
723 24: baz:3->3+
724
724
725 merge
725 merge
726 $ hg up 24 --quiet
726 $ hg up 24 --quiet
727 $ echo 7 >> baz
727 $ echo 7 >> baz
728 $ hg ci -m 'one more line, out of line range'
728 $ hg ci -m 'one more line, out of line range'
729 created new head
729 created new head
730 $ sed 's/3+/3-/' baz > baz.new
730 $ sed 's/3+/3-/' baz > baz.new
731 $ mv baz.new baz
731 $ mv baz.new baz
732 $ hg ci -m 'baz:3+->3-'
732 $ hg ci -m 'baz:3+->3-'
733 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7)'
733 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7)'
734 16: baz:0
734 16: baz:0
735 19: baz:3
735 19: baz:3
736 20: baz:4
736 20: baz:4
737 24: baz:3->3+
737 24: baz:3->3+
738 27: baz:3+->3-
738 27: baz:3+->3-
739 $ hg merge 25
739 $ hg merge 25
740 merging baz and qux to qux
740 merging baz and qux to qux
741 warning: conflicts while merging qux! (edit, then use 'hg resolve --mark')
741 warning: conflicts while merging qux! (edit, then use 'hg resolve --mark')
742 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
742 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
743 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
743 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
744 [1]
744 [1]
745 $ cat qux
745 $ cat qux
746 0
746 0
747 0
747 0
748 1 baz:1
748 1 baz:1
749 2 baz:2
749 2 baz:2
750 <<<<<<< working copy: 863de62655ef - test: baz:3+->3-
750 <<<<<<< working copy: 863de62655ef - test: baz:3+->3-
751 3- baz:3
751 3- baz:3
752 4 baz:4
752 4 baz:4
753 ||||||| base
753 ||||||| base
754 3+ baz:3
754 3+ baz:3
755 4 baz:4
755 4 baz:4
756 =======
756 =======
757 3+ baz:3
757 3+ baz:3
758 4+ baz:4
758 4+ baz:4
759 >>>>>>> merge rev: cb8df70ae185 - test: qux:4->4+
759 >>>>>>> merge rev: cb8df70ae185 - test: qux:4->4+
760 5
760 5
761 6
761 6
762 7
762 7
763 $ cat > qux <<EOF
763 $ cat > qux <<EOF
764 > 0
764 > 0
765 > 0
765 > 0
766 > 1 baz:1
766 > 1 baz:1
767 > 2 baz:2
767 > 2 baz:2
768 > 3- baz:3
768 > 3- baz:3
769 > 4 baz:4
769 > 4 baz:4
770 > 5
770 > 5
771 > 6
771 > 6
772 > 7
772 > 7
773 > EOF
773 > EOF
774 $ hg resolve --mark -q
774 $ hg resolve --mark -q
775 $ rm qux.orig
775 $ rm qux.orig
776 $ hg ci -m merge
776 $ hg ci -m merge
777 $ hg log -T '{rev}: {desc}\n' -r 'followlines(qux, 5:7)'
777 $ hg log -T '{rev}: {desc}\n' -r 'followlines(qux, 5:7)'
778 16: baz:0
778 16: baz:0
779 19: baz:3
779 19: baz:3
780 20: baz:4
780 20: baz:4
781 24: baz:3->3+
781 24: baz:3->3+
782 25: qux:4->4+
782 25: qux:4->4+
783 27: baz:3+->3-
783 27: baz:3+->3-
784 28: merge
784 28: merge
785 $ hg up 25 --quiet
785 $ hg up 25 --quiet
786 $ hg merge 27
786 $ hg merge 27
787 merging qux and baz to qux
787 merging qux and baz to qux
788 warning: conflicts while merging qux! (edit, then use 'hg resolve --mark')
788 warning: conflicts while merging qux! (edit, then use 'hg resolve --mark')
789 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
789 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
790 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
790 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
791 [1]
791 [1]
792 $ cat qux
792 $ cat qux
793 0
793 0
794 0
794 0
795 1 baz:1
795 1 baz:1
796 2 baz:2
796 2 baz:2
797 <<<<<<< working copy: cb8df70ae185 - test: qux:4->4+
797 <<<<<<< working copy: cb8df70ae185 - test: qux:4->4+
798 3+ baz:3
798 3+ baz:3
799 4+ baz:4
799 4+ baz:4
800 ||||||| base
800 ||||||| base
801 3+ baz:3
801 3+ baz:3
802 4 baz:4
802 4 baz:4
803 =======
803 =======
804 3- baz:3
804 3- baz:3
805 4 baz:4
805 4 baz:4
806 >>>>>>> merge rev: 863de62655ef - test: baz:3+->3-
806 >>>>>>> merge rev: 863de62655ef - test: baz:3+->3-
807 5
807 5
808 6
808 6
809 7
809 7
810 $ cat > qux <<EOF
810 $ cat > qux <<EOF
811 > 0
811 > 0
812 > 0
812 > 0
813 > 1 baz:1
813 > 1 baz:1
814 > 2 baz:2
814 > 2 baz:2
815 > 3+ baz:3
815 > 3+ baz:3
816 > 4+ baz:4
816 > 4+ baz:4
817 > 5
817 > 5
818 > 6
818 > 6
819 > EOF
819 > EOF
820 $ hg resolve --mark -q
820 $ hg resolve --mark -q
821 $ rm qux.orig
821 $ rm qux.orig
822 $ hg ci -m 'merge from other side'
822 $ hg ci -m 'merge from other side'
823 created new head
823 created new head
824 $ hg log -T '{rev}: {desc}\n' -r 'followlines(qux, 5:7)'
824 $ hg log -T '{rev}: {desc}\n' -r 'followlines(qux, 5:7)'
825 16: baz:0
825 16: baz:0
826 19: baz:3
826 19: baz:3
827 20: baz:4
827 20: baz:4
828 24: baz:3->3+
828 24: baz:3->3+
829 25: qux:4->4+
829 25: qux:4->4+
830 27: baz:3+->3-
830 27: baz:3+->3-
831 29: merge from other side
831 29: merge from other side
832 $ hg up 24 --quiet
832 $ hg up 24 --quiet
833
833
834 we are missing the branch with rename when following children
834 we are missing the branch with rename when following children
835 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7, startrev=26, descend=True)'
835 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 5:7, startrev=26, descend=True)'
836 27: baz:3+->3-
836 27: baz:3+->3-
837
837
838 we follow all branches in descending direction
838 we follow all branches in descending direction
839 $ hg up 23 --quiet
839 $ hg up 23 --quiet
840 $ sed 's/3/+3/' baz > baz.new
840 $ sed 's/3/+3/' baz > baz.new
841 $ mv baz.new baz
841 $ mv baz.new baz
842 $ hg ci -m 'baz:3->+3'
842 $ hg ci -m 'baz:3->+3'
843 created new head
843 created new head
844 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 2:5, startrev=16, descend=True)' --graph
844 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 2:5, startrev=16, descend=True)' --graph
845 @ 30: baz:3->+3
845 @ 30: baz:3->+3
846 :
846 :
847 : o 27: baz:3+->3-
847 : o 27: baz:3+->3-
848 : :
848 : :
849 : o 24: baz:3->3+
849 : o 24: baz:3->3+
850 :/
850 :/
851 o 20: baz:4
851 o 20: baz:4
852 |\
852 |\
853 | o 19: baz:3
853 | o 19: baz:3
854 |/
854 |/
855 o 18: baz:2
855 o 18: baz:2
856 :
856 :
857 o 16: baz:0
857 o 16: baz:0
858 |
858 |
859 ~
859 ~
860
860
861 Issue5595: on a merge changeset with different line ranges depending on
861 Issue5595: on a merge changeset with different line ranges depending on
862 parent, be conservative and use the surrounding interval to avoid loosing
862 parent, be conservative and use the surrounding interval to avoid loosing
863 track of possible further descendants in specified range.
863 track of possible further descendants in specified range.
864
864
865 $ hg up 23 --quiet
865 $ hg up 23 --quiet
866 $ hg cat baz -r 24
866 $ hg cat baz -r 24
867 0
867 0
868 0
868 0
869 1 baz:1
869 1 baz:1
870 2 baz:2
870 2 baz:2
871 3+ baz:3
871 3+ baz:3
872 4 baz:4
872 4 baz:4
873 5
873 5
874 6
874 6
875 $ cat > baz << EOF
875 $ cat > baz << EOF
876 > 0
876 > 0
877 > 0
877 > 0
878 > a
878 > a
879 > b
879 > b
880 > 3+ baz:3
880 > 3+ baz:3
881 > 4 baz:4
881 > 4 baz:4
882 > y
882 > y
883 > z
883 > z
884 > EOF
884 > EOF
885 $ hg ci -m 'baz: mostly rewrite with some content from 24'
885 $ hg ci -m 'baz: mostly rewrite with some content from 24'
886 created new head
886 created new head
887 $ hg merge --tool :merge-other 24
887 $ hg merge --tool :merge-other 24
888 merging baz
888 merging baz
889 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
889 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
890 (branch merge, don't forget to commit)
890 (branch merge, don't forget to commit)
891 $ hg ci -m 'merge forgetting about baz rewrite'
891 $ hg ci -m 'merge forgetting about baz rewrite'
892 $ cat > baz << EOF
892 $ cat > baz << EOF
893 > 0
893 > 0
894 > 0
894 > 0
895 > 1 baz:1
895 > 1 baz:1
896 > 2+ baz:2
896 > 2+ baz:2
897 > 3+ baz:3
897 > 3+ baz:3
898 > 4 baz:4
898 > 4 baz:4
899 > 5
899 > 5
900 > 6
900 > 6
901 > EOF
901 > EOF
902 $ hg ci -m 'baz: narrow change (2->2+)'
902 $ hg ci -m 'baz: narrow change (2->2+)'
903 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:4, startrev=20, descend=True)' --graph
903 $ hg log -T '{rev}: {desc}\n' -r 'followlines(baz, 3:4, startrev=20, descend=True)' --graph
904 @ 33: baz: narrow change (2->2+)
904 @ 33: baz: narrow change (2->2+)
905 |
905 |
906 o 32: merge forgetting about baz rewrite
906 o 32: merge forgetting about baz rewrite
907 |\
907 |\
908 | o 31: baz: mostly rewrite with some content from 24
908 | o 31: baz: mostly rewrite with some content from 24
909 | :
909 | :
910 | : o 30: baz:3->+3
910 | : o 30: baz:3->+3
911 | :/
911 | :/
912 +---o 27: baz:3+->3-
912 +---o 27: baz:3+->3-
913 | :
913 | :
914 o : 24: baz:3->3+
914 o : 24: baz:3->3+
915 :/
915 :/
916 o 20: baz:4
916 o 20: baz:4
917 |\
917 |\
918 ~ ~
918 ~ ~
919
919
920 An integer as a line range, which is parsed as '1:1'
920 An integer as a line range, which is parsed as '1:1'
921
921
922 $ hg log -r 'followlines(baz, 1)'
922 $ hg log -r 'followlines(baz, 1)'
923 changeset: 22:2174d0bf352a
923 changeset: 22:2174d0bf352a
924 user: test
924 user: test
925 date: Thu Jan 01 00:00:00 1970 +0000
925 date: Thu Jan 01 00:00:00 1970 +0000
926 summary: added two lines with 0
926 summary: added two lines with 0
927
927
928
928
929 check error cases
929 check error cases
930 $ hg up 24 --quiet
930 $ hg up 24 --quiet
931 $ hg log -r 'followlines()'
931 $ hg log -r 'followlines()'
932 hg: parse error: followlines takes at least 1 positional arguments
932 hg: parse error: followlines takes at least 1 positional arguments
933 [10]
933 [10]
934 $ hg log -r 'followlines(baz)'
934 $ hg log -r 'followlines(baz)'
935 hg: parse error: followlines requires a line range
935 hg: parse error: followlines requires a line range
936 [10]
936 [10]
937 $ hg log -r 'followlines(baz, x)'
937 $ hg log -r 'followlines(baz, x)'
938 hg: parse error: followlines expects a line number or a range
938 hg: parse error: followlines expects a line number or a range
939 [10]
939 [10]
940 $ hg log -r 'followlines(baz, 1:2, startrev=desc("b"))'
940 $ hg log -r 'followlines(baz, 1:2, startrev=desc("b"))'
941 hg: parse error: followlines expects exactly one revision
941 hg: parse error: followlines expects exactly one revision
942 [10]
942 [10]
943 $ hg log -r 'followlines("glob:*", 1:2)'
943 $ hg log -r 'followlines("glob:*", 1:2)'
944 hg: parse error: followlines expects exactly one file
944 hg: parse error: followlines expects exactly one file
945 [10]
945 [10]
946 $ hg log -r 'followlines(baz, 1:)'
946 $ hg log -r 'followlines(baz, 1:)'
947 hg: parse error: line range bounds must be integers
947 hg: parse error: line range bounds must be integers
948 [10]
948 [10]
949 $ hg log -r 'followlines(baz, :1)'
949 $ hg log -r 'followlines(baz, :1)'
950 hg: parse error: line range bounds must be integers
950 hg: parse error: line range bounds must be integers
951 [10]
951 [10]
952 $ hg log -r 'followlines(baz, x:4)'
952 $ hg log -r 'followlines(baz, x:4)'
953 hg: parse error: line range bounds must be integers
953 hg: parse error: line range bounds must be integers
954 [10]
954 [10]
955 $ hg log -r 'followlines(baz, 5:4)'
955 $ hg log -r 'followlines(baz, 5:4)'
956 hg: parse error: line range must be positive
956 hg: parse error: line range must be positive
957 [10]
957 [10]
958 $ hg log -r 'followlines(baz, 0:4)'
958 $ hg log -r 'followlines(baz, 0:4)'
959 hg: parse error: fromline must be strictly positive
959 hg: parse error: fromline must be strictly positive
960 [10]
960 [10]
961 $ hg log -r 'followlines(baz, 2:40)'
961 $ hg log -r 'followlines(baz, 2:40)'
962 abort: line range exceeds file size
962 abort: line range exceeds file size
963 [10]
963 [10]
964 $ hg log -r 'followlines(baz, 2:4, startrev=20, descend=[1])'
964 $ hg log -r 'followlines(baz, 2:4, startrev=20, descend=[1])'
965 hg: parse error at 43: not a prefix: [
965 hg: parse error at 43: not a prefix: [
966 (followlines(baz, 2:4, startrev=20, descend=[1])
966 (followlines(baz, 2:4, startrev=20, descend=[1])
967 ^ here)
967 ^ here)
968 [10]
968 [10]
969 $ hg log -r 'followlines(baz, 2:4, startrev=20, descend=a)'
969 $ hg log -r 'followlines(baz, 2:4, startrev=20, descend=a)'
970 hg: parse error: descend argument must be a boolean
970 hg: parse error: descend argument must be a boolean
971 [10]
971 [10]
972
972
973 Test empty annotate output
973 Test empty annotate output
974
974
975 $ printf '\0' > binary
975 $ printf '\0' > binary
976 $ touch empty
976 $ touch empty
977 $ hg ci -qAm 'add binary and empty files'
977 $ hg ci -qAm 'add binary and empty files'
978
978
979 $ hg annotate binary empty
979 $ hg annotate binary empty
980 binary: binary file
980 binary: binary file
981
981
982 $ hg annotate -Tjson binary empty
982 $ hg annotate -Tjson binary empty
983 [
983 [
984 {
984 {
985 "path": "binary"
985 "path": "binary"
986 },
986 },
987 {
987 {
988 "lines": [],
988 "lines": [],
989 "path": "empty"
989 "path": "empty"
990 }
990 }
991 ]
991 ]
992
992
993 Test annotate with whitespace options
993 Test annotate with whitespace options
994
994
995 $ cd ..
995 $ cd ..
996 $ hg init repo-ws
996 $ hg init repo-ws
997 $ cd repo-ws
997 $ cd repo-ws
998 $ cat > a <<EOF
998 $ cat > a <<EOF
999 > aa
999 > aa
1000 >
1000 >
1001 > b b
1001 > b b
1002 > EOF
1002 > EOF
1003 $ hg ci -Am "adda"
1003 $ hg ci -Am "adda"
1004 adding a
1004 adding a
1005 $ sed 's/EOL$//g' > a <<EOF
1005 $ sed 's/EOL$//g' > a <<EOF
1006 > a a
1006 > a a
1007 >
1007 >
1008 > EOL
1008 > EOL
1009 > b b
1009 > b b
1010 > EOF
1010 > EOF
1011 $ hg ci -m "changea"
1011 $ hg ci -m "changea"
1012
1012
1013 Annotate with no option
1013 Annotate with no option
1014
1014
1015 $ hg annotate a
1015 $ hg annotate a
1016 1: a a
1016 1: a a
1017 0:
1017 0:
1018 1:
1018 1:
1019 1: b b
1019 1: b b
1020
1020
1021 Annotate with --ignore-space-change
1021 Annotate with --ignore-space-change
1022
1022
1023 $ hg annotate --ignore-space-change a
1023 $ hg annotate --ignore-space-change a
1024 1: a a
1024 1: a a
1025 1:
1025 1:
1026 0:
1026 0:
1027 0: b b
1027 0: b b
1028
1028
1029 Annotate with --ignore-all-space
1029 Annotate with --ignore-all-space
1030
1030
1031 $ hg annotate --ignore-all-space a
1031 $ hg annotate --ignore-all-space a
1032 0: a a
1032 0: a a
1033 0:
1033 0:
1034 1:
1034 1:
1035 0: b b
1035 0: b b
1036
1036
1037 Annotate with --ignore-blank-lines (similar to no options case)
1037 Annotate with --ignore-blank-lines (similar to no options case)
1038
1038
1039 $ hg annotate --ignore-blank-lines a
1039 $ hg annotate --ignore-blank-lines a
1040 1: a a
1040 1: a a
1041 0:
1041 0:
1042 1:
1042 1:
1043 1: b b
1043 1: b b
1044
1044
1045 $ cd ..
1045 $ cd ..
1046
1046
1047 Annotate with orphaned CR (issue5798)
1047 Annotate with orphaned CR (issue5798)
1048 -------------------------------------
1048 -------------------------------------
1049
1049
1050 $ hg init repo-cr
1050 $ hg init repo-cr
1051 $ cd repo-cr
1051 $ cd repo-cr
1052
1052
1053 $ cat <<'EOF' >> "$TESTTMP/substcr.py"
1053 $ cat <<'EOF' >> "$TESTTMP/substcr.py"
1054 > import sys
1054 > import sys
1055 > from mercurial.utils import procutil
1055 > from mercurial.utils import procutil
1056 > procutil.setbinary(sys.stdin)
1056 > procutil.setbinary(sys.stdin)
1057 > procutil.setbinary(sys.stdout)
1057 > procutil.setbinary(sys.stdout)
1058 > stdin = getattr(sys.stdin, 'buffer', sys.stdin)
1058 > stdin = getattr(sys.stdin, 'buffer', sys.stdin)
1059 > stdout = getattr(sys.stdout, 'buffer', sys.stdout)
1059 > stdout = getattr(sys.stdout, 'buffer', sys.stdout)
1060 > stdout.write(stdin.read().replace(b'\r', b'[CR]'))
1060 > stdout.write(stdin.read().replace(b'\r', b'[CR]'))
1061 > EOF
1061 > EOF
1062
1062
1063 >>> with open('a', 'wb') as f:
1063 >>> with open('a', 'wb') as f:
1064 ... f.write(b'0a\r0b\r\n0c\r0d\r\n0e\n0f\n0g') and None
1064 ... f.write(b'0a\r0b\r\n0c\r0d\r\n0e\n0f\n0g') and None
1065 $ hg ci -qAm0
1065 $ hg ci -qAm0
1066 >>> with open('a', 'wb') as f:
1066 >>> with open('a', 'wb') as f:
1067 ... f.write(b'0a\r0b\r\n1c\r1d\r\n0e\n1f\n0g') and None
1067 ... f.write(b'0a\r0b\r\n1c\r1d\r\n0e\n1f\n0g') and None
1068 $ hg ci -m1
1068 $ hg ci -m1
1069
1069
1070 $ hg annotate -r0 a | "$PYTHON" "$TESTTMP/substcr.py"
1070 $ hg annotate -r0 a | "$PYTHON" "$TESTTMP/substcr.py"
1071 0: 0a[CR]0b[CR]
1071 0: 0a[CR]0b[CR]
1072 0: 0c[CR]0d[CR]
1072 0: 0c[CR]0d[CR]
1073 0: 0e
1073 0: 0e
1074 0: 0f
1074 0: 0f
1075 0: 0g
1075 0: 0g
1076 $ hg annotate -r1 a | "$PYTHON" "$TESTTMP/substcr.py"
1076 $ hg annotate -r1 a | "$PYTHON" "$TESTTMP/substcr.py"
1077 0: 0a[CR]0b[CR]
1077 0: 0a[CR]0b[CR]
1078 1: 1c[CR]1d[CR]
1078 1: 1c[CR]1d[CR]
1079 0: 0e
1079 0: 0e
1080 1: 1f
1080 1: 1f
1081 0: 0g
1081 0: 0g
1082
1082
1083 $ cd ..
1083 $ cd ..
1084
1084
1085 Annotate with linkrev pointing to another branch
1085 Annotate with linkrev pointing to another branch
1086 ------------------------------------------------
1086 ------------------------------------------------
1087
1087
1088 create history with a filerev whose linkrev points to another branch
1088 create history with a filerev whose linkrev points to another branch
1089
1089
1090 $ hg init branchedlinkrev
1090 $ hg init branchedlinkrev
1091 $ cd branchedlinkrev
1091 $ cd branchedlinkrev
1092 $ echo A > a
1092 $ echo A > a
1093 $ hg commit -Am 'contentA'
1093 $ hg commit -Am 'contentA'
1094 adding a
1094 adding a
1095 $ echo B >> a
1095 $ echo B >> a
1096 $ hg commit -m 'contentB'
1096 $ hg commit -m 'contentB'
1097 $ hg up --rev 'desc(contentA)'
1097 $ hg up --rev 'desc(contentA)'
1098 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1098 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1099 $ echo unrelated > unrelated
1099 $ echo unrelated > unrelated
1100 $ hg commit -Am 'unrelated'
1100 $ hg commit -Am 'unrelated'
1101 adding unrelated
1101 adding unrelated
1102 created new head
1102 created new head
1103 $ hg graft -r 'desc(contentB)'
1103 $ hg graft -r 'desc(contentB)'
1104 grafting 1:fd27c222e3e6 "contentB"
1104 grafting 1:fd27c222e3e6 "contentB"
1105 $ echo C >> a
1105 $ echo C >> a
1106 $ hg commit -m 'contentC'
1106 $ hg commit -m 'contentC'
1107 $ echo W >> a
1107 $ echo W >> a
1108 $ hg log -G
1108 $ hg log -G
1109 @ changeset: 4:072f1e8df249
1109 @ changeset: 4:072f1e8df249
1110 | tag: tip
1110 | tag: tip
1111 | user: test
1111 | user: test
1112 | date: Thu Jan 01 00:00:00 1970 +0000
1112 | date: Thu Jan 01 00:00:00 1970 +0000
1113 | summary: contentC
1113 | summary: contentC
1114 |
1114 |
1115 o changeset: 3:ff38df03cc4b
1115 o changeset: 3:ff38df03cc4b
1116 | user: test
1116 | user: test
1117 | date: Thu Jan 01 00:00:00 1970 +0000
1117 | date: Thu Jan 01 00:00:00 1970 +0000
1118 | summary: contentB
1118 | summary: contentB
1119 |
1119 |
1120 o changeset: 2:62aaf3f6fc06
1120 o changeset: 2:62aaf3f6fc06
1121 | parent: 0:f0932f74827e
1121 | parent: 0:f0932f74827e
1122 | user: test
1122 | user: test
1123 | date: Thu Jan 01 00:00:00 1970 +0000
1123 | date: Thu Jan 01 00:00:00 1970 +0000
1124 | summary: unrelated
1124 | summary: unrelated
1125 |
1125 |
1126 | o changeset: 1:fd27c222e3e6
1126 | o changeset: 1:fd27c222e3e6
1127 |/ user: test
1127 |/ user: test
1128 | date: Thu Jan 01 00:00:00 1970 +0000
1128 | date: Thu Jan 01 00:00:00 1970 +0000
1129 | summary: contentB
1129 | summary: contentB
1130 |
1130 |
1131 o changeset: 0:f0932f74827e
1131 o changeset: 0:f0932f74827e
1132 user: test
1132 user: test
1133 date: Thu Jan 01 00:00:00 1970 +0000
1133 date: Thu Jan 01 00:00:00 1970 +0000
1134 summary: contentA
1134 summary: contentA
1135
1135
1136
1136
1137 Annotate should list ancestor of starting revision only
1137 Annotate should list ancestor of starting revision only
1138
1138
1139 $ hg annotate a
1139 $ hg annotate a
1140 0: A
1140 0: A
1141 3: B
1141 3: B
1142 4: C
1142 4: C
1143
1143
1144 $ hg annotate a -r 'wdir()'
1144 $ hg annotate a -r 'wdir()'
1145 0 : A
1145 0 : A
1146 3 : B
1146 3 : B
1147 4 : C
1147 4 : C
1148 4+: W
1148 4+: W
1149
1149
1150 Even when the starting revision is the linkrev-shadowed one:
1150 Even when the starting revision is the linkrev-shadowed one:
1151
1151
1152 $ hg annotate a -r 3
1152 $ hg annotate a -r 3
1153 0: A
1153 0: A
1154 3: B
1154 3: B
1155
1155
1156 $ cd ..
1156 $ cd ..
1157
1157
1158 Issue5360: Deleted chunk in p1 of a merge changeset
1158 Issue5360: Deleted chunk in p1 of a merge changeset
1159
1159
1160 $ hg init repo-5360
1160 $ hg init repo-5360
1161 $ cd repo-5360
1161 $ cd repo-5360
1162 $ echo 1 > a
1162 $ echo 1 > a
1163 $ hg commit -A a -m 1
1163 $ hg commit -A a -m 1
1164 $ echo 2 >> a
1164 $ echo 2 >> a
1165 $ hg commit -m 2
1165 $ hg commit -m 2
1166 $ echo a > a
1166 $ echo a > a
1167 $ hg commit -m a
1167 $ hg commit -m a
1168 $ hg update '.^' -q
1168 $ hg update '.^' -q
1169 $ echo 3 >> a
1169 $ echo 3 >> a
1170 $ hg commit -m 3 -q
1170 $ hg commit -m 3 -q
1171 $ hg merge 2 -q
1171 $ hg merge 2 -q
1172 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
1172 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
1173 [1]
1173 [1]
1174 $ cat a
1174 $ cat a
1175 <<<<<<< working copy: 0a068f0261cf - test: 3
1175 <<<<<<< working copy: 0a068f0261cf - test: 3
1176 1
1176 1
1177 2
1177 2
1178 3
1178 3
1179 ||||||| base
1179 ||||||| base
1180 1
1180 1
1181 2
1181 2
1182 =======
1182 =======
1183 a
1183 a
1184 >>>>>>> merge rev: 9409851bc20a - test: a
1184 >>>>>>> merge rev: 9409851bc20a - test: a
1185 $ cat > a << EOF
1185 $ cat > a << EOF
1186 > b
1186 > b
1187 > 1
1187 > 1
1188 > 2
1188 > 2
1189 > 3
1189 > 3
1190 > a
1190 > a
1191 > EOF
1191 > EOF
1192 $ hg resolve --mark -q
1192 $ hg resolve --mark -q
1193 $ rm a.orig
1193 $ rm a.orig
1194 $ hg commit -m m
1194 $ hg commit -m m
1195 $ hg annotate a
1195 $ hg annotate a
1196 4: b
1196 4: b
1197 0: 1
1197 0: 1
1198 1: 2
1198 1: 2
1199 3: 3
1199 3: 3
1200 2: a
1200 2: a
1201
1201
1202 $ cd ..
1202 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now