##// END OF EJS Templates
util: dates cannot consist entirely of whitespace (issue2732)
Idan Kamara -
r13780:bc7b5d1c default
parent child Browse files
Show More
@@ -1,1577 +1,1580 b''
1 # util.py - Mercurial utility functions and platform specfic implementations
1 # util.py - Mercurial utility functions and platform specfic implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Mercurial utility functions and platform specfic implementations.
10 """Mercurial utility functions and platform specfic implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 from i18n import _
16 from i18n import _
17 import error, osutil, encoding
17 import error, osutil, encoding
18 import errno, re, shutil, sys, tempfile, traceback
18 import errno, re, shutil, sys, tempfile, traceback
19 import os, stat, time, calendar, textwrap, unicodedata, signal
19 import os, stat, time, calendar, textwrap, unicodedata, signal
20 import imp, socket
20 import imp, socket
21
21
22 # Python compatibility
22 # Python compatibility
23
23
24 def sha1(s):
24 def sha1(s):
25 return _fastsha1(s)
25 return _fastsha1(s)
26
26
27 def _fastsha1(s):
27 def _fastsha1(s):
28 # This function will import sha1 from hashlib or sha (whichever is
28 # This function will import sha1 from hashlib or sha (whichever is
29 # available) and overwrite itself with it on the first call.
29 # available) and overwrite itself with it on the first call.
30 # Subsequent calls will go directly to the imported function.
30 # Subsequent calls will go directly to the imported function.
31 if sys.version_info >= (2, 5):
31 if sys.version_info >= (2, 5):
32 from hashlib import sha1 as _sha1
32 from hashlib import sha1 as _sha1
33 else:
33 else:
34 from sha import sha as _sha1
34 from sha import sha as _sha1
35 global _fastsha1, sha1
35 global _fastsha1, sha1
36 _fastsha1 = sha1 = _sha1
36 _fastsha1 = sha1 = _sha1
37 return _sha1(s)
37 return _sha1(s)
38
38
39 import __builtin__
39 import __builtin__
40
40
41 if sys.version_info[0] < 3:
41 if sys.version_info[0] < 3:
42 def fakebuffer(sliceable, offset=0):
42 def fakebuffer(sliceable, offset=0):
43 return sliceable[offset:]
43 return sliceable[offset:]
44 else:
44 else:
45 def fakebuffer(sliceable, offset=0):
45 def fakebuffer(sliceable, offset=0):
46 return memoryview(sliceable)[offset:]
46 return memoryview(sliceable)[offset:]
47 try:
47 try:
48 buffer
48 buffer
49 except NameError:
49 except NameError:
50 __builtin__.buffer = fakebuffer
50 __builtin__.buffer = fakebuffer
51
51
52 import subprocess
52 import subprocess
53 closefds = os.name == 'posix'
53 closefds = os.name == 'posix'
54
54
55 def popen2(cmd, env=None, newlines=False):
55 def popen2(cmd, env=None, newlines=False):
56 # Setting bufsize to -1 lets the system decide the buffer size.
56 # Setting bufsize to -1 lets the system decide the buffer size.
57 # The default for bufsize is 0, meaning unbuffered. This leads to
57 # The default for bufsize is 0, meaning unbuffered. This leads to
58 # poor performance on Mac OS X: http://bugs.python.org/issue4194
58 # poor performance on Mac OS X: http://bugs.python.org/issue4194
59 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
59 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
60 close_fds=closefds,
60 close_fds=closefds,
61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
62 universal_newlines=newlines,
62 universal_newlines=newlines,
63 env=env)
63 env=env)
64 return p.stdin, p.stdout
64 return p.stdin, p.stdout
65
65
66 def popen3(cmd, env=None, newlines=False):
66 def popen3(cmd, env=None, newlines=False):
67 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
67 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
68 close_fds=closefds,
68 close_fds=closefds,
69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
70 stderr=subprocess.PIPE,
70 stderr=subprocess.PIPE,
71 universal_newlines=newlines,
71 universal_newlines=newlines,
72 env=env)
72 env=env)
73 return p.stdin, p.stdout, p.stderr
73 return p.stdin, p.stdout, p.stderr
74
74
75 def version():
75 def version():
76 """Return version information if available."""
76 """Return version information if available."""
77 try:
77 try:
78 import __version__
78 import __version__
79 return __version__.version
79 return __version__.version
80 except ImportError:
80 except ImportError:
81 return 'unknown'
81 return 'unknown'
82
82
83 # used by parsedate
83 # used by parsedate
84 defaultdateformats = (
84 defaultdateformats = (
85 '%Y-%m-%d %H:%M:%S',
85 '%Y-%m-%d %H:%M:%S',
86 '%Y-%m-%d %I:%M:%S%p',
86 '%Y-%m-%d %I:%M:%S%p',
87 '%Y-%m-%d %H:%M',
87 '%Y-%m-%d %H:%M',
88 '%Y-%m-%d %I:%M%p',
88 '%Y-%m-%d %I:%M%p',
89 '%Y-%m-%d',
89 '%Y-%m-%d',
90 '%m-%d',
90 '%m-%d',
91 '%m/%d',
91 '%m/%d',
92 '%m/%d/%y',
92 '%m/%d/%y',
93 '%m/%d/%Y',
93 '%m/%d/%Y',
94 '%a %b %d %H:%M:%S %Y',
94 '%a %b %d %H:%M:%S %Y',
95 '%a %b %d %I:%M:%S%p %Y',
95 '%a %b %d %I:%M:%S%p %Y',
96 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
96 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
97 '%b %d %H:%M:%S %Y',
97 '%b %d %H:%M:%S %Y',
98 '%b %d %I:%M:%S%p %Y',
98 '%b %d %I:%M:%S%p %Y',
99 '%b %d %H:%M:%S',
99 '%b %d %H:%M:%S',
100 '%b %d %I:%M:%S%p',
100 '%b %d %I:%M:%S%p',
101 '%b %d %H:%M',
101 '%b %d %H:%M',
102 '%b %d %I:%M%p',
102 '%b %d %I:%M%p',
103 '%b %d %Y',
103 '%b %d %Y',
104 '%b %d',
104 '%b %d',
105 '%H:%M:%S',
105 '%H:%M:%S',
106 '%I:%M:%S%p',
106 '%I:%M:%S%p',
107 '%H:%M',
107 '%H:%M',
108 '%I:%M%p',
108 '%I:%M%p',
109 )
109 )
110
110
111 extendeddateformats = defaultdateformats + (
111 extendeddateformats = defaultdateformats + (
112 "%Y",
112 "%Y",
113 "%Y-%m",
113 "%Y-%m",
114 "%b",
114 "%b",
115 "%b %Y",
115 "%b %Y",
116 )
116 )
117
117
118 def cachefunc(func):
118 def cachefunc(func):
119 '''cache the result of function calls'''
119 '''cache the result of function calls'''
120 # XXX doesn't handle keywords args
120 # XXX doesn't handle keywords args
121 cache = {}
121 cache = {}
122 if func.func_code.co_argcount == 1:
122 if func.func_code.co_argcount == 1:
123 # we gain a small amount of time because
123 # we gain a small amount of time because
124 # we don't need to pack/unpack the list
124 # we don't need to pack/unpack the list
125 def f(arg):
125 def f(arg):
126 if arg not in cache:
126 if arg not in cache:
127 cache[arg] = func(arg)
127 cache[arg] = func(arg)
128 return cache[arg]
128 return cache[arg]
129 else:
129 else:
130 def f(*args):
130 def f(*args):
131 if args not in cache:
131 if args not in cache:
132 cache[args] = func(*args)
132 cache[args] = func(*args)
133 return cache[args]
133 return cache[args]
134
134
135 return f
135 return f
136
136
137 def lrucachefunc(func):
137 def lrucachefunc(func):
138 '''cache most recent results of function calls'''
138 '''cache most recent results of function calls'''
139 cache = {}
139 cache = {}
140 order = []
140 order = []
141 if func.func_code.co_argcount == 1:
141 if func.func_code.co_argcount == 1:
142 def f(arg):
142 def f(arg):
143 if arg not in cache:
143 if arg not in cache:
144 if len(cache) > 20:
144 if len(cache) > 20:
145 del cache[order.pop(0)]
145 del cache[order.pop(0)]
146 cache[arg] = func(arg)
146 cache[arg] = func(arg)
147 else:
147 else:
148 order.remove(arg)
148 order.remove(arg)
149 order.append(arg)
149 order.append(arg)
150 return cache[arg]
150 return cache[arg]
151 else:
151 else:
152 def f(*args):
152 def f(*args):
153 if args not in cache:
153 if args not in cache:
154 if len(cache) > 20:
154 if len(cache) > 20:
155 del cache[order.pop(0)]
155 del cache[order.pop(0)]
156 cache[args] = func(*args)
156 cache[args] = func(*args)
157 else:
157 else:
158 order.remove(args)
158 order.remove(args)
159 order.append(args)
159 order.append(args)
160 return cache[args]
160 return cache[args]
161
161
162 return f
162 return f
163
163
164 class propertycache(object):
164 class propertycache(object):
165 def __init__(self, func):
165 def __init__(self, func):
166 self.func = func
166 self.func = func
167 self.name = func.__name__
167 self.name = func.__name__
168 def __get__(self, obj, type=None):
168 def __get__(self, obj, type=None):
169 result = self.func(obj)
169 result = self.func(obj)
170 setattr(obj, self.name, result)
170 setattr(obj, self.name, result)
171 return result
171 return result
172
172
173 def pipefilter(s, cmd):
173 def pipefilter(s, cmd):
174 '''filter string S through command CMD, returning its output'''
174 '''filter string S through command CMD, returning its output'''
175 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
175 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
176 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
176 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
177 pout, perr = p.communicate(s)
177 pout, perr = p.communicate(s)
178 return pout
178 return pout
179
179
180 def tempfilter(s, cmd):
180 def tempfilter(s, cmd):
181 '''filter string S through a pair of temporary files with CMD.
181 '''filter string S through a pair of temporary files with CMD.
182 CMD is used as a template to create the real command to be run,
182 CMD is used as a template to create the real command to be run,
183 with the strings INFILE and OUTFILE replaced by the real names of
183 with the strings INFILE and OUTFILE replaced by the real names of
184 the temporary files generated.'''
184 the temporary files generated.'''
185 inname, outname = None, None
185 inname, outname = None, None
186 try:
186 try:
187 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
187 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
188 fp = os.fdopen(infd, 'wb')
188 fp = os.fdopen(infd, 'wb')
189 fp.write(s)
189 fp.write(s)
190 fp.close()
190 fp.close()
191 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
191 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
192 os.close(outfd)
192 os.close(outfd)
193 cmd = cmd.replace('INFILE', inname)
193 cmd = cmd.replace('INFILE', inname)
194 cmd = cmd.replace('OUTFILE', outname)
194 cmd = cmd.replace('OUTFILE', outname)
195 code = os.system(cmd)
195 code = os.system(cmd)
196 if sys.platform == 'OpenVMS' and code & 1:
196 if sys.platform == 'OpenVMS' and code & 1:
197 code = 0
197 code = 0
198 if code:
198 if code:
199 raise Abort(_("command '%s' failed: %s") %
199 raise Abort(_("command '%s' failed: %s") %
200 (cmd, explain_exit(code)))
200 (cmd, explain_exit(code)))
201 fp = open(outname, 'rb')
201 fp = open(outname, 'rb')
202 r = fp.read()
202 r = fp.read()
203 fp.close()
203 fp.close()
204 return r
204 return r
205 finally:
205 finally:
206 try:
206 try:
207 if inname:
207 if inname:
208 os.unlink(inname)
208 os.unlink(inname)
209 except:
209 except:
210 pass
210 pass
211 try:
211 try:
212 if outname:
212 if outname:
213 os.unlink(outname)
213 os.unlink(outname)
214 except:
214 except:
215 pass
215 pass
216
216
217 filtertable = {
217 filtertable = {
218 'tempfile:': tempfilter,
218 'tempfile:': tempfilter,
219 'pipe:': pipefilter,
219 'pipe:': pipefilter,
220 }
220 }
221
221
222 def filter(s, cmd):
222 def filter(s, cmd):
223 "filter a string through a command that transforms its input to its output"
223 "filter a string through a command that transforms its input to its output"
224 for name, fn in filtertable.iteritems():
224 for name, fn in filtertable.iteritems():
225 if cmd.startswith(name):
225 if cmd.startswith(name):
226 return fn(s, cmd[len(name):].lstrip())
226 return fn(s, cmd[len(name):].lstrip())
227 return pipefilter(s, cmd)
227 return pipefilter(s, cmd)
228
228
229 def binary(s):
229 def binary(s):
230 """return true if a string is binary data"""
230 """return true if a string is binary data"""
231 return bool(s and '\0' in s)
231 return bool(s and '\0' in s)
232
232
233 def increasingchunks(source, min=1024, max=65536):
233 def increasingchunks(source, min=1024, max=65536):
234 '''return no less than min bytes per chunk while data remains,
234 '''return no less than min bytes per chunk while data remains,
235 doubling min after each chunk until it reaches max'''
235 doubling min after each chunk until it reaches max'''
236 def log2(x):
236 def log2(x):
237 if not x:
237 if not x:
238 return 0
238 return 0
239 i = 0
239 i = 0
240 while x:
240 while x:
241 x >>= 1
241 x >>= 1
242 i += 1
242 i += 1
243 return i - 1
243 return i - 1
244
244
245 buf = []
245 buf = []
246 blen = 0
246 blen = 0
247 for chunk in source:
247 for chunk in source:
248 buf.append(chunk)
248 buf.append(chunk)
249 blen += len(chunk)
249 blen += len(chunk)
250 if blen >= min:
250 if blen >= min:
251 if min < max:
251 if min < max:
252 min = min << 1
252 min = min << 1
253 nmin = 1 << log2(blen)
253 nmin = 1 << log2(blen)
254 if nmin > min:
254 if nmin > min:
255 min = nmin
255 min = nmin
256 if min > max:
256 if min > max:
257 min = max
257 min = max
258 yield ''.join(buf)
258 yield ''.join(buf)
259 blen = 0
259 blen = 0
260 buf = []
260 buf = []
261 if buf:
261 if buf:
262 yield ''.join(buf)
262 yield ''.join(buf)
263
263
264 Abort = error.Abort
264 Abort = error.Abort
265
265
266 def always(fn):
266 def always(fn):
267 return True
267 return True
268
268
269 def never(fn):
269 def never(fn):
270 return False
270 return False
271
271
272 def pathto(root, n1, n2):
272 def pathto(root, n1, n2):
273 '''return the relative path from one place to another.
273 '''return the relative path from one place to another.
274 root should use os.sep to separate directories
274 root should use os.sep to separate directories
275 n1 should use os.sep to separate directories
275 n1 should use os.sep to separate directories
276 n2 should use "/" to separate directories
276 n2 should use "/" to separate directories
277 returns an os.sep-separated path.
277 returns an os.sep-separated path.
278
278
279 If n1 is a relative path, it's assumed it's
279 If n1 is a relative path, it's assumed it's
280 relative to root.
280 relative to root.
281 n2 should always be relative to root.
281 n2 should always be relative to root.
282 '''
282 '''
283 if not n1:
283 if not n1:
284 return localpath(n2)
284 return localpath(n2)
285 if os.path.isabs(n1):
285 if os.path.isabs(n1):
286 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
286 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
287 return os.path.join(root, localpath(n2))
287 return os.path.join(root, localpath(n2))
288 n2 = '/'.join((pconvert(root), n2))
288 n2 = '/'.join((pconvert(root), n2))
289 a, b = splitpath(n1), n2.split('/')
289 a, b = splitpath(n1), n2.split('/')
290 a.reverse()
290 a.reverse()
291 b.reverse()
291 b.reverse()
292 while a and b and a[-1] == b[-1]:
292 while a and b and a[-1] == b[-1]:
293 a.pop()
293 a.pop()
294 b.pop()
294 b.pop()
295 b.reverse()
295 b.reverse()
296 return os.sep.join((['..'] * len(a)) + b) or '.'
296 return os.sep.join((['..'] * len(a)) + b) or '.'
297
297
298 def canonpath(root, cwd, myname, auditor=None):
298 def canonpath(root, cwd, myname, auditor=None):
299 """return the canonical path of myname, given cwd and root"""
299 """return the canonical path of myname, given cwd and root"""
300 if endswithsep(root):
300 if endswithsep(root):
301 rootsep = root
301 rootsep = root
302 else:
302 else:
303 rootsep = root + os.sep
303 rootsep = root + os.sep
304 name = myname
304 name = myname
305 if not os.path.isabs(name):
305 if not os.path.isabs(name):
306 name = os.path.join(root, cwd, name)
306 name = os.path.join(root, cwd, name)
307 name = os.path.normpath(name)
307 name = os.path.normpath(name)
308 if auditor is None:
308 if auditor is None:
309 auditor = path_auditor(root)
309 auditor = path_auditor(root)
310 if name != rootsep and name.startswith(rootsep):
310 if name != rootsep and name.startswith(rootsep):
311 name = name[len(rootsep):]
311 name = name[len(rootsep):]
312 auditor(name)
312 auditor(name)
313 return pconvert(name)
313 return pconvert(name)
314 elif name == root:
314 elif name == root:
315 return ''
315 return ''
316 else:
316 else:
317 # Determine whether `name' is in the hierarchy at or beneath `root',
317 # Determine whether `name' is in the hierarchy at or beneath `root',
318 # by iterating name=dirname(name) until that causes no change (can't
318 # by iterating name=dirname(name) until that causes no change (can't
319 # check name == '/', because that doesn't work on windows). For each
319 # check name == '/', because that doesn't work on windows). For each
320 # `name', compare dev/inode numbers. If they match, the list `rel'
320 # `name', compare dev/inode numbers. If they match, the list `rel'
321 # holds the reversed list of components making up the relative file
321 # holds the reversed list of components making up the relative file
322 # name we want.
322 # name we want.
323 root_st = os.stat(root)
323 root_st = os.stat(root)
324 rel = []
324 rel = []
325 while True:
325 while True:
326 try:
326 try:
327 name_st = os.stat(name)
327 name_st = os.stat(name)
328 except OSError:
328 except OSError:
329 break
329 break
330 if samestat(name_st, root_st):
330 if samestat(name_st, root_st):
331 if not rel:
331 if not rel:
332 # name was actually the same as root (maybe a symlink)
332 # name was actually the same as root (maybe a symlink)
333 return ''
333 return ''
334 rel.reverse()
334 rel.reverse()
335 name = os.path.join(*rel)
335 name = os.path.join(*rel)
336 auditor(name)
336 auditor(name)
337 return pconvert(name)
337 return pconvert(name)
338 dirname, basename = os.path.split(name)
338 dirname, basename = os.path.split(name)
339 rel.append(basename)
339 rel.append(basename)
340 if dirname == name:
340 if dirname == name:
341 break
341 break
342 name = dirname
342 name = dirname
343
343
344 raise Abort('%s not under root' % myname)
344 raise Abort('%s not under root' % myname)
345
345
346 _hgexecutable = None
346 _hgexecutable = None
347
347
348 def main_is_frozen():
348 def main_is_frozen():
349 """return True if we are a frozen executable.
349 """return True if we are a frozen executable.
350
350
351 The code supports py2exe (most common, Windows only) and tools/freeze
351 The code supports py2exe (most common, Windows only) and tools/freeze
352 (portable, not much used).
352 (portable, not much used).
353 """
353 """
354 return (hasattr(sys, "frozen") or # new py2exe
354 return (hasattr(sys, "frozen") or # new py2exe
355 hasattr(sys, "importers") or # old py2exe
355 hasattr(sys, "importers") or # old py2exe
356 imp.is_frozen("__main__")) # tools/freeze
356 imp.is_frozen("__main__")) # tools/freeze
357
357
358 def hgexecutable():
358 def hgexecutable():
359 """return location of the 'hg' executable.
359 """return location of the 'hg' executable.
360
360
361 Defaults to $HG or 'hg' in the search path.
361 Defaults to $HG or 'hg' in the search path.
362 """
362 """
363 if _hgexecutable is None:
363 if _hgexecutable is None:
364 hg = os.environ.get('HG')
364 hg = os.environ.get('HG')
365 if hg:
365 if hg:
366 set_hgexecutable(hg)
366 set_hgexecutable(hg)
367 elif main_is_frozen():
367 elif main_is_frozen():
368 set_hgexecutable(sys.executable)
368 set_hgexecutable(sys.executable)
369 else:
369 else:
370 exe = find_exe('hg') or os.path.basename(sys.argv[0])
370 exe = find_exe('hg') or os.path.basename(sys.argv[0])
371 set_hgexecutable(exe)
371 set_hgexecutable(exe)
372 return _hgexecutable
372 return _hgexecutable
373
373
374 def set_hgexecutable(path):
374 def set_hgexecutable(path):
375 """set location of the 'hg' executable"""
375 """set location of the 'hg' executable"""
376 global _hgexecutable
376 global _hgexecutable
377 _hgexecutable = path
377 _hgexecutable = path
378
378
379 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
379 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
380 '''enhanced shell command execution.
380 '''enhanced shell command execution.
381 run with environment maybe modified, maybe in different dir.
381 run with environment maybe modified, maybe in different dir.
382
382
383 if command fails and onerr is None, return status. if ui object,
383 if command fails and onerr is None, return status. if ui object,
384 print error message and return status, else raise onerr object as
384 print error message and return status, else raise onerr object as
385 exception.
385 exception.
386
386
387 if out is specified, it is assumed to be a file-like object that has a
387 if out is specified, it is assumed to be a file-like object that has a
388 write() method. stdout and stderr will be redirected to out.'''
388 write() method. stdout and stderr will be redirected to out.'''
389 try:
389 try:
390 sys.stdout.flush()
390 sys.stdout.flush()
391 except Exception:
391 except Exception:
392 pass
392 pass
393 def py2shell(val):
393 def py2shell(val):
394 'convert python object into string that is useful to shell'
394 'convert python object into string that is useful to shell'
395 if val is None or val is False:
395 if val is None or val is False:
396 return '0'
396 return '0'
397 if val is True:
397 if val is True:
398 return '1'
398 return '1'
399 return str(val)
399 return str(val)
400 origcmd = cmd
400 origcmd = cmd
401 cmd = quotecommand(cmd)
401 cmd = quotecommand(cmd)
402 env = dict(os.environ)
402 env = dict(os.environ)
403 env.update((k, py2shell(v)) for k, v in environ.iteritems())
403 env.update((k, py2shell(v)) for k, v in environ.iteritems())
404 env['HG'] = hgexecutable()
404 env['HG'] = hgexecutable()
405 if out is None:
405 if out is None:
406 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
406 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
407 env=env, cwd=cwd)
407 env=env, cwd=cwd)
408 else:
408 else:
409 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
409 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
410 env=env, cwd=cwd, stdout=subprocess.PIPE,
410 env=env, cwd=cwd, stdout=subprocess.PIPE,
411 stderr=subprocess.STDOUT)
411 stderr=subprocess.STDOUT)
412 for line in proc.stdout:
412 for line in proc.stdout:
413 out.write(line)
413 out.write(line)
414 proc.wait()
414 proc.wait()
415 rc = proc.returncode
415 rc = proc.returncode
416 if sys.platform == 'OpenVMS' and rc & 1:
416 if sys.platform == 'OpenVMS' and rc & 1:
417 rc = 0
417 rc = 0
418 if rc and onerr:
418 if rc and onerr:
419 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
419 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
420 explain_exit(rc)[0])
420 explain_exit(rc)[0])
421 if errprefix:
421 if errprefix:
422 errmsg = '%s: %s' % (errprefix, errmsg)
422 errmsg = '%s: %s' % (errprefix, errmsg)
423 try:
423 try:
424 onerr.warn(errmsg + '\n')
424 onerr.warn(errmsg + '\n')
425 except AttributeError:
425 except AttributeError:
426 raise onerr(errmsg)
426 raise onerr(errmsg)
427 return rc
427 return rc
428
428
429 def checksignature(func):
429 def checksignature(func):
430 '''wrap a function with code to check for calling errors'''
430 '''wrap a function with code to check for calling errors'''
431 def check(*args, **kwargs):
431 def check(*args, **kwargs):
432 try:
432 try:
433 return func(*args, **kwargs)
433 return func(*args, **kwargs)
434 except TypeError:
434 except TypeError:
435 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
435 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
436 raise error.SignatureError
436 raise error.SignatureError
437 raise
437 raise
438
438
439 return check
439 return check
440
440
441 def unlinkpath(f):
441 def unlinkpath(f):
442 """unlink and remove the directory if it is empty"""
442 """unlink and remove the directory if it is empty"""
443 os.unlink(f)
443 os.unlink(f)
444 # try removing directories that might now be empty
444 # try removing directories that might now be empty
445 try:
445 try:
446 os.removedirs(os.path.dirname(f))
446 os.removedirs(os.path.dirname(f))
447 except OSError:
447 except OSError:
448 pass
448 pass
449
449
450 def copyfile(src, dest):
450 def copyfile(src, dest):
451 "copy a file, preserving mode and atime/mtime"
451 "copy a file, preserving mode and atime/mtime"
452 if os.path.islink(src):
452 if os.path.islink(src):
453 try:
453 try:
454 os.unlink(dest)
454 os.unlink(dest)
455 except:
455 except:
456 pass
456 pass
457 os.symlink(os.readlink(src), dest)
457 os.symlink(os.readlink(src), dest)
458 else:
458 else:
459 try:
459 try:
460 shutil.copyfile(src, dest)
460 shutil.copyfile(src, dest)
461 shutil.copymode(src, dest)
461 shutil.copymode(src, dest)
462 except shutil.Error, inst:
462 except shutil.Error, inst:
463 raise Abort(str(inst))
463 raise Abort(str(inst))
464
464
465 def copyfiles(src, dst, hardlink=None):
465 def copyfiles(src, dst, hardlink=None):
466 """Copy a directory tree using hardlinks if possible"""
466 """Copy a directory tree using hardlinks if possible"""
467
467
468 if hardlink is None:
468 if hardlink is None:
469 hardlink = (os.stat(src).st_dev ==
469 hardlink = (os.stat(src).st_dev ==
470 os.stat(os.path.dirname(dst)).st_dev)
470 os.stat(os.path.dirname(dst)).st_dev)
471
471
472 num = 0
472 num = 0
473 if os.path.isdir(src):
473 if os.path.isdir(src):
474 os.mkdir(dst)
474 os.mkdir(dst)
475 for name, kind in osutil.listdir(src):
475 for name, kind in osutil.listdir(src):
476 srcname = os.path.join(src, name)
476 srcname = os.path.join(src, name)
477 dstname = os.path.join(dst, name)
477 dstname = os.path.join(dst, name)
478 hardlink, n = copyfiles(srcname, dstname, hardlink)
478 hardlink, n = copyfiles(srcname, dstname, hardlink)
479 num += n
479 num += n
480 else:
480 else:
481 if hardlink:
481 if hardlink:
482 try:
482 try:
483 os_link(src, dst)
483 os_link(src, dst)
484 except (IOError, OSError):
484 except (IOError, OSError):
485 hardlink = False
485 hardlink = False
486 shutil.copy(src, dst)
486 shutil.copy(src, dst)
487 else:
487 else:
488 shutil.copy(src, dst)
488 shutil.copy(src, dst)
489 num += 1
489 num += 1
490
490
491 return hardlink, num
491 return hardlink, num
492
492
493 class path_auditor(object):
493 class path_auditor(object):
494 '''ensure that a filesystem path contains no banned components.
494 '''ensure that a filesystem path contains no banned components.
495 the following properties of a path are checked:
495 the following properties of a path are checked:
496
496
497 - ends with a directory separator
497 - ends with a directory separator
498 - under top-level .hg
498 - under top-level .hg
499 - starts at the root of a windows drive
499 - starts at the root of a windows drive
500 - contains ".."
500 - contains ".."
501 - traverses a symlink (e.g. a/symlink_here/b)
501 - traverses a symlink (e.g. a/symlink_here/b)
502 - inside a nested repository (a callback can be used to approve
502 - inside a nested repository (a callback can be used to approve
503 some nested repositories, e.g., subrepositories)
503 some nested repositories, e.g., subrepositories)
504 '''
504 '''
505
505
506 def __init__(self, root, callback=None):
506 def __init__(self, root, callback=None):
507 self.audited = set()
507 self.audited = set()
508 self.auditeddir = set()
508 self.auditeddir = set()
509 self.root = root
509 self.root = root
510 self.callback = callback
510 self.callback = callback
511
511
512 def __call__(self, path):
512 def __call__(self, path):
513 if path in self.audited:
513 if path in self.audited:
514 return
514 return
515 # AIX ignores "/" at end of path, others raise EISDIR.
515 # AIX ignores "/" at end of path, others raise EISDIR.
516 if endswithsep(path):
516 if endswithsep(path):
517 raise Abort(_("path ends in directory separator: %s") % path)
517 raise Abort(_("path ends in directory separator: %s") % path)
518 normpath = os.path.normcase(path)
518 normpath = os.path.normcase(path)
519 parts = splitpath(normpath)
519 parts = splitpath(normpath)
520 if (os.path.splitdrive(path)[0]
520 if (os.path.splitdrive(path)[0]
521 or parts[0].lower() in ('.hg', '.hg.', '')
521 or parts[0].lower() in ('.hg', '.hg.', '')
522 or os.pardir in parts):
522 or os.pardir in parts):
523 raise Abort(_("path contains illegal component: %s") % path)
523 raise Abort(_("path contains illegal component: %s") % path)
524 if '.hg' in path.lower():
524 if '.hg' in path.lower():
525 lparts = [p.lower() for p in parts]
525 lparts = [p.lower() for p in parts]
526 for p in '.hg', '.hg.':
526 for p in '.hg', '.hg.':
527 if p in lparts[1:]:
527 if p in lparts[1:]:
528 pos = lparts.index(p)
528 pos = lparts.index(p)
529 base = os.path.join(*parts[:pos])
529 base = os.path.join(*parts[:pos])
530 raise Abort(_('path %r is inside repo %r') % (path, base))
530 raise Abort(_('path %r is inside repo %r') % (path, base))
531 def check(prefix):
531 def check(prefix):
532 curpath = os.path.join(self.root, prefix)
532 curpath = os.path.join(self.root, prefix)
533 try:
533 try:
534 st = os.lstat(curpath)
534 st = os.lstat(curpath)
535 except OSError, err:
535 except OSError, err:
536 # EINVAL can be raised as invalid path syntax under win32.
536 # EINVAL can be raised as invalid path syntax under win32.
537 # They must be ignored for patterns can be checked too.
537 # They must be ignored for patterns can be checked too.
538 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
538 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
539 raise
539 raise
540 else:
540 else:
541 if stat.S_ISLNK(st.st_mode):
541 if stat.S_ISLNK(st.st_mode):
542 raise Abort(_('path %r traverses symbolic link %r') %
542 raise Abort(_('path %r traverses symbolic link %r') %
543 (path, prefix))
543 (path, prefix))
544 elif (stat.S_ISDIR(st.st_mode) and
544 elif (stat.S_ISDIR(st.st_mode) and
545 os.path.isdir(os.path.join(curpath, '.hg'))):
545 os.path.isdir(os.path.join(curpath, '.hg'))):
546 if not self.callback or not self.callback(curpath):
546 if not self.callback or not self.callback(curpath):
547 raise Abort(_('path %r is inside repo %r') %
547 raise Abort(_('path %r is inside repo %r') %
548 (path, prefix))
548 (path, prefix))
549 parts.pop()
549 parts.pop()
550 prefixes = []
550 prefixes = []
551 while parts:
551 while parts:
552 prefix = os.sep.join(parts)
552 prefix = os.sep.join(parts)
553 if prefix in self.auditeddir:
553 if prefix in self.auditeddir:
554 break
554 break
555 check(prefix)
555 check(prefix)
556 prefixes.append(prefix)
556 prefixes.append(prefix)
557 parts.pop()
557 parts.pop()
558
558
559 self.audited.add(path)
559 self.audited.add(path)
560 # only add prefixes to the cache after checking everything: we don't
560 # only add prefixes to the cache after checking everything: we don't
561 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
561 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
562 self.auditeddir.update(prefixes)
562 self.auditeddir.update(prefixes)
563
563
564 def lookup_reg(key, name=None, scope=None):
564 def lookup_reg(key, name=None, scope=None):
565 return None
565 return None
566
566
567 def hidewindow():
567 def hidewindow():
568 """Hide current shell window.
568 """Hide current shell window.
569
569
570 Used to hide the window opened when starting asynchronous
570 Used to hide the window opened when starting asynchronous
571 child process under Windows, unneeded on other systems.
571 child process under Windows, unneeded on other systems.
572 """
572 """
573 pass
573 pass
574
574
575 if os.name == 'nt':
575 if os.name == 'nt':
576 from windows import *
576 from windows import *
577 else:
577 else:
578 from posix import *
578 from posix import *
579
579
580 def makelock(info, pathname):
580 def makelock(info, pathname):
581 try:
581 try:
582 return os.symlink(info, pathname)
582 return os.symlink(info, pathname)
583 except OSError, why:
583 except OSError, why:
584 if why.errno == errno.EEXIST:
584 if why.errno == errno.EEXIST:
585 raise
585 raise
586 except AttributeError: # no symlink in os
586 except AttributeError: # no symlink in os
587 pass
587 pass
588
588
589 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
589 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
590 os.write(ld, info)
590 os.write(ld, info)
591 os.close(ld)
591 os.close(ld)
592
592
593 def readlock(pathname):
593 def readlock(pathname):
594 try:
594 try:
595 return os.readlink(pathname)
595 return os.readlink(pathname)
596 except OSError, why:
596 except OSError, why:
597 if why.errno not in (errno.EINVAL, errno.ENOSYS):
597 if why.errno not in (errno.EINVAL, errno.ENOSYS):
598 raise
598 raise
599 except AttributeError: # no symlink in os
599 except AttributeError: # no symlink in os
600 pass
600 pass
601 fp = posixfile(pathname)
601 fp = posixfile(pathname)
602 r = fp.read()
602 r = fp.read()
603 fp.close()
603 fp.close()
604 return r
604 return r
605
605
606 def fstat(fp):
606 def fstat(fp):
607 '''stat file object that may not have fileno method.'''
607 '''stat file object that may not have fileno method.'''
608 try:
608 try:
609 return os.fstat(fp.fileno())
609 return os.fstat(fp.fileno())
610 except AttributeError:
610 except AttributeError:
611 return os.stat(fp.name)
611 return os.stat(fp.name)
612
612
613 # File system features
613 # File system features
614
614
615 def checkcase(path):
615 def checkcase(path):
616 """
616 """
617 Check whether the given path is on a case-sensitive filesystem
617 Check whether the given path is on a case-sensitive filesystem
618
618
619 Requires a path (like /foo/.hg) ending with a foldable final
619 Requires a path (like /foo/.hg) ending with a foldable final
620 directory component.
620 directory component.
621 """
621 """
622 s1 = os.stat(path)
622 s1 = os.stat(path)
623 d, b = os.path.split(path)
623 d, b = os.path.split(path)
624 p2 = os.path.join(d, b.upper())
624 p2 = os.path.join(d, b.upper())
625 if path == p2:
625 if path == p2:
626 p2 = os.path.join(d, b.lower())
626 p2 = os.path.join(d, b.lower())
627 try:
627 try:
628 s2 = os.stat(p2)
628 s2 = os.stat(p2)
629 if s2 == s1:
629 if s2 == s1:
630 return False
630 return False
631 return True
631 return True
632 except:
632 except:
633 return True
633 return True
634
634
635 _fspathcache = {}
635 _fspathcache = {}
636 def fspath(name, root):
636 def fspath(name, root):
637 '''Get name in the case stored in the filesystem
637 '''Get name in the case stored in the filesystem
638
638
639 The name is either relative to root, or it is an absolute path starting
639 The name is either relative to root, or it is an absolute path starting
640 with root. Note that this function is unnecessary, and should not be
640 with root. Note that this function is unnecessary, and should not be
641 called, for case-sensitive filesystems (simply because it's expensive).
641 called, for case-sensitive filesystems (simply because it's expensive).
642 '''
642 '''
643 # If name is absolute, make it relative
643 # If name is absolute, make it relative
644 if name.lower().startswith(root.lower()):
644 if name.lower().startswith(root.lower()):
645 l = len(root)
645 l = len(root)
646 if name[l] == os.sep or name[l] == os.altsep:
646 if name[l] == os.sep or name[l] == os.altsep:
647 l = l + 1
647 l = l + 1
648 name = name[l:]
648 name = name[l:]
649
649
650 if not os.path.lexists(os.path.join(root, name)):
650 if not os.path.lexists(os.path.join(root, name)):
651 return None
651 return None
652
652
653 seps = os.sep
653 seps = os.sep
654 if os.altsep:
654 if os.altsep:
655 seps = seps + os.altsep
655 seps = seps + os.altsep
656 # Protect backslashes. This gets silly very quickly.
656 # Protect backslashes. This gets silly very quickly.
657 seps.replace('\\','\\\\')
657 seps.replace('\\','\\\\')
658 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
658 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
659 dir = os.path.normcase(os.path.normpath(root))
659 dir = os.path.normcase(os.path.normpath(root))
660 result = []
660 result = []
661 for part, sep in pattern.findall(name):
661 for part, sep in pattern.findall(name):
662 if sep:
662 if sep:
663 result.append(sep)
663 result.append(sep)
664 continue
664 continue
665
665
666 if dir not in _fspathcache:
666 if dir not in _fspathcache:
667 _fspathcache[dir] = os.listdir(dir)
667 _fspathcache[dir] = os.listdir(dir)
668 contents = _fspathcache[dir]
668 contents = _fspathcache[dir]
669
669
670 lpart = part.lower()
670 lpart = part.lower()
671 lenp = len(part)
671 lenp = len(part)
672 for n in contents:
672 for n in contents:
673 if lenp == len(n) and n.lower() == lpart:
673 if lenp == len(n) and n.lower() == lpart:
674 result.append(n)
674 result.append(n)
675 break
675 break
676 else:
676 else:
677 # Cannot happen, as the file exists!
677 # Cannot happen, as the file exists!
678 result.append(part)
678 result.append(part)
679 dir = os.path.join(dir, lpart)
679 dir = os.path.join(dir, lpart)
680
680
681 return ''.join(result)
681 return ''.join(result)
682
682
683 def checkexec(path):
683 def checkexec(path):
684 """
684 """
685 Check whether the given path is on a filesystem with UNIX-like exec flags
685 Check whether the given path is on a filesystem with UNIX-like exec flags
686
686
687 Requires a directory (like /foo/.hg)
687 Requires a directory (like /foo/.hg)
688 """
688 """
689
689
690 # VFAT on some Linux versions can flip mode but it doesn't persist
690 # VFAT on some Linux versions can flip mode but it doesn't persist
691 # a FS remount. Frequently we can detect it if files are created
691 # a FS remount. Frequently we can detect it if files are created
692 # with exec bit on.
692 # with exec bit on.
693
693
694 try:
694 try:
695 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
695 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
696 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
696 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
697 try:
697 try:
698 os.close(fh)
698 os.close(fh)
699 m = os.stat(fn).st_mode & 0777
699 m = os.stat(fn).st_mode & 0777
700 new_file_has_exec = m & EXECFLAGS
700 new_file_has_exec = m & EXECFLAGS
701 os.chmod(fn, m ^ EXECFLAGS)
701 os.chmod(fn, m ^ EXECFLAGS)
702 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
702 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
703 finally:
703 finally:
704 os.unlink(fn)
704 os.unlink(fn)
705 except (IOError, OSError):
705 except (IOError, OSError):
706 # we don't care, the user probably won't be able to commit anyway
706 # we don't care, the user probably won't be able to commit anyway
707 return False
707 return False
708 return not (new_file_has_exec or exec_flags_cannot_flip)
708 return not (new_file_has_exec or exec_flags_cannot_flip)
709
709
710 def checklink(path):
710 def checklink(path):
711 """check whether the given path is on a symlink-capable filesystem"""
711 """check whether the given path is on a symlink-capable filesystem"""
712 # mktemp is not racy because symlink creation will fail if the
712 # mktemp is not racy because symlink creation will fail if the
713 # file already exists
713 # file already exists
714 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
714 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
715 try:
715 try:
716 os.symlink(".", name)
716 os.symlink(".", name)
717 os.unlink(name)
717 os.unlink(name)
718 return True
718 return True
719 except (OSError, AttributeError):
719 except (OSError, AttributeError):
720 return False
720 return False
721
721
722 def checknlink(testfile):
722 def checknlink(testfile):
723 '''check whether hardlink count reporting works properly'''
723 '''check whether hardlink count reporting works properly'''
724
724
725 # testfile may be open, so we need a separate file for checking to
725 # testfile may be open, so we need a separate file for checking to
726 # work around issue2543 (or testfile may get lost on Samba shares)
726 # work around issue2543 (or testfile may get lost on Samba shares)
727 f1 = testfile + ".hgtmp1"
727 f1 = testfile + ".hgtmp1"
728 if os.path.lexists(f1):
728 if os.path.lexists(f1):
729 return False
729 return False
730 try:
730 try:
731 posixfile(f1, 'w').close()
731 posixfile(f1, 'w').close()
732 except IOError:
732 except IOError:
733 return False
733 return False
734
734
735 f2 = testfile + ".hgtmp2"
735 f2 = testfile + ".hgtmp2"
736 fd = None
736 fd = None
737 try:
737 try:
738 try:
738 try:
739 os_link(f1, f2)
739 os_link(f1, f2)
740 except OSError:
740 except OSError:
741 return False
741 return False
742
742
743 # nlinks() may behave differently for files on Windows shares if
743 # nlinks() may behave differently for files on Windows shares if
744 # the file is open.
744 # the file is open.
745 fd = posixfile(f2)
745 fd = posixfile(f2)
746 return nlinks(f2) > 1
746 return nlinks(f2) > 1
747 finally:
747 finally:
748 if fd is not None:
748 if fd is not None:
749 fd.close()
749 fd.close()
750 for f in (f1, f2):
750 for f in (f1, f2):
751 try:
751 try:
752 os.unlink(f)
752 os.unlink(f)
753 except OSError:
753 except OSError:
754 pass
754 pass
755
755
756 return False
756 return False
757
757
758 def endswithsep(path):
758 def endswithsep(path):
759 '''Check path ends with os.sep or os.altsep.'''
759 '''Check path ends with os.sep or os.altsep.'''
760 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
760 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
761
761
762 def splitpath(path):
762 def splitpath(path):
763 '''Split path by os.sep.
763 '''Split path by os.sep.
764 Note that this function does not use os.altsep because this is
764 Note that this function does not use os.altsep because this is
765 an alternative of simple "xxx.split(os.sep)".
765 an alternative of simple "xxx.split(os.sep)".
766 It is recommended to use os.path.normpath() before using this
766 It is recommended to use os.path.normpath() before using this
767 function if need.'''
767 function if need.'''
768 return path.split(os.sep)
768 return path.split(os.sep)
769
769
770 def gui():
770 def gui():
771 '''Are we running in a GUI?'''
771 '''Are we running in a GUI?'''
772 if sys.platform == 'darwin':
772 if sys.platform == 'darwin':
773 if 'SSH_CONNECTION' in os.environ:
773 if 'SSH_CONNECTION' in os.environ:
774 # handle SSH access to a box where the user is logged in
774 # handle SSH access to a box where the user is logged in
775 return False
775 return False
776 elif getattr(osutil, 'isgui', None):
776 elif getattr(osutil, 'isgui', None):
777 # check if a CoreGraphics session is available
777 # check if a CoreGraphics session is available
778 return osutil.isgui()
778 return osutil.isgui()
779 else:
779 else:
780 # pure build; use a safe default
780 # pure build; use a safe default
781 return True
781 return True
782 else:
782 else:
783 return os.name == "nt" or os.environ.get("DISPLAY")
783 return os.name == "nt" or os.environ.get("DISPLAY")
784
784
785 def mktempcopy(name, emptyok=False, createmode=None):
785 def mktempcopy(name, emptyok=False, createmode=None):
786 """Create a temporary file with the same contents from name
786 """Create a temporary file with the same contents from name
787
787
788 The permission bits are copied from the original file.
788 The permission bits are copied from the original file.
789
789
790 If the temporary file is going to be truncated immediately, you
790 If the temporary file is going to be truncated immediately, you
791 can use emptyok=True as an optimization.
791 can use emptyok=True as an optimization.
792
792
793 Returns the name of the temporary file.
793 Returns the name of the temporary file.
794 """
794 """
795 d, fn = os.path.split(name)
795 d, fn = os.path.split(name)
796 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
796 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
797 os.close(fd)
797 os.close(fd)
798 # Temporary files are created with mode 0600, which is usually not
798 # Temporary files are created with mode 0600, which is usually not
799 # what we want. If the original file already exists, just copy
799 # what we want. If the original file already exists, just copy
800 # its mode. Otherwise, manually obey umask.
800 # its mode. Otherwise, manually obey umask.
801 try:
801 try:
802 st_mode = os.lstat(name).st_mode & 0777
802 st_mode = os.lstat(name).st_mode & 0777
803 except OSError, inst:
803 except OSError, inst:
804 if inst.errno != errno.ENOENT:
804 if inst.errno != errno.ENOENT:
805 raise
805 raise
806 st_mode = createmode
806 st_mode = createmode
807 if st_mode is None:
807 if st_mode is None:
808 st_mode = ~umask
808 st_mode = ~umask
809 st_mode &= 0666
809 st_mode &= 0666
810 os.chmod(temp, st_mode)
810 os.chmod(temp, st_mode)
811 if emptyok:
811 if emptyok:
812 return temp
812 return temp
813 try:
813 try:
814 try:
814 try:
815 ifp = posixfile(name, "rb")
815 ifp = posixfile(name, "rb")
816 except IOError, inst:
816 except IOError, inst:
817 if inst.errno == errno.ENOENT:
817 if inst.errno == errno.ENOENT:
818 return temp
818 return temp
819 if not getattr(inst, 'filename', None):
819 if not getattr(inst, 'filename', None):
820 inst.filename = name
820 inst.filename = name
821 raise
821 raise
822 ofp = posixfile(temp, "wb")
822 ofp = posixfile(temp, "wb")
823 for chunk in filechunkiter(ifp):
823 for chunk in filechunkiter(ifp):
824 ofp.write(chunk)
824 ofp.write(chunk)
825 ifp.close()
825 ifp.close()
826 ofp.close()
826 ofp.close()
827 except:
827 except:
828 try: os.unlink(temp)
828 try: os.unlink(temp)
829 except: pass
829 except: pass
830 raise
830 raise
831 return temp
831 return temp
832
832
833 class atomictempfile(object):
833 class atomictempfile(object):
834 """file-like object that atomically updates a file
834 """file-like object that atomically updates a file
835
835
836 All writes will be redirected to a temporary copy of the original
836 All writes will be redirected to a temporary copy of the original
837 file. When rename is called, the copy is renamed to the original
837 file. When rename is called, the copy is renamed to the original
838 name, making the changes visible.
838 name, making the changes visible.
839 """
839 """
840 def __init__(self, name, mode='w+b', createmode=None):
840 def __init__(self, name, mode='w+b', createmode=None):
841 self.__name = name
841 self.__name = name
842 self._fp = None
842 self._fp = None
843 self.temp = mktempcopy(name, emptyok=('w' in mode),
843 self.temp = mktempcopy(name, emptyok=('w' in mode),
844 createmode=createmode)
844 createmode=createmode)
845 self._fp = posixfile(self.temp, mode)
845 self._fp = posixfile(self.temp, mode)
846
846
847 def __getattr__(self, name):
847 def __getattr__(self, name):
848 return getattr(self._fp, name)
848 return getattr(self._fp, name)
849
849
850 def rename(self):
850 def rename(self):
851 if not self._fp.closed:
851 if not self._fp.closed:
852 self._fp.close()
852 self._fp.close()
853 rename(self.temp, localpath(self.__name))
853 rename(self.temp, localpath(self.__name))
854
854
855 def close(self):
855 def close(self):
856 if not self._fp:
856 if not self._fp:
857 return
857 return
858 if not self._fp.closed:
858 if not self._fp.closed:
859 try:
859 try:
860 os.unlink(self.temp)
860 os.unlink(self.temp)
861 except: pass
861 except: pass
862 self._fp.close()
862 self._fp.close()
863
863
864 def __del__(self):
864 def __del__(self):
865 self.close()
865 self.close()
866
866
867 def makedirs(name, mode=None):
867 def makedirs(name, mode=None):
868 """recursive directory creation with parent mode inheritance"""
868 """recursive directory creation with parent mode inheritance"""
869 parent = os.path.abspath(os.path.dirname(name))
869 parent = os.path.abspath(os.path.dirname(name))
870 try:
870 try:
871 os.mkdir(name)
871 os.mkdir(name)
872 if mode is not None:
872 if mode is not None:
873 os.chmod(name, mode)
873 os.chmod(name, mode)
874 return
874 return
875 except OSError, err:
875 except OSError, err:
876 if err.errno == errno.EEXIST:
876 if err.errno == errno.EEXIST:
877 return
877 return
878 if not name or parent == name or err.errno != errno.ENOENT:
878 if not name or parent == name or err.errno != errno.ENOENT:
879 raise
879 raise
880 makedirs(parent, mode)
880 makedirs(parent, mode)
881 makedirs(name, mode)
881 makedirs(name, mode)
882
882
883 class opener(object):
883 class opener(object):
884 """Open files relative to a base directory
884 """Open files relative to a base directory
885
885
886 This class is used to hide the details of COW semantics and
886 This class is used to hide the details of COW semantics and
887 remote file access from higher level code.
887 remote file access from higher level code.
888 """
888 """
889 def __init__(self, base, audit=True):
889 def __init__(self, base, audit=True):
890 self.base = base
890 self.base = base
891 if audit:
891 if audit:
892 self.auditor = path_auditor(base)
892 self.auditor = path_auditor(base)
893 else:
893 else:
894 self.auditor = always
894 self.auditor = always
895 self.createmode = None
895 self.createmode = None
896 self._trustnlink = None
896 self._trustnlink = None
897
897
898 @propertycache
898 @propertycache
899 def _can_symlink(self):
899 def _can_symlink(self):
900 return checklink(self.base)
900 return checklink(self.base)
901
901
902 def _fixfilemode(self, name):
902 def _fixfilemode(self, name):
903 if self.createmode is None:
903 if self.createmode is None:
904 return
904 return
905 os.chmod(name, self.createmode & 0666)
905 os.chmod(name, self.createmode & 0666)
906
906
907 def __call__(self, path, mode="r", text=False, atomictemp=False):
907 def __call__(self, path, mode="r", text=False, atomictemp=False):
908 self.auditor(path)
908 self.auditor(path)
909 f = os.path.join(self.base, path)
909 f = os.path.join(self.base, path)
910
910
911 if not text and "b" not in mode:
911 if not text and "b" not in mode:
912 mode += "b" # for that other OS
912 mode += "b" # for that other OS
913
913
914 nlink = -1
914 nlink = -1
915 dirname, basename = os.path.split(f)
915 dirname, basename = os.path.split(f)
916 # If basename is empty, then the path is malformed because it points
916 # If basename is empty, then the path is malformed because it points
917 # to a directory. Let the posixfile() call below raise IOError.
917 # to a directory. Let the posixfile() call below raise IOError.
918 if basename and mode not in ('r', 'rb'):
918 if basename and mode not in ('r', 'rb'):
919 if atomictemp:
919 if atomictemp:
920 if not os.path.isdir(dirname):
920 if not os.path.isdir(dirname):
921 makedirs(dirname, self.createmode)
921 makedirs(dirname, self.createmode)
922 return atomictempfile(f, mode, self.createmode)
922 return atomictempfile(f, mode, self.createmode)
923 try:
923 try:
924 if 'w' in mode:
924 if 'w' in mode:
925 unlink(f)
925 unlink(f)
926 nlink = 0
926 nlink = 0
927 else:
927 else:
928 # nlinks() may behave differently for files on Windows
928 # nlinks() may behave differently for files on Windows
929 # shares if the file is open.
929 # shares if the file is open.
930 fd = posixfile(f)
930 fd = posixfile(f)
931 nlink = nlinks(f)
931 nlink = nlinks(f)
932 if nlink < 1:
932 if nlink < 1:
933 nlink = 2 # force mktempcopy (issue1922)
933 nlink = 2 # force mktempcopy (issue1922)
934 fd.close()
934 fd.close()
935 except (OSError, IOError), e:
935 except (OSError, IOError), e:
936 if e.errno != errno.ENOENT:
936 if e.errno != errno.ENOENT:
937 raise
937 raise
938 nlink = 0
938 nlink = 0
939 if not os.path.isdir(dirname):
939 if not os.path.isdir(dirname):
940 makedirs(dirname, self.createmode)
940 makedirs(dirname, self.createmode)
941 if nlink > 0:
941 if nlink > 0:
942 if self._trustnlink is None:
942 if self._trustnlink is None:
943 self._trustnlink = nlink > 1 or checknlink(f)
943 self._trustnlink = nlink > 1 or checknlink(f)
944 if nlink > 1 or not self._trustnlink:
944 if nlink > 1 or not self._trustnlink:
945 rename(mktempcopy(f), f)
945 rename(mktempcopy(f), f)
946 fp = posixfile(f, mode)
946 fp = posixfile(f, mode)
947 if nlink == 0:
947 if nlink == 0:
948 self._fixfilemode(f)
948 self._fixfilemode(f)
949 return fp
949 return fp
950
950
951 def symlink(self, src, dst):
951 def symlink(self, src, dst):
952 self.auditor(dst)
952 self.auditor(dst)
953 linkname = os.path.join(self.base, dst)
953 linkname = os.path.join(self.base, dst)
954 try:
954 try:
955 os.unlink(linkname)
955 os.unlink(linkname)
956 except OSError:
956 except OSError:
957 pass
957 pass
958
958
959 dirname = os.path.dirname(linkname)
959 dirname = os.path.dirname(linkname)
960 if not os.path.exists(dirname):
960 if not os.path.exists(dirname):
961 makedirs(dirname, self.createmode)
961 makedirs(dirname, self.createmode)
962
962
963 if self._can_symlink:
963 if self._can_symlink:
964 try:
964 try:
965 os.symlink(src, linkname)
965 os.symlink(src, linkname)
966 except OSError, err:
966 except OSError, err:
967 raise OSError(err.errno, _('could not symlink to %r: %s') %
967 raise OSError(err.errno, _('could not symlink to %r: %s') %
968 (src, err.strerror), linkname)
968 (src, err.strerror), linkname)
969 else:
969 else:
970 f = self(dst, "w")
970 f = self(dst, "w")
971 f.write(src)
971 f.write(src)
972 f.close()
972 f.close()
973 self._fixfilemode(dst)
973 self._fixfilemode(dst)
974
974
975 class chunkbuffer(object):
975 class chunkbuffer(object):
976 """Allow arbitrary sized chunks of data to be efficiently read from an
976 """Allow arbitrary sized chunks of data to be efficiently read from an
977 iterator over chunks of arbitrary size."""
977 iterator over chunks of arbitrary size."""
978
978
979 def __init__(self, in_iter):
979 def __init__(self, in_iter):
980 """in_iter is the iterator that's iterating over the input chunks.
980 """in_iter is the iterator that's iterating over the input chunks.
981 targetsize is how big a buffer to try to maintain."""
981 targetsize is how big a buffer to try to maintain."""
982 def splitbig(chunks):
982 def splitbig(chunks):
983 for chunk in chunks:
983 for chunk in chunks:
984 if len(chunk) > 2**20:
984 if len(chunk) > 2**20:
985 pos = 0
985 pos = 0
986 while pos < len(chunk):
986 while pos < len(chunk):
987 end = pos + 2 ** 18
987 end = pos + 2 ** 18
988 yield chunk[pos:end]
988 yield chunk[pos:end]
989 pos = end
989 pos = end
990 else:
990 else:
991 yield chunk
991 yield chunk
992 self.iter = splitbig(in_iter)
992 self.iter = splitbig(in_iter)
993 self._queue = []
993 self._queue = []
994
994
995 def read(self, l):
995 def read(self, l):
996 """Read L bytes of data from the iterator of chunks of data.
996 """Read L bytes of data from the iterator of chunks of data.
997 Returns less than L bytes if the iterator runs dry."""
997 Returns less than L bytes if the iterator runs dry."""
998 left = l
998 left = l
999 buf = ''
999 buf = ''
1000 queue = self._queue
1000 queue = self._queue
1001 while left > 0:
1001 while left > 0:
1002 # refill the queue
1002 # refill the queue
1003 if not queue:
1003 if not queue:
1004 target = 2**18
1004 target = 2**18
1005 for chunk in self.iter:
1005 for chunk in self.iter:
1006 queue.append(chunk)
1006 queue.append(chunk)
1007 target -= len(chunk)
1007 target -= len(chunk)
1008 if target <= 0:
1008 if target <= 0:
1009 break
1009 break
1010 if not queue:
1010 if not queue:
1011 break
1011 break
1012
1012
1013 chunk = queue.pop(0)
1013 chunk = queue.pop(0)
1014 left -= len(chunk)
1014 left -= len(chunk)
1015 if left < 0:
1015 if left < 0:
1016 queue.insert(0, chunk[left:])
1016 queue.insert(0, chunk[left:])
1017 buf += chunk[:left]
1017 buf += chunk[:left]
1018 else:
1018 else:
1019 buf += chunk
1019 buf += chunk
1020
1020
1021 return buf
1021 return buf
1022
1022
1023 def filechunkiter(f, size=65536, limit=None):
1023 def filechunkiter(f, size=65536, limit=None):
1024 """Create a generator that produces the data in the file size
1024 """Create a generator that produces the data in the file size
1025 (default 65536) bytes at a time, up to optional limit (default is
1025 (default 65536) bytes at a time, up to optional limit (default is
1026 to read all data). Chunks may be less than size bytes if the
1026 to read all data). Chunks may be less than size bytes if the
1027 chunk is the last chunk in the file, or the file is a socket or
1027 chunk is the last chunk in the file, or the file is a socket or
1028 some other type of file that sometimes reads less data than is
1028 some other type of file that sometimes reads less data than is
1029 requested."""
1029 requested."""
1030 assert size >= 0
1030 assert size >= 0
1031 assert limit is None or limit >= 0
1031 assert limit is None or limit >= 0
1032 while True:
1032 while True:
1033 if limit is None:
1033 if limit is None:
1034 nbytes = size
1034 nbytes = size
1035 else:
1035 else:
1036 nbytes = min(limit, size)
1036 nbytes = min(limit, size)
1037 s = nbytes and f.read(nbytes)
1037 s = nbytes and f.read(nbytes)
1038 if not s:
1038 if not s:
1039 break
1039 break
1040 if limit:
1040 if limit:
1041 limit -= len(s)
1041 limit -= len(s)
1042 yield s
1042 yield s
1043
1043
1044 def makedate():
1044 def makedate():
1045 lt = time.localtime()
1045 lt = time.localtime()
1046 if lt[8] == 1 and time.daylight:
1046 if lt[8] == 1 and time.daylight:
1047 tz = time.altzone
1047 tz = time.altzone
1048 else:
1048 else:
1049 tz = time.timezone
1049 tz = time.timezone
1050 t = time.mktime(lt)
1050 t = time.mktime(lt)
1051 if t < 0:
1051 if t < 0:
1052 hint = _("check your clock")
1052 hint = _("check your clock")
1053 raise Abort(_("negative timestamp: %d") % t, hint=hint)
1053 raise Abort(_("negative timestamp: %d") % t, hint=hint)
1054 return t, tz
1054 return t, tz
1055
1055
1056 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1056 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1057 """represent a (unixtime, offset) tuple as a localized time.
1057 """represent a (unixtime, offset) tuple as a localized time.
1058 unixtime is seconds since the epoch, and offset is the time zone's
1058 unixtime is seconds since the epoch, and offset is the time zone's
1059 number of seconds away from UTC. if timezone is false, do not
1059 number of seconds away from UTC. if timezone is false, do not
1060 append time zone to string."""
1060 append time zone to string."""
1061 t, tz = date or makedate()
1061 t, tz = date or makedate()
1062 if t < 0:
1062 if t < 0:
1063 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1063 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1064 tz = 0
1064 tz = 0
1065 if "%1" in format or "%2" in format:
1065 if "%1" in format or "%2" in format:
1066 sign = (tz > 0) and "-" or "+"
1066 sign = (tz > 0) and "-" or "+"
1067 minutes = abs(tz) // 60
1067 minutes = abs(tz) // 60
1068 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1068 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1069 format = format.replace("%2", "%02d" % (minutes % 60))
1069 format = format.replace("%2", "%02d" % (minutes % 60))
1070 s = time.strftime(format, time.gmtime(float(t) - tz))
1070 s = time.strftime(format, time.gmtime(float(t) - tz))
1071 return s
1071 return s
1072
1072
1073 def shortdate(date=None):
1073 def shortdate(date=None):
1074 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1074 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1075 return datestr(date, format='%Y-%m-%d')
1075 return datestr(date, format='%Y-%m-%d')
1076
1076
1077 def strdate(string, format, defaults=[]):
1077 def strdate(string, format, defaults=[]):
1078 """parse a localized time string and return a (unixtime, offset) tuple.
1078 """parse a localized time string and return a (unixtime, offset) tuple.
1079 if the string cannot be parsed, ValueError is raised."""
1079 if the string cannot be parsed, ValueError is raised."""
1080 def timezone(string):
1080 def timezone(string):
1081 tz = string.split()[-1]
1081 tz = string.split()[-1]
1082 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1082 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1083 sign = (tz[0] == "+") and 1 or -1
1083 sign = (tz[0] == "+") and 1 or -1
1084 hours = int(tz[1:3])
1084 hours = int(tz[1:3])
1085 minutes = int(tz[3:5])
1085 minutes = int(tz[3:5])
1086 return -sign * (hours * 60 + minutes) * 60
1086 return -sign * (hours * 60 + minutes) * 60
1087 if tz == "GMT" or tz == "UTC":
1087 if tz == "GMT" or tz == "UTC":
1088 return 0
1088 return 0
1089 return None
1089 return None
1090
1090
1091 # NOTE: unixtime = localunixtime + offset
1091 # NOTE: unixtime = localunixtime + offset
1092 offset, date = timezone(string), string
1092 offset, date = timezone(string), string
1093 if offset is not None:
1093 if offset is not None:
1094 date = " ".join(string.split()[:-1])
1094 date = " ".join(string.split()[:-1])
1095
1095
1096 # add missing elements from defaults
1096 # add missing elements from defaults
1097 usenow = False # default to using biased defaults
1097 usenow = False # default to using biased defaults
1098 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1098 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1099 found = [True for p in part if ("%"+p) in format]
1099 found = [True for p in part if ("%"+p) in format]
1100 if not found:
1100 if not found:
1101 date += "@" + defaults[part][usenow]
1101 date += "@" + defaults[part][usenow]
1102 format += "@%" + part[0]
1102 format += "@%" + part[0]
1103 else:
1103 else:
1104 # We've found a specific time element, less specific time
1104 # We've found a specific time element, less specific time
1105 # elements are relative to today
1105 # elements are relative to today
1106 usenow = True
1106 usenow = True
1107
1107
1108 timetuple = time.strptime(date, format)
1108 timetuple = time.strptime(date, format)
1109 localunixtime = int(calendar.timegm(timetuple))
1109 localunixtime = int(calendar.timegm(timetuple))
1110 if offset is None:
1110 if offset is None:
1111 # local timezone
1111 # local timezone
1112 unixtime = int(time.mktime(timetuple))
1112 unixtime = int(time.mktime(timetuple))
1113 offset = unixtime - localunixtime
1113 offset = unixtime - localunixtime
1114 else:
1114 else:
1115 unixtime = localunixtime + offset
1115 unixtime = localunixtime + offset
1116 return unixtime, offset
1116 return unixtime, offset
1117
1117
1118 def parsedate(date, formats=None, bias={}):
1118 def parsedate(date, formats=None, bias={}):
1119 """parse a localized date/time and return a (unixtime, offset) tuple.
1119 """parse a localized date/time and return a (unixtime, offset) tuple.
1120
1120
1121 The date may be a "unixtime offset" string or in one of the specified
1121 The date may be a "unixtime offset" string or in one of the specified
1122 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1122 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1123 """
1123 """
1124 if not date:
1124 if not date:
1125 return 0, 0
1125 return 0, 0
1126 if isinstance(date, tuple) and len(date) == 2:
1126 if isinstance(date, tuple) and len(date) == 2:
1127 return date
1127 return date
1128 if not formats:
1128 if not formats:
1129 formats = defaultdateformats
1129 formats = defaultdateformats
1130 date = date.strip()
1130 date = date.strip()
1131 try:
1131 try:
1132 when, offset = map(int, date.split(' '))
1132 when, offset = map(int, date.split(' '))
1133 except ValueError:
1133 except ValueError:
1134 # fill out defaults
1134 # fill out defaults
1135 now = makedate()
1135 now = makedate()
1136 defaults = {}
1136 defaults = {}
1137 nowmap = {}
1137 nowmap = {}
1138 for part in ("d", "mb", "yY", "HI", "M", "S"):
1138 for part in ("d", "mb", "yY", "HI", "M", "S"):
1139 # this piece is for rounding the specific end of unknowns
1139 # this piece is for rounding the specific end of unknowns
1140 b = bias.get(part)
1140 b = bias.get(part)
1141 if b is None:
1141 if b is None:
1142 if part[0] in "HMS":
1142 if part[0] in "HMS":
1143 b = "00"
1143 b = "00"
1144 else:
1144 else:
1145 b = "0"
1145 b = "0"
1146
1146
1147 # this piece is for matching the generic end to today's date
1147 # this piece is for matching the generic end to today's date
1148 n = datestr(now, "%" + part[0])
1148 n = datestr(now, "%" + part[0])
1149
1149
1150 defaults[part] = (b, n)
1150 defaults[part] = (b, n)
1151
1151
1152 for format in formats:
1152 for format in formats:
1153 try:
1153 try:
1154 when, offset = strdate(date, format, defaults)
1154 when, offset = strdate(date, format, defaults)
1155 except (ValueError, OverflowError):
1155 except (ValueError, OverflowError):
1156 pass
1156 pass
1157 else:
1157 else:
1158 break
1158 break
1159 else:
1159 else:
1160 raise Abort(_('invalid date: %r') % date)
1160 raise Abort(_('invalid date: %r') % date)
1161 # validate explicit (probably user-specified) date and
1161 # validate explicit (probably user-specified) date and
1162 # time zone offset. values must fit in signed 32 bits for
1162 # time zone offset. values must fit in signed 32 bits for
1163 # current 32-bit linux runtimes. timezones go from UTC-12
1163 # current 32-bit linux runtimes. timezones go from UTC-12
1164 # to UTC+14
1164 # to UTC+14
1165 if abs(when) > 0x7fffffff:
1165 if abs(when) > 0x7fffffff:
1166 raise Abort(_('date exceeds 32 bits: %d') % when)
1166 raise Abort(_('date exceeds 32 bits: %d') % when)
1167 if when < 0:
1167 if when < 0:
1168 raise Abort(_('negative date value: %d') % when)
1168 raise Abort(_('negative date value: %d') % when)
1169 if offset < -50400 or offset > 43200:
1169 if offset < -50400 or offset > 43200:
1170 raise Abort(_('impossible time zone offset: %d') % offset)
1170 raise Abort(_('impossible time zone offset: %d') % offset)
1171 return when, offset
1171 return when, offset
1172
1172
1173 def matchdate(date):
1173 def matchdate(date):
1174 """Return a function that matches a given date match specifier
1174 """Return a function that matches a given date match specifier
1175
1175
1176 Formats include:
1176 Formats include:
1177
1177
1178 '{date}' match a given date to the accuracy provided
1178 '{date}' match a given date to the accuracy provided
1179
1179
1180 '<{date}' on or before a given date
1180 '<{date}' on or before a given date
1181
1181
1182 '>{date}' on or after a given date
1182 '>{date}' on or after a given date
1183
1183
1184 >>> p1 = parsedate("10:29:59")
1184 >>> p1 = parsedate("10:29:59")
1185 >>> p2 = parsedate("10:30:00")
1185 >>> p2 = parsedate("10:30:00")
1186 >>> p3 = parsedate("10:30:59")
1186 >>> p3 = parsedate("10:30:59")
1187 >>> p4 = parsedate("10:31:00")
1187 >>> p4 = parsedate("10:31:00")
1188 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1188 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1189 >>> f = matchdate("10:30")
1189 >>> f = matchdate("10:30")
1190 >>> f(p1[0])
1190 >>> f(p1[0])
1191 False
1191 False
1192 >>> f(p2[0])
1192 >>> f(p2[0])
1193 True
1193 True
1194 >>> f(p3[0])
1194 >>> f(p3[0])
1195 True
1195 True
1196 >>> f(p4[0])
1196 >>> f(p4[0])
1197 False
1197 False
1198 >>> f(p5[0])
1198 >>> f(p5[0])
1199 False
1199 False
1200 """
1200 """
1201
1201
1202 def lower(date):
1202 def lower(date):
1203 d = dict(mb="1", d="1")
1203 d = dict(mb="1", d="1")
1204 return parsedate(date, extendeddateformats, d)[0]
1204 return parsedate(date, extendeddateformats, d)[0]
1205
1205
1206 def upper(date):
1206 def upper(date):
1207 d = dict(mb="12", HI="23", M="59", S="59")
1207 d = dict(mb="12", HI="23", M="59", S="59")
1208 for days in ("31", "30", "29"):
1208 for days in ("31", "30", "29"):
1209 try:
1209 try:
1210 d["d"] = days
1210 d["d"] = days
1211 return parsedate(date, extendeddateformats, d)[0]
1211 return parsedate(date, extendeddateformats, d)[0]
1212 except:
1212 except:
1213 pass
1213 pass
1214 d["d"] = "28"
1214 d["d"] = "28"
1215 return parsedate(date, extendeddateformats, d)[0]
1215 return parsedate(date, extendeddateformats, d)[0]
1216
1216
1217 date = date.strip()
1217 date = date.strip()
1218 if date[0] == "<":
1218
1219 if not date:
1220 raise Abort(_("dates cannot consist entirely of whitespace"))
1221 elif date[0] == "<":
1219 when = upper(date[1:])
1222 when = upper(date[1:])
1220 return lambda x: x <= when
1223 return lambda x: x <= when
1221 elif date[0] == ">":
1224 elif date[0] == ">":
1222 when = lower(date[1:])
1225 when = lower(date[1:])
1223 return lambda x: x >= when
1226 return lambda x: x >= when
1224 elif date[0] == "-":
1227 elif date[0] == "-":
1225 try:
1228 try:
1226 days = int(date[1:])
1229 days = int(date[1:])
1227 except ValueError:
1230 except ValueError:
1228 raise Abort(_("invalid day spec: %s") % date[1:])
1231 raise Abort(_("invalid day spec: %s") % date[1:])
1229 when = makedate()[0] - days * 3600 * 24
1232 when = makedate()[0] - days * 3600 * 24
1230 return lambda x: x >= when
1233 return lambda x: x >= when
1231 elif " to " in date:
1234 elif " to " in date:
1232 a, b = date.split(" to ")
1235 a, b = date.split(" to ")
1233 start, stop = lower(a), upper(b)
1236 start, stop = lower(a), upper(b)
1234 return lambda x: x >= start and x <= stop
1237 return lambda x: x >= start and x <= stop
1235 else:
1238 else:
1236 start, stop = lower(date), upper(date)
1239 start, stop = lower(date), upper(date)
1237 return lambda x: x >= start and x <= stop
1240 return lambda x: x >= start and x <= stop
1238
1241
1239 def shortuser(user):
1242 def shortuser(user):
1240 """Return a short representation of a user name or email address."""
1243 """Return a short representation of a user name or email address."""
1241 f = user.find('@')
1244 f = user.find('@')
1242 if f >= 0:
1245 if f >= 0:
1243 user = user[:f]
1246 user = user[:f]
1244 f = user.find('<')
1247 f = user.find('<')
1245 if f >= 0:
1248 if f >= 0:
1246 user = user[f + 1:]
1249 user = user[f + 1:]
1247 f = user.find(' ')
1250 f = user.find(' ')
1248 if f >= 0:
1251 if f >= 0:
1249 user = user[:f]
1252 user = user[:f]
1250 f = user.find('.')
1253 f = user.find('.')
1251 if f >= 0:
1254 if f >= 0:
1252 user = user[:f]
1255 user = user[:f]
1253 return user
1256 return user
1254
1257
1255 def email(author):
1258 def email(author):
1256 '''get email of author.'''
1259 '''get email of author.'''
1257 r = author.find('>')
1260 r = author.find('>')
1258 if r == -1:
1261 if r == -1:
1259 r = None
1262 r = None
1260 return author[author.find('<') + 1:r]
1263 return author[author.find('<') + 1:r]
1261
1264
1262 def _ellipsis(text, maxlength):
1265 def _ellipsis(text, maxlength):
1263 if len(text) <= maxlength:
1266 if len(text) <= maxlength:
1264 return text, False
1267 return text, False
1265 else:
1268 else:
1266 return "%s..." % (text[:maxlength - 3]), True
1269 return "%s..." % (text[:maxlength - 3]), True
1267
1270
1268 def ellipsis(text, maxlength=400):
1271 def ellipsis(text, maxlength=400):
1269 """Trim string to at most maxlength (default: 400) characters."""
1272 """Trim string to at most maxlength (default: 400) characters."""
1270 try:
1273 try:
1271 # use unicode not to split at intermediate multi-byte sequence
1274 # use unicode not to split at intermediate multi-byte sequence
1272 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1275 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1273 maxlength)
1276 maxlength)
1274 if not truncated:
1277 if not truncated:
1275 return text
1278 return text
1276 return utext.encode(encoding.encoding)
1279 return utext.encode(encoding.encoding)
1277 except (UnicodeDecodeError, UnicodeEncodeError):
1280 except (UnicodeDecodeError, UnicodeEncodeError):
1278 return _ellipsis(text, maxlength)[0]
1281 return _ellipsis(text, maxlength)[0]
1279
1282
1280 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1283 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1281 '''yield every hg repository under path, recursively.'''
1284 '''yield every hg repository under path, recursively.'''
1282 def errhandler(err):
1285 def errhandler(err):
1283 if err.filename == path:
1286 if err.filename == path:
1284 raise err
1287 raise err
1285 if followsym and hasattr(os.path, 'samestat'):
1288 if followsym and hasattr(os.path, 'samestat'):
1286 def _add_dir_if_not_there(dirlst, dirname):
1289 def _add_dir_if_not_there(dirlst, dirname):
1287 match = False
1290 match = False
1288 samestat = os.path.samestat
1291 samestat = os.path.samestat
1289 dirstat = os.stat(dirname)
1292 dirstat = os.stat(dirname)
1290 for lstdirstat in dirlst:
1293 for lstdirstat in dirlst:
1291 if samestat(dirstat, lstdirstat):
1294 if samestat(dirstat, lstdirstat):
1292 match = True
1295 match = True
1293 break
1296 break
1294 if not match:
1297 if not match:
1295 dirlst.append(dirstat)
1298 dirlst.append(dirstat)
1296 return not match
1299 return not match
1297 else:
1300 else:
1298 followsym = False
1301 followsym = False
1299
1302
1300 if (seen_dirs is None) and followsym:
1303 if (seen_dirs is None) and followsym:
1301 seen_dirs = []
1304 seen_dirs = []
1302 _add_dir_if_not_there(seen_dirs, path)
1305 _add_dir_if_not_there(seen_dirs, path)
1303 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1306 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1304 dirs.sort()
1307 dirs.sort()
1305 if '.hg' in dirs:
1308 if '.hg' in dirs:
1306 yield root # found a repository
1309 yield root # found a repository
1307 qroot = os.path.join(root, '.hg', 'patches')
1310 qroot = os.path.join(root, '.hg', 'patches')
1308 if os.path.isdir(os.path.join(qroot, '.hg')):
1311 if os.path.isdir(os.path.join(qroot, '.hg')):
1309 yield qroot # we have a patch queue repo here
1312 yield qroot # we have a patch queue repo here
1310 if recurse:
1313 if recurse:
1311 # avoid recursing inside the .hg directory
1314 # avoid recursing inside the .hg directory
1312 dirs.remove('.hg')
1315 dirs.remove('.hg')
1313 else:
1316 else:
1314 dirs[:] = [] # don't descend further
1317 dirs[:] = [] # don't descend further
1315 elif followsym:
1318 elif followsym:
1316 newdirs = []
1319 newdirs = []
1317 for d in dirs:
1320 for d in dirs:
1318 fname = os.path.join(root, d)
1321 fname = os.path.join(root, d)
1319 if _add_dir_if_not_there(seen_dirs, fname):
1322 if _add_dir_if_not_there(seen_dirs, fname):
1320 if os.path.islink(fname):
1323 if os.path.islink(fname):
1321 for hgname in walkrepos(fname, True, seen_dirs):
1324 for hgname in walkrepos(fname, True, seen_dirs):
1322 yield hgname
1325 yield hgname
1323 else:
1326 else:
1324 newdirs.append(d)
1327 newdirs.append(d)
1325 dirs[:] = newdirs
1328 dirs[:] = newdirs
1326
1329
1327 _rcpath = None
1330 _rcpath = None
1328
1331
1329 def os_rcpath():
1332 def os_rcpath():
1330 '''return default os-specific hgrc search path'''
1333 '''return default os-specific hgrc search path'''
1331 path = system_rcpath()
1334 path = system_rcpath()
1332 path.extend(user_rcpath())
1335 path.extend(user_rcpath())
1333 path = [os.path.normpath(f) for f in path]
1336 path = [os.path.normpath(f) for f in path]
1334 return path
1337 return path
1335
1338
1336 def rcpath():
1339 def rcpath():
1337 '''return hgrc search path. if env var HGRCPATH is set, use it.
1340 '''return hgrc search path. if env var HGRCPATH is set, use it.
1338 for each item in path, if directory, use files ending in .rc,
1341 for each item in path, if directory, use files ending in .rc,
1339 else use item.
1342 else use item.
1340 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1343 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1341 if no HGRCPATH, use default os-specific path.'''
1344 if no HGRCPATH, use default os-specific path.'''
1342 global _rcpath
1345 global _rcpath
1343 if _rcpath is None:
1346 if _rcpath is None:
1344 if 'HGRCPATH' in os.environ:
1347 if 'HGRCPATH' in os.environ:
1345 _rcpath = []
1348 _rcpath = []
1346 for p in os.environ['HGRCPATH'].split(os.pathsep):
1349 for p in os.environ['HGRCPATH'].split(os.pathsep):
1347 if not p:
1350 if not p:
1348 continue
1351 continue
1349 p = expandpath(p)
1352 p = expandpath(p)
1350 if os.path.isdir(p):
1353 if os.path.isdir(p):
1351 for f, kind in osutil.listdir(p):
1354 for f, kind in osutil.listdir(p):
1352 if f.endswith('.rc'):
1355 if f.endswith('.rc'):
1353 _rcpath.append(os.path.join(p, f))
1356 _rcpath.append(os.path.join(p, f))
1354 else:
1357 else:
1355 _rcpath.append(p)
1358 _rcpath.append(p)
1356 else:
1359 else:
1357 _rcpath = os_rcpath()
1360 _rcpath = os_rcpath()
1358 return _rcpath
1361 return _rcpath
1359
1362
1360 def bytecount(nbytes):
1363 def bytecount(nbytes):
1361 '''return byte count formatted as readable string, with units'''
1364 '''return byte count formatted as readable string, with units'''
1362
1365
1363 units = (
1366 units = (
1364 (100, 1 << 30, _('%.0f GB')),
1367 (100, 1 << 30, _('%.0f GB')),
1365 (10, 1 << 30, _('%.1f GB')),
1368 (10, 1 << 30, _('%.1f GB')),
1366 (1, 1 << 30, _('%.2f GB')),
1369 (1, 1 << 30, _('%.2f GB')),
1367 (100, 1 << 20, _('%.0f MB')),
1370 (100, 1 << 20, _('%.0f MB')),
1368 (10, 1 << 20, _('%.1f MB')),
1371 (10, 1 << 20, _('%.1f MB')),
1369 (1, 1 << 20, _('%.2f MB')),
1372 (1, 1 << 20, _('%.2f MB')),
1370 (100, 1 << 10, _('%.0f KB')),
1373 (100, 1 << 10, _('%.0f KB')),
1371 (10, 1 << 10, _('%.1f KB')),
1374 (10, 1 << 10, _('%.1f KB')),
1372 (1, 1 << 10, _('%.2f KB')),
1375 (1, 1 << 10, _('%.2f KB')),
1373 (1, 1, _('%.0f bytes')),
1376 (1, 1, _('%.0f bytes')),
1374 )
1377 )
1375
1378
1376 for multiplier, divisor, format in units:
1379 for multiplier, divisor, format in units:
1377 if nbytes >= divisor * multiplier:
1380 if nbytes >= divisor * multiplier:
1378 return format % (nbytes / float(divisor))
1381 return format % (nbytes / float(divisor))
1379 return units[-1][2] % nbytes
1382 return units[-1][2] % nbytes
1380
1383
1381 def drop_scheme(scheme, path):
1384 def drop_scheme(scheme, path):
1382 sc = scheme + ':'
1385 sc = scheme + ':'
1383 if path.startswith(sc):
1386 if path.startswith(sc):
1384 path = path[len(sc):]
1387 path = path[len(sc):]
1385 if path.startswith('//'):
1388 if path.startswith('//'):
1386 if scheme == 'file':
1389 if scheme == 'file':
1387 i = path.find('/', 2)
1390 i = path.find('/', 2)
1388 if i == -1:
1391 if i == -1:
1389 return ''
1392 return ''
1390 # On Windows, absolute paths are rooted at the current drive
1393 # On Windows, absolute paths are rooted at the current drive
1391 # root. On POSIX they are rooted at the file system root.
1394 # root. On POSIX they are rooted at the file system root.
1392 if os.name == 'nt':
1395 if os.name == 'nt':
1393 droot = os.path.splitdrive(os.getcwd())[0] + '/'
1396 droot = os.path.splitdrive(os.getcwd())[0] + '/'
1394 path = os.path.join(droot, path[i + 1:])
1397 path = os.path.join(droot, path[i + 1:])
1395 else:
1398 else:
1396 path = path[i:]
1399 path = path[i:]
1397 else:
1400 else:
1398 path = path[2:]
1401 path = path[2:]
1399 return path
1402 return path
1400
1403
1401 def uirepr(s):
1404 def uirepr(s):
1402 # Avoid double backslash in Windows path repr()
1405 # Avoid double backslash in Windows path repr()
1403 return repr(s).replace('\\\\', '\\')
1406 return repr(s).replace('\\\\', '\\')
1404
1407
1405 # delay import of textwrap
1408 # delay import of textwrap
1406 def MBTextWrapper(**kwargs):
1409 def MBTextWrapper(**kwargs):
1407 class tw(textwrap.TextWrapper):
1410 class tw(textwrap.TextWrapper):
1408 """
1411 """
1409 Extend TextWrapper for double-width characters.
1412 Extend TextWrapper for double-width characters.
1410
1413
1411 Some Asian characters use two terminal columns instead of one.
1414 Some Asian characters use two terminal columns instead of one.
1412 A good example of this behavior can be seen with u'\u65e5\u672c',
1415 A good example of this behavior can be seen with u'\u65e5\u672c',
1413 the two Japanese characters for "Japan":
1416 the two Japanese characters for "Japan":
1414 len() returns 2, but when printed to a terminal, they eat 4 columns.
1417 len() returns 2, but when printed to a terminal, they eat 4 columns.
1415
1418
1416 (Note that this has nothing to do whatsoever with unicode
1419 (Note that this has nothing to do whatsoever with unicode
1417 representation, or encoding of the underlying string)
1420 representation, or encoding of the underlying string)
1418 """
1421 """
1419 def __init__(self, **kwargs):
1422 def __init__(self, **kwargs):
1420 textwrap.TextWrapper.__init__(self, **kwargs)
1423 textwrap.TextWrapper.__init__(self, **kwargs)
1421
1424
1422 def _cutdown(self, str, space_left):
1425 def _cutdown(self, str, space_left):
1423 l = 0
1426 l = 0
1424 ucstr = unicode(str, encoding.encoding)
1427 ucstr = unicode(str, encoding.encoding)
1425 colwidth = unicodedata.east_asian_width
1428 colwidth = unicodedata.east_asian_width
1426 for i in xrange(len(ucstr)):
1429 for i in xrange(len(ucstr)):
1427 l += colwidth(ucstr[i]) in 'WFA' and 2 or 1
1430 l += colwidth(ucstr[i]) in 'WFA' and 2 or 1
1428 if space_left < l:
1431 if space_left < l:
1429 return (ucstr[:i].encode(encoding.encoding),
1432 return (ucstr[:i].encode(encoding.encoding),
1430 ucstr[i:].encode(encoding.encoding))
1433 ucstr[i:].encode(encoding.encoding))
1431 return str, ''
1434 return str, ''
1432
1435
1433 # overriding of base class
1436 # overriding of base class
1434 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1437 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1435 space_left = max(width - cur_len, 1)
1438 space_left = max(width - cur_len, 1)
1436
1439
1437 if self.break_long_words:
1440 if self.break_long_words:
1438 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1441 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1439 cur_line.append(cut)
1442 cur_line.append(cut)
1440 reversed_chunks[-1] = res
1443 reversed_chunks[-1] = res
1441 elif not cur_line:
1444 elif not cur_line:
1442 cur_line.append(reversed_chunks.pop())
1445 cur_line.append(reversed_chunks.pop())
1443
1446
1444 global MBTextWrapper
1447 global MBTextWrapper
1445 MBTextWrapper = tw
1448 MBTextWrapper = tw
1446 return tw(**kwargs)
1449 return tw(**kwargs)
1447
1450
1448 def wrap(line, width, initindent='', hangindent=''):
1451 def wrap(line, width, initindent='', hangindent=''):
1449 maxindent = max(len(hangindent), len(initindent))
1452 maxindent = max(len(hangindent), len(initindent))
1450 if width <= maxindent:
1453 if width <= maxindent:
1451 # adjust for weird terminal size
1454 # adjust for weird terminal size
1452 width = max(78, maxindent + 1)
1455 width = max(78, maxindent + 1)
1453 wrapper = MBTextWrapper(width=width,
1456 wrapper = MBTextWrapper(width=width,
1454 initial_indent=initindent,
1457 initial_indent=initindent,
1455 subsequent_indent=hangindent)
1458 subsequent_indent=hangindent)
1456 return wrapper.fill(line)
1459 return wrapper.fill(line)
1457
1460
1458 def iterlines(iterator):
1461 def iterlines(iterator):
1459 for chunk in iterator:
1462 for chunk in iterator:
1460 for line in chunk.splitlines():
1463 for line in chunk.splitlines():
1461 yield line
1464 yield line
1462
1465
1463 def expandpath(path):
1466 def expandpath(path):
1464 return os.path.expanduser(os.path.expandvars(path))
1467 return os.path.expanduser(os.path.expandvars(path))
1465
1468
1466 def hgcmd():
1469 def hgcmd():
1467 """Return the command used to execute current hg
1470 """Return the command used to execute current hg
1468
1471
1469 This is different from hgexecutable() because on Windows we want
1472 This is different from hgexecutable() because on Windows we want
1470 to avoid things opening new shell windows like batch files, so we
1473 to avoid things opening new shell windows like batch files, so we
1471 get either the python call or current executable.
1474 get either the python call or current executable.
1472 """
1475 """
1473 if main_is_frozen():
1476 if main_is_frozen():
1474 return [sys.executable]
1477 return [sys.executable]
1475 return gethgcmd()
1478 return gethgcmd()
1476
1479
1477 def rundetached(args, condfn):
1480 def rundetached(args, condfn):
1478 """Execute the argument list in a detached process.
1481 """Execute the argument list in a detached process.
1479
1482
1480 condfn is a callable which is called repeatedly and should return
1483 condfn is a callable which is called repeatedly and should return
1481 True once the child process is known to have started successfully.
1484 True once the child process is known to have started successfully.
1482 At this point, the child process PID is returned. If the child
1485 At this point, the child process PID is returned. If the child
1483 process fails to start or finishes before condfn() evaluates to
1486 process fails to start or finishes before condfn() evaluates to
1484 True, return -1.
1487 True, return -1.
1485 """
1488 """
1486 # Windows case is easier because the child process is either
1489 # Windows case is easier because the child process is either
1487 # successfully starting and validating the condition or exiting
1490 # successfully starting and validating the condition or exiting
1488 # on failure. We just poll on its PID. On Unix, if the child
1491 # on failure. We just poll on its PID. On Unix, if the child
1489 # process fails to start, it will be left in a zombie state until
1492 # process fails to start, it will be left in a zombie state until
1490 # the parent wait on it, which we cannot do since we expect a long
1493 # the parent wait on it, which we cannot do since we expect a long
1491 # running process on success. Instead we listen for SIGCHLD telling
1494 # running process on success. Instead we listen for SIGCHLD telling
1492 # us our child process terminated.
1495 # us our child process terminated.
1493 terminated = set()
1496 terminated = set()
1494 def handler(signum, frame):
1497 def handler(signum, frame):
1495 terminated.add(os.wait())
1498 terminated.add(os.wait())
1496 prevhandler = None
1499 prevhandler = None
1497 if hasattr(signal, 'SIGCHLD'):
1500 if hasattr(signal, 'SIGCHLD'):
1498 prevhandler = signal.signal(signal.SIGCHLD, handler)
1501 prevhandler = signal.signal(signal.SIGCHLD, handler)
1499 try:
1502 try:
1500 pid = spawndetached(args)
1503 pid = spawndetached(args)
1501 while not condfn():
1504 while not condfn():
1502 if ((pid in terminated or not testpid(pid))
1505 if ((pid in terminated or not testpid(pid))
1503 and not condfn()):
1506 and not condfn()):
1504 return -1
1507 return -1
1505 time.sleep(0.1)
1508 time.sleep(0.1)
1506 return pid
1509 return pid
1507 finally:
1510 finally:
1508 if prevhandler is not None:
1511 if prevhandler is not None:
1509 signal.signal(signal.SIGCHLD, prevhandler)
1512 signal.signal(signal.SIGCHLD, prevhandler)
1510
1513
1511 try:
1514 try:
1512 any, all = any, all
1515 any, all = any, all
1513 except NameError:
1516 except NameError:
1514 def any(iterable):
1517 def any(iterable):
1515 for i in iterable:
1518 for i in iterable:
1516 if i:
1519 if i:
1517 return True
1520 return True
1518 return False
1521 return False
1519
1522
1520 def all(iterable):
1523 def all(iterable):
1521 for i in iterable:
1524 for i in iterable:
1522 if not i:
1525 if not i:
1523 return False
1526 return False
1524 return True
1527 return True
1525
1528
1526 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1529 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1527 """Return the result of interpolating items in the mapping into string s.
1530 """Return the result of interpolating items in the mapping into string s.
1528
1531
1529 prefix is a single character string, or a two character string with
1532 prefix is a single character string, or a two character string with
1530 a backslash as the first character if the prefix needs to be escaped in
1533 a backslash as the first character if the prefix needs to be escaped in
1531 a regular expression.
1534 a regular expression.
1532
1535
1533 fn is an optional function that will be applied to the replacement text
1536 fn is an optional function that will be applied to the replacement text
1534 just before replacement.
1537 just before replacement.
1535
1538
1536 escape_prefix is an optional flag that allows using doubled prefix for
1539 escape_prefix is an optional flag that allows using doubled prefix for
1537 its escaping.
1540 its escaping.
1538 """
1541 """
1539 fn = fn or (lambda s: s)
1542 fn = fn or (lambda s: s)
1540 patterns = '|'.join(mapping.keys())
1543 patterns = '|'.join(mapping.keys())
1541 if escape_prefix:
1544 if escape_prefix:
1542 patterns += '|' + prefix
1545 patterns += '|' + prefix
1543 if len(prefix) > 1:
1546 if len(prefix) > 1:
1544 prefix_char = prefix[1:]
1547 prefix_char = prefix[1:]
1545 else:
1548 else:
1546 prefix_char = prefix
1549 prefix_char = prefix
1547 mapping[prefix_char] = prefix_char
1550 mapping[prefix_char] = prefix_char
1548 r = re.compile(r'%s(%s)' % (prefix, patterns))
1551 r = re.compile(r'%s(%s)' % (prefix, patterns))
1549 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1552 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1550
1553
1551 def getport(port):
1554 def getport(port):
1552 """Return the port for a given network service.
1555 """Return the port for a given network service.
1553
1556
1554 If port is an integer, it's returned as is. If it's a string, it's
1557 If port is an integer, it's returned as is. If it's a string, it's
1555 looked up using socket.getservbyname(). If there's no matching
1558 looked up using socket.getservbyname(). If there's no matching
1556 service, util.Abort is raised.
1559 service, util.Abort is raised.
1557 """
1560 """
1558 try:
1561 try:
1559 return int(port)
1562 return int(port)
1560 except ValueError:
1563 except ValueError:
1561 pass
1564 pass
1562
1565
1563 try:
1566 try:
1564 return socket.getservbyname(port)
1567 return socket.getservbyname(port)
1565 except socket.error:
1568 except socket.error:
1566 raise Abort(_("no port number associated with service '%s'") % port)
1569 raise Abort(_("no port number associated with service '%s'") % port)
1567
1570
1568 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1571 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1569 '0': False, 'no': False, 'false': False, 'off': False,
1572 '0': False, 'no': False, 'false': False, 'off': False,
1570 'never': False}
1573 'never': False}
1571
1574
1572 def parsebool(s):
1575 def parsebool(s):
1573 """Parse s into a boolean.
1576 """Parse s into a boolean.
1574
1577
1575 If s is not a valid boolean, returns None.
1578 If s is not a valid boolean, returns None.
1576 """
1579 """
1577 return _booleans.get(s.lower(), None)
1580 return _booleans.get(s.lower(), None)
@@ -1,1119 +1,1123 b''
1 $ hg init a
1 $ hg init a
2
2
3 $ cd a
3 $ cd a
4 $ echo a > a
4 $ echo a > a
5 $ hg ci -Ama -d '1 0'
5 $ hg ci -Ama -d '1 0'
6 adding a
6 adding a
7
7
8 $ hg cp a b
8 $ hg cp a b
9 $ hg ci -mb -d '2 0'
9 $ hg ci -mb -d '2 0'
10
10
11 $ mkdir dir
11 $ mkdir dir
12 $ hg mv b dir
12 $ hg mv b dir
13 $ hg ci -mc -d '3 0'
13 $ hg ci -mc -d '3 0'
14
14
15 $ hg mv a b
15 $ hg mv a b
16 $ echo a > d
16 $ echo a > d
17 $ hg add d
17 $ hg add d
18 $ hg ci -md -d '4 0'
18 $ hg ci -md -d '4 0'
19
19
20 $ hg mv dir/b e
20 $ hg mv dir/b e
21 $ hg ci -me -d '5 0'
21 $ hg ci -me -d '5 0'
22
22
23 $ hg log a
23 $ hg log a
24 changeset: 0:8580ff50825a
24 changeset: 0:8580ff50825a
25 user: test
25 user: test
26 date: Thu Jan 01 00:00:01 1970 +0000
26 date: Thu Jan 01 00:00:01 1970 +0000
27 summary: a
27 summary: a
28
28
29
29
30 -f, directory
30 -f, directory
31
31
32 $ hg log -f dir
32 $ hg log -f dir
33 abort: cannot follow nonexistent file: "dir"
33 abort: cannot follow nonexistent file: "dir"
34 [255]
34 [255]
35
35
36 -f, but no args
36 -f, but no args
37
37
38 $ hg log -f
38 $ hg log -f
39 changeset: 4:66c1345dc4f9
39 changeset: 4:66c1345dc4f9
40 tag: tip
40 tag: tip
41 user: test
41 user: test
42 date: Thu Jan 01 00:00:05 1970 +0000
42 date: Thu Jan 01 00:00:05 1970 +0000
43 summary: e
43 summary: e
44
44
45 changeset: 3:7c6c671bb7cc
45 changeset: 3:7c6c671bb7cc
46 user: test
46 user: test
47 date: Thu Jan 01 00:00:04 1970 +0000
47 date: Thu Jan 01 00:00:04 1970 +0000
48 summary: d
48 summary: d
49
49
50 changeset: 2:41dd4284081e
50 changeset: 2:41dd4284081e
51 user: test
51 user: test
52 date: Thu Jan 01 00:00:03 1970 +0000
52 date: Thu Jan 01 00:00:03 1970 +0000
53 summary: c
53 summary: c
54
54
55 changeset: 1:784de7cef101
55 changeset: 1:784de7cef101
56 user: test
56 user: test
57 date: Thu Jan 01 00:00:02 1970 +0000
57 date: Thu Jan 01 00:00:02 1970 +0000
58 summary: b
58 summary: b
59
59
60 changeset: 0:8580ff50825a
60 changeset: 0:8580ff50825a
61 user: test
61 user: test
62 date: Thu Jan 01 00:00:01 1970 +0000
62 date: Thu Jan 01 00:00:01 1970 +0000
63 summary: a
63 summary: a
64
64
65
65
66 one rename
66 one rename
67
67
68 $ hg log -vf a
68 $ hg log -vf a
69 changeset: 0:8580ff50825a
69 changeset: 0:8580ff50825a
70 user: test
70 user: test
71 date: Thu Jan 01 00:00:01 1970 +0000
71 date: Thu Jan 01 00:00:01 1970 +0000
72 files: a
72 files: a
73 description:
73 description:
74 a
74 a
75
75
76
76
77
77
78 many renames
78 many renames
79
79
80 $ hg log -vf e
80 $ hg log -vf e
81 changeset: 4:66c1345dc4f9
81 changeset: 4:66c1345dc4f9
82 tag: tip
82 tag: tip
83 user: test
83 user: test
84 date: Thu Jan 01 00:00:05 1970 +0000
84 date: Thu Jan 01 00:00:05 1970 +0000
85 files: dir/b e
85 files: dir/b e
86 description:
86 description:
87 e
87 e
88
88
89
89
90 changeset: 2:41dd4284081e
90 changeset: 2:41dd4284081e
91 user: test
91 user: test
92 date: Thu Jan 01 00:00:03 1970 +0000
92 date: Thu Jan 01 00:00:03 1970 +0000
93 files: b dir/b
93 files: b dir/b
94 description:
94 description:
95 c
95 c
96
96
97
97
98 changeset: 1:784de7cef101
98 changeset: 1:784de7cef101
99 user: test
99 user: test
100 date: Thu Jan 01 00:00:02 1970 +0000
100 date: Thu Jan 01 00:00:02 1970 +0000
101 files: b
101 files: b
102 description:
102 description:
103 b
103 b
104
104
105
105
106 changeset: 0:8580ff50825a
106 changeset: 0:8580ff50825a
107 user: test
107 user: test
108 date: Thu Jan 01 00:00:01 1970 +0000
108 date: Thu Jan 01 00:00:01 1970 +0000
109 files: a
109 files: a
110 description:
110 description:
111 a
111 a
112
112
113
113
114
114
115
115
116 log -pf dir/b
116 log -pf dir/b
117
117
118 $ hg log -pf dir/b
118 $ hg log -pf dir/b
119 changeset: 2:41dd4284081e
119 changeset: 2:41dd4284081e
120 user: test
120 user: test
121 date: Thu Jan 01 00:00:03 1970 +0000
121 date: Thu Jan 01 00:00:03 1970 +0000
122 summary: c
122 summary: c
123
123
124 diff -r 784de7cef101 -r 41dd4284081e dir/b
124 diff -r 784de7cef101 -r 41dd4284081e dir/b
125 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
125 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
126 +++ b/dir/b Thu Jan 01 00:00:03 1970 +0000
126 +++ b/dir/b Thu Jan 01 00:00:03 1970 +0000
127 @@ -0,0 +1,1 @@
127 @@ -0,0 +1,1 @@
128 +a
128 +a
129
129
130 changeset: 1:784de7cef101
130 changeset: 1:784de7cef101
131 user: test
131 user: test
132 date: Thu Jan 01 00:00:02 1970 +0000
132 date: Thu Jan 01 00:00:02 1970 +0000
133 summary: b
133 summary: b
134
134
135 diff -r 8580ff50825a -r 784de7cef101 b
135 diff -r 8580ff50825a -r 784de7cef101 b
136 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
136 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
137 +++ b/b Thu Jan 01 00:00:02 1970 +0000
137 +++ b/b Thu Jan 01 00:00:02 1970 +0000
138 @@ -0,0 +1,1 @@
138 @@ -0,0 +1,1 @@
139 +a
139 +a
140
140
141 changeset: 0:8580ff50825a
141 changeset: 0:8580ff50825a
142 user: test
142 user: test
143 date: Thu Jan 01 00:00:01 1970 +0000
143 date: Thu Jan 01 00:00:01 1970 +0000
144 summary: a
144 summary: a
145
145
146 diff -r 000000000000 -r 8580ff50825a a
146 diff -r 000000000000 -r 8580ff50825a a
147 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
147 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
148 +++ b/a Thu Jan 01 00:00:01 1970 +0000
148 +++ b/a Thu Jan 01 00:00:01 1970 +0000
149 @@ -0,0 +1,1 @@
149 @@ -0,0 +1,1 @@
150 +a
150 +a
151
151
152
152
153 log -vf dir/b
153 log -vf dir/b
154
154
155 $ hg log -vf dir/b
155 $ hg log -vf dir/b
156 changeset: 2:41dd4284081e
156 changeset: 2:41dd4284081e
157 user: test
157 user: test
158 date: Thu Jan 01 00:00:03 1970 +0000
158 date: Thu Jan 01 00:00:03 1970 +0000
159 files: b dir/b
159 files: b dir/b
160 description:
160 description:
161 c
161 c
162
162
163
163
164 changeset: 1:784de7cef101
164 changeset: 1:784de7cef101
165 user: test
165 user: test
166 date: Thu Jan 01 00:00:02 1970 +0000
166 date: Thu Jan 01 00:00:02 1970 +0000
167 files: b
167 files: b
168 description:
168 description:
169 b
169 b
170
170
171
171
172 changeset: 0:8580ff50825a
172 changeset: 0:8580ff50825a
173 user: test
173 user: test
174 date: Thu Jan 01 00:00:01 1970 +0000
174 date: Thu Jan 01 00:00:01 1970 +0000
175 files: a
175 files: a
176 description:
176 description:
177 a
177 a
178
178
179
179
180
180
181
181
182 log copies with --copies
182 log copies with --copies
183
183
184 $ hg log -vC --template '{rev} {file_copies}\n'
184 $ hg log -vC --template '{rev} {file_copies}\n'
185 4 e (dir/b)
185 4 e (dir/b)
186 3 b (a)
186 3 b (a)
187 2 dir/b (b)
187 2 dir/b (b)
188 1 b (a)
188 1 b (a)
189 0
189 0
190
190
191 log copies switch without --copies, with old filecopy template
191 log copies switch without --copies, with old filecopy template
192
192
193 $ hg log -v --template '{rev} {file_copies_switch%filecopy}\n'
193 $ hg log -v --template '{rev} {file_copies_switch%filecopy}\n'
194 4
194 4
195 3
195 3
196 2
196 2
197 1
197 1
198 0
198 0
199
199
200 log copies switch with --copies
200 log copies switch with --copies
201
201
202 $ hg log -vC --template '{rev} {file_copies_switch}\n'
202 $ hg log -vC --template '{rev} {file_copies_switch}\n'
203 4 e (dir/b)
203 4 e (dir/b)
204 3 b (a)
204 3 b (a)
205 2 dir/b (b)
205 2 dir/b (b)
206 1 b (a)
206 1 b (a)
207 0
207 0
208
208
209
209
210 log copies with hardcoded style and with --style=default
210 log copies with hardcoded style and with --style=default
211
211
212 $ hg log -vC -r4
212 $ hg log -vC -r4
213 changeset: 4:66c1345dc4f9
213 changeset: 4:66c1345dc4f9
214 tag: tip
214 tag: tip
215 user: test
215 user: test
216 date: Thu Jan 01 00:00:05 1970 +0000
216 date: Thu Jan 01 00:00:05 1970 +0000
217 files: dir/b e
217 files: dir/b e
218 copies: e (dir/b)
218 copies: e (dir/b)
219 description:
219 description:
220 e
220 e
221
221
222
222
223 $ hg log -vC -r4 --style=default
223 $ hg log -vC -r4 --style=default
224 changeset: 4:66c1345dc4f9
224 changeset: 4:66c1345dc4f9
225 tag: tip
225 tag: tip
226 user: test
226 user: test
227 date: Thu Jan 01 00:00:05 1970 +0000
227 date: Thu Jan 01 00:00:05 1970 +0000
228 files: dir/b e
228 files: dir/b e
229 copies: e (dir/b)
229 copies: e (dir/b)
230 description:
230 description:
231 e
231 e
232
232
233
233
234
234
235
235
236 log copies, non-linear manifest
236 log copies, non-linear manifest
237
237
238 $ hg up -C 3
238 $ hg up -C 3
239 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
239 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
240 $ hg mv dir/b e
240 $ hg mv dir/b e
241 $ echo foo > foo
241 $ echo foo > foo
242 $ hg ci -Ame2 -d '6 0'
242 $ hg ci -Ame2 -d '6 0'
243 adding foo
243 adding foo
244 created new head
244 created new head
245 $ hg log -v --template '{rev} {file_copies}\n' -r 5
245 $ hg log -v --template '{rev} {file_copies}\n' -r 5
246 5 e (dir/b)
246 5 e (dir/b)
247
247
248
248
249 log copies, execute bit set
249 log copies, execute bit set
250
250
251 $ chmod +x e
251 $ chmod +x e
252 $ hg ci -me3 -d '7 0'
252 $ hg ci -me3 -d '7 0'
253 $ hg log -v --template '{rev} {file_copies}\n' -r 6
253 $ hg log -v --template '{rev} {file_copies}\n' -r 6
254 6
254 6
255
255
256
256
257 log -p d
257 log -p d
258
258
259 $ hg log -pv d
259 $ hg log -pv d
260 changeset: 3:7c6c671bb7cc
260 changeset: 3:7c6c671bb7cc
261 user: test
261 user: test
262 date: Thu Jan 01 00:00:04 1970 +0000
262 date: Thu Jan 01 00:00:04 1970 +0000
263 files: a b d
263 files: a b d
264 description:
264 description:
265 d
265 d
266
266
267
267
268 diff -r 41dd4284081e -r 7c6c671bb7cc d
268 diff -r 41dd4284081e -r 7c6c671bb7cc d
269 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
269 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
270 +++ b/d Thu Jan 01 00:00:04 1970 +0000
270 +++ b/d Thu Jan 01 00:00:04 1970 +0000
271 @@ -0,0 +1,1 @@
271 @@ -0,0 +1,1 @@
272 +a
272 +a
273
273
274
274
275
275
276 log --removed file
276 log --removed file
277
277
278 $ hg log --removed -v a
278 $ hg log --removed -v a
279 changeset: 3:7c6c671bb7cc
279 changeset: 3:7c6c671bb7cc
280 user: test
280 user: test
281 date: Thu Jan 01 00:00:04 1970 +0000
281 date: Thu Jan 01 00:00:04 1970 +0000
282 files: a b d
282 files: a b d
283 description:
283 description:
284 d
284 d
285
285
286
286
287 changeset: 0:8580ff50825a
287 changeset: 0:8580ff50825a
288 user: test
288 user: test
289 date: Thu Jan 01 00:00:01 1970 +0000
289 date: Thu Jan 01 00:00:01 1970 +0000
290 files: a
290 files: a
291 description:
291 description:
292 a
292 a
293
293
294
294
295
295
296 log --removed revrange file
296 log --removed revrange file
297
297
298 $ hg log --removed -v -r0:2 a
298 $ hg log --removed -v -r0:2 a
299 changeset: 0:8580ff50825a
299 changeset: 0:8580ff50825a
300 user: test
300 user: test
301 date: Thu Jan 01 00:00:01 1970 +0000
301 date: Thu Jan 01 00:00:01 1970 +0000
302 files: a
302 files: a
303 description:
303 description:
304 a
304 a
305
305
306
306
307
307
308
308
309 log --follow tests
309 log --follow tests
310
310
311 $ hg init ../follow
311 $ hg init ../follow
312 $ cd ../follow
312 $ cd ../follow
313
313
314 $ echo base > base
314 $ echo base > base
315 $ hg ci -Ambase -d '1 0'
315 $ hg ci -Ambase -d '1 0'
316 adding base
316 adding base
317
317
318 $ echo r1 >> base
318 $ echo r1 >> base
319 $ hg ci -Amr1 -d '1 0'
319 $ hg ci -Amr1 -d '1 0'
320 $ echo r2 >> base
320 $ echo r2 >> base
321 $ hg ci -Amr2 -d '1 0'
321 $ hg ci -Amr2 -d '1 0'
322
322
323 $ hg up -C 1
323 $ hg up -C 1
324 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
324 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
325 $ echo b1 > b1
325 $ echo b1 > b1
326 $ hg ci -Amb1 -d '1 0'
326 $ hg ci -Amb1 -d '1 0'
327 adding b1
327 adding b1
328 created new head
328 created new head
329
329
330
330
331 log -f
331 log -f
332
332
333 $ hg log -f
333 $ hg log -f
334 changeset: 3:e62f78d544b4
334 changeset: 3:e62f78d544b4
335 tag: tip
335 tag: tip
336 parent: 1:3d5bf5654eda
336 parent: 1:3d5bf5654eda
337 user: test
337 user: test
338 date: Thu Jan 01 00:00:01 1970 +0000
338 date: Thu Jan 01 00:00:01 1970 +0000
339 summary: b1
339 summary: b1
340
340
341 changeset: 1:3d5bf5654eda
341 changeset: 1:3d5bf5654eda
342 user: test
342 user: test
343 date: Thu Jan 01 00:00:01 1970 +0000
343 date: Thu Jan 01 00:00:01 1970 +0000
344 summary: r1
344 summary: r1
345
345
346 changeset: 0:67e992f2c4f3
346 changeset: 0:67e992f2c4f3
347 user: test
347 user: test
348 date: Thu Jan 01 00:00:01 1970 +0000
348 date: Thu Jan 01 00:00:01 1970 +0000
349 summary: base
349 summary: base
350
350
351
351
352
352
353 log -f -r 1:tip
353 log -f -r 1:tip
354
354
355 $ hg up -C 0
355 $ hg up -C 0
356 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
356 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
357 $ echo b2 > b2
357 $ echo b2 > b2
358 $ hg ci -Amb2 -d '1 0'
358 $ hg ci -Amb2 -d '1 0'
359 adding b2
359 adding b2
360 created new head
360 created new head
361 $ hg log -f -r 1:tip
361 $ hg log -f -r 1:tip
362 changeset: 1:3d5bf5654eda
362 changeset: 1:3d5bf5654eda
363 user: test
363 user: test
364 date: Thu Jan 01 00:00:01 1970 +0000
364 date: Thu Jan 01 00:00:01 1970 +0000
365 summary: r1
365 summary: r1
366
366
367 changeset: 2:60c670bf5b30
367 changeset: 2:60c670bf5b30
368 user: test
368 user: test
369 date: Thu Jan 01 00:00:01 1970 +0000
369 date: Thu Jan 01 00:00:01 1970 +0000
370 summary: r2
370 summary: r2
371
371
372 changeset: 3:e62f78d544b4
372 changeset: 3:e62f78d544b4
373 parent: 1:3d5bf5654eda
373 parent: 1:3d5bf5654eda
374 user: test
374 user: test
375 date: Thu Jan 01 00:00:01 1970 +0000
375 date: Thu Jan 01 00:00:01 1970 +0000
376 summary: b1
376 summary: b1
377
377
378
378
379
379
380 log -r . with two parents
380 log -r . with two parents
381
381
382 $ hg up -C 3
382 $ hg up -C 3
383 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
383 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
384 $ hg merge tip
384 $ hg merge tip
385 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
385 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
386 (branch merge, don't forget to commit)
386 (branch merge, don't forget to commit)
387 $ hg log -r .
387 $ hg log -r .
388 changeset: 3:e62f78d544b4
388 changeset: 3:e62f78d544b4
389 parent: 1:3d5bf5654eda
389 parent: 1:3d5bf5654eda
390 user: test
390 user: test
391 date: Thu Jan 01 00:00:01 1970 +0000
391 date: Thu Jan 01 00:00:01 1970 +0000
392 summary: b1
392 summary: b1
393
393
394
394
395
395
396 log -r . with one parent
396 log -r . with one parent
397
397
398 $ hg ci -mm12 -d '1 0'
398 $ hg ci -mm12 -d '1 0'
399 $ hg log -r .
399 $ hg log -r .
400 changeset: 5:302e9dd6890d
400 changeset: 5:302e9dd6890d
401 tag: tip
401 tag: tip
402 parent: 3:e62f78d544b4
402 parent: 3:e62f78d544b4
403 parent: 4:ddb82e70d1a1
403 parent: 4:ddb82e70d1a1
404 user: test
404 user: test
405 date: Thu Jan 01 00:00:01 1970 +0000
405 date: Thu Jan 01 00:00:01 1970 +0000
406 summary: m12
406 summary: m12
407
407
408
408
409 $ echo postm >> b1
409 $ echo postm >> b1
410 $ hg ci -Amb1.1 -d'1 0'
410 $ hg ci -Amb1.1 -d'1 0'
411
411
412
412
413 log --follow-first
413 log --follow-first
414
414
415 $ hg log --follow-first
415 $ hg log --follow-first
416 changeset: 6:2404bbcab562
416 changeset: 6:2404bbcab562
417 tag: tip
417 tag: tip
418 user: test
418 user: test
419 date: Thu Jan 01 00:00:01 1970 +0000
419 date: Thu Jan 01 00:00:01 1970 +0000
420 summary: b1.1
420 summary: b1.1
421
421
422 changeset: 5:302e9dd6890d
422 changeset: 5:302e9dd6890d
423 parent: 3:e62f78d544b4
423 parent: 3:e62f78d544b4
424 parent: 4:ddb82e70d1a1
424 parent: 4:ddb82e70d1a1
425 user: test
425 user: test
426 date: Thu Jan 01 00:00:01 1970 +0000
426 date: Thu Jan 01 00:00:01 1970 +0000
427 summary: m12
427 summary: m12
428
428
429 changeset: 3:e62f78d544b4
429 changeset: 3:e62f78d544b4
430 parent: 1:3d5bf5654eda
430 parent: 1:3d5bf5654eda
431 user: test
431 user: test
432 date: Thu Jan 01 00:00:01 1970 +0000
432 date: Thu Jan 01 00:00:01 1970 +0000
433 summary: b1
433 summary: b1
434
434
435 changeset: 1:3d5bf5654eda
435 changeset: 1:3d5bf5654eda
436 user: test
436 user: test
437 date: Thu Jan 01 00:00:01 1970 +0000
437 date: Thu Jan 01 00:00:01 1970 +0000
438 summary: r1
438 summary: r1
439
439
440 changeset: 0:67e992f2c4f3
440 changeset: 0:67e992f2c4f3
441 user: test
441 user: test
442 date: Thu Jan 01 00:00:01 1970 +0000
442 date: Thu Jan 01 00:00:01 1970 +0000
443 summary: base
443 summary: base
444
444
445
445
446
446
447 log -P 2
447 log -P 2
448
448
449 $ hg log -P 2
449 $ hg log -P 2
450 changeset: 6:2404bbcab562
450 changeset: 6:2404bbcab562
451 tag: tip
451 tag: tip
452 user: test
452 user: test
453 date: Thu Jan 01 00:00:01 1970 +0000
453 date: Thu Jan 01 00:00:01 1970 +0000
454 summary: b1.1
454 summary: b1.1
455
455
456 changeset: 5:302e9dd6890d
456 changeset: 5:302e9dd6890d
457 parent: 3:e62f78d544b4
457 parent: 3:e62f78d544b4
458 parent: 4:ddb82e70d1a1
458 parent: 4:ddb82e70d1a1
459 user: test
459 user: test
460 date: Thu Jan 01 00:00:01 1970 +0000
460 date: Thu Jan 01 00:00:01 1970 +0000
461 summary: m12
461 summary: m12
462
462
463 changeset: 4:ddb82e70d1a1
463 changeset: 4:ddb82e70d1a1
464 parent: 0:67e992f2c4f3
464 parent: 0:67e992f2c4f3
465 user: test
465 user: test
466 date: Thu Jan 01 00:00:01 1970 +0000
466 date: Thu Jan 01 00:00:01 1970 +0000
467 summary: b2
467 summary: b2
468
468
469 changeset: 3:e62f78d544b4
469 changeset: 3:e62f78d544b4
470 parent: 1:3d5bf5654eda
470 parent: 1:3d5bf5654eda
471 user: test
471 user: test
472 date: Thu Jan 01 00:00:01 1970 +0000
472 date: Thu Jan 01 00:00:01 1970 +0000
473 summary: b1
473 summary: b1
474
474
475
475
476
476
477 log -r tip -p --git
477 log -r tip -p --git
478
478
479 $ hg log -r tip -p --git
479 $ hg log -r tip -p --git
480 changeset: 6:2404bbcab562
480 changeset: 6:2404bbcab562
481 tag: tip
481 tag: tip
482 user: test
482 user: test
483 date: Thu Jan 01 00:00:01 1970 +0000
483 date: Thu Jan 01 00:00:01 1970 +0000
484 summary: b1.1
484 summary: b1.1
485
485
486 diff --git a/b1 b/b1
486 diff --git a/b1 b/b1
487 --- a/b1
487 --- a/b1
488 +++ b/b1
488 +++ b/b1
489 @@ -1,1 +1,2 @@
489 @@ -1,1 +1,2 @@
490 b1
490 b1
491 +postm
491 +postm
492
492
493
493
494
494
495 log -r ""
495 log -r ""
496
496
497 $ hg log -r ''
497 $ hg log -r ''
498 hg: parse error: empty query
498 hg: parse error: empty query
499 [255]
499 [255]
500
500
501 log -r <some unknown node id>
501 log -r <some unknown node id>
502
502
503 $ hg log -r 1000000000000000000000000000000000000000
503 $ hg log -r 1000000000000000000000000000000000000000
504 abort: unknown revision '1000000000000000000000000000000000000000'!
504 abort: unknown revision '1000000000000000000000000000000000000000'!
505 [255]
505 [255]
506
506
507 log -k r1
507 log -k r1
508
508
509 $ hg log -k r1
509 $ hg log -k r1
510 changeset: 1:3d5bf5654eda
510 changeset: 1:3d5bf5654eda
511 user: test
511 user: test
512 date: Thu Jan 01 00:00:01 1970 +0000
512 date: Thu Jan 01 00:00:01 1970 +0000
513 summary: r1
513 summary: r1
514
514
515 log -d " " (whitespaces only)
515
516
517 $ hg log -d " "
518 abort: dates cannot consist entirely of whitespace
519 [255]
516
520
517 log -d -1
521 log -d -1
518
522
519 $ hg log -d -1
523 $ hg log -d -1
520
524
521
525
522 log -p -l2 --color=always
526 log -p -l2 --color=always
523
527
524 $ hg --config extensions.color= --config color.mode=ansi \
528 $ hg --config extensions.color= --config color.mode=ansi \
525 > log -p -l2 --color=always
529 > log -p -l2 --color=always
526 \x1b[0;33mchangeset: 6:2404bbcab562\x1b[0m (esc)
530 \x1b[0;33mchangeset: 6:2404bbcab562\x1b[0m (esc)
527 tag: tip
531 tag: tip
528 user: test
532 user: test
529 date: Thu Jan 01 00:00:01 1970 +0000
533 date: Thu Jan 01 00:00:01 1970 +0000
530 summary: b1.1
534 summary: b1.1
531
535
532 \x1b[0;1mdiff -r 302e9dd6890d -r 2404bbcab562 b1\x1b[0m (esc)
536 \x1b[0;1mdiff -r 302e9dd6890d -r 2404bbcab562 b1\x1b[0m (esc)
533 \x1b[0;31;1m--- a/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
537 \x1b[0;31;1m--- a/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
534 \x1b[0;32;1m+++ b/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
538 \x1b[0;32;1m+++ b/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
535 \x1b[0;35m@@ -1,1 +1,2 @@\x1b[0m (esc)
539 \x1b[0;35m@@ -1,1 +1,2 @@\x1b[0m (esc)
536 b1
540 b1
537 \x1b[0;32m+postm\x1b[0m (esc)
541 \x1b[0;32m+postm\x1b[0m (esc)
538
542
539 \x1b[0;33mchangeset: 5:302e9dd6890d\x1b[0m (esc)
543 \x1b[0;33mchangeset: 5:302e9dd6890d\x1b[0m (esc)
540 parent: 3:e62f78d544b4
544 parent: 3:e62f78d544b4
541 parent: 4:ddb82e70d1a1
545 parent: 4:ddb82e70d1a1
542 user: test
546 user: test
543 date: Thu Jan 01 00:00:01 1970 +0000
547 date: Thu Jan 01 00:00:01 1970 +0000
544 summary: m12
548 summary: m12
545
549
546 \x1b[0;1mdiff -r e62f78d544b4 -r 302e9dd6890d b2\x1b[0m (esc)
550 \x1b[0;1mdiff -r e62f78d544b4 -r 302e9dd6890d b2\x1b[0m (esc)
547 \x1b[0;31;1m--- /dev/null Thu Jan 01 00:00:00 1970 +0000\x1b[0m (esc)
551 \x1b[0;31;1m--- /dev/null Thu Jan 01 00:00:00 1970 +0000\x1b[0m (esc)
548 \x1b[0;32;1m+++ b/b2 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
552 \x1b[0;32;1m+++ b/b2 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
549 \x1b[0;35m@@ -0,0 +1,1 @@\x1b[0m (esc)
553 \x1b[0;35m@@ -0,0 +1,1 @@\x1b[0m (esc)
550 \x1b[0;32m+b2\x1b[0m (esc)
554 \x1b[0;32m+b2\x1b[0m (esc)
551
555
552
556
553
557
554 log -r tip --stat
558 log -r tip --stat
555
559
556 $ hg log -r tip --stat
560 $ hg log -r tip --stat
557 changeset: 6:2404bbcab562
561 changeset: 6:2404bbcab562
558 tag: tip
562 tag: tip
559 user: test
563 user: test
560 date: Thu Jan 01 00:00:01 1970 +0000
564 date: Thu Jan 01 00:00:01 1970 +0000
561 summary: b1.1
565 summary: b1.1
562
566
563 b1 | 1 +
567 b1 | 1 +
564 1 files changed, 1 insertions(+), 0 deletions(-)
568 1 files changed, 1 insertions(+), 0 deletions(-)
565
569
566
570
567 $ cd ..
571 $ cd ..
568
572
569 $ hg init usertest
573 $ hg init usertest
570 $ cd usertest
574 $ cd usertest
571
575
572 $ echo a > a
576 $ echo a > a
573 $ hg ci -A -m "a" -u "User One <user1@example.org>"
577 $ hg ci -A -m "a" -u "User One <user1@example.org>"
574 adding a
578 adding a
575 $ echo b > b
579 $ echo b > b
576 $ hg ci -A -m "b" -u "User Two <user2@example.org>"
580 $ hg ci -A -m "b" -u "User Two <user2@example.org>"
577 adding b
581 adding b
578
582
579 $ hg log -u "User One <user1@example.org>"
583 $ hg log -u "User One <user1@example.org>"
580 changeset: 0:29a4c94f1924
584 changeset: 0:29a4c94f1924
581 user: User One <user1@example.org>
585 user: User One <user1@example.org>
582 date: Thu Jan 01 00:00:00 1970 +0000
586 date: Thu Jan 01 00:00:00 1970 +0000
583 summary: a
587 summary: a
584
588
585 $ hg log -u "user1" -u "user2"
589 $ hg log -u "user1" -u "user2"
586 changeset: 1:e834b5e69c0e
590 changeset: 1:e834b5e69c0e
587 tag: tip
591 tag: tip
588 user: User Two <user2@example.org>
592 user: User Two <user2@example.org>
589 date: Thu Jan 01 00:00:00 1970 +0000
593 date: Thu Jan 01 00:00:00 1970 +0000
590 summary: b
594 summary: b
591
595
592 changeset: 0:29a4c94f1924
596 changeset: 0:29a4c94f1924
593 user: User One <user1@example.org>
597 user: User One <user1@example.org>
594 date: Thu Jan 01 00:00:00 1970 +0000
598 date: Thu Jan 01 00:00:00 1970 +0000
595 summary: a
599 summary: a
596
600
597 $ hg log -u "user3"
601 $ hg log -u "user3"
598
602
599 $ cd ..
603 $ cd ..
600
604
601 $ hg init branches
605 $ hg init branches
602 $ cd branches
606 $ cd branches
603
607
604 $ echo a > a
608 $ echo a > a
605 $ hg ci -A -m "commit on default"
609 $ hg ci -A -m "commit on default"
606 adding a
610 adding a
607 $ hg branch test
611 $ hg branch test
608 marked working directory as branch test
612 marked working directory as branch test
609 $ echo b > b
613 $ echo b > b
610 $ hg ci -A -m "commit on test"
614 $ hg ci -A -m "commit on test"
611 adding b
615 adding b
612
616
613 $ hg up default
617 $ hg up default
614 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
618 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
615 $ echo c > c
619 $ echo c > c
616 $ hg ci -A -m "commit on default"
620 $ hg ci -A -m "commit on default"
617 adding c
621 adding c
618 $ hg up test
622 $ hg up test
619 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
623 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
620 $ echo c > c
624 $ echo c > c
621 $ hg ci -A -m "commit on test"
625 $ hg ci -A -m "commit on test"
622 adding c
626 adding c
623
627
624
628
625 log -b default
629 log -b default
626
630
627 $ hg log -b default
631 $ hg log -b default
628 changeset: 2:c3a4f03cc9a7
632 changeset: 2:c3a4f03cc9a7
629 parent: 0:24427303d56f
633 parent: 0:24427303d56f
630 user: test
634 user: test
631 date: Thu Jan 01 00:00:00 1970 +0000
635 date: Thu Jan 01 00:00:00 1970 +0000
632 summary: commit on default
636 summary: commit on default
633
637
634 changeset: 0:24427303d56f
638 changeset: 0:24427303d56f
635 user: test
639 user: test
636 date: Thu Jan 01 00:00:00 1970 +0000
640 date: Thu Jan 01 00:00:00 1970 +0000
637 summary: commit on default
641 summary: commit on default
638
642
639
643
640
644
641 log -b test
645 log -b test
642
646
643 $ hg log -b test
647 $ hg log -b test
644 changeset: 3:f5d8de11c2e2
648 changeset: 3:f5d8de11c2e2
645 branch: test
649 branch: test
646 tag: tip
650 tag: tip
647 parent: 1:d32277701ccb
651 parent: 1:d32277701ccb
648 user: test
652 user: test
649 date: Thu Jan 01 00:00:00 1970 +0000
653 date: Thu Jan 01 00:00:00 1970 +0000
650 summary: commit on test
654 summary: commit on test
651
655
652 changeset: 1:d32277701ccb
656 changeset: 1:d32277701ccb
653 branch: test
657 branch: test
654 user: test
658 user: test
655 date: Thu Jan 01 00:00:00 1970 +0000
659 date: Thu Jan 01 00:00:00 1970 +0000
656 summary: commit on test
660 summary: commit on test
657
661
658
662
659
663
660 log -b dummy
664 log -b dummy
661
665
662 $ hg log -b dummy
666 $ hg log -b dummy
663 abort: unknown revision 'dummy'!
667 abort: unknown revision 'dummy'!
664 [255]
668 [255]
665
669
666
670
667 log -b .
671 log -b .
668
672
669 $ hg log -b .
673 $ hg log -b .
670 changeset: 3:f5d8de11c2e2
674 changeset: 3:f5d8de11c2e2
671 branch: test
675 branch: test
672 tag: tip
676 tag: tip
673 parent: 1:d32277701ccb
677 parent: 1:d32277701ccb
674 user: test
678 user: test
675 date: Thu Jan 01 00:00:00 1970 +0000
679 date: Thu Jan 01 00:00:00 1970 +0000
676 summary: commit on test
680 summary: commit on test
677
681
678 changeset: 1:d32277701ccb
682 changeset: 1:d32277701ccb
679 branch: test
683 branch: test
680 user: test
684 user: test
681 date: Thu Jan 01 00:00:00 1970 +0000
685 date: Thu Jan 01 00:00:00 1970 +0000
682 summary: commit on test
686 summary: commit on test
683
687
684
688
685
689
686 log -b default -b test
690 log -b default -b test
687
691
688 $ hg log -b default -b test
692 $ hg log -b default -b test
689 changeset: 3:f5d8de11c2e2
693 changeset: 3:f5d8de11c2e2
690 branch: test
694 branch: test
691 tag: tip
695 tag: tip
692 parent: 1:d32277701ccb
696 parent: 1:d32277701ccb
693 user: test
697 user: test
694 date: Thu Jan 01 00:00:00 1970 +0000
698 date: Thu Jan 01 00:00:00 1970 +0000
695 summary: commit on test
699 summary: commit on test
696
700
697 changeset: 2:c3a4f03cc9a7
701 changeset: 2:c3a4f03cc9a7
698 parent: 0:24427303d56f
702 parent: 0:24427303d56f
699 user: test
703 user: test
700 date: Thu Jan 01 00:00:00 1970 +0000
704 date: Thu Jan 01 00:00:00 1970 +0000
701 summary: commit on default
705 summary: commit on default
702
706
703 changeset: 1:d32277701ccb
707 changeset: 1:d32277701ccb
704 branch: test
708 branch: test
705 user: test
709 user: test
706 date: Thu Jan 01 00:00:00 1970 +0000
710 date: Thu Jan 01 00:00:00 1970 +0000
707 summary: commit on test
711 summary: commit on test
708
712
709 changeset: 0:24427303d56f
713 changeset: 0:24427303d56f
710 user: test
714 user: test
711 date: Thu Jan 01 00:00:00 1970 +0000
715 date: Thu Jan 01 00:00:00 1970 +0000
712 summary: commit on default
716 summary: commit on default
713
717
714
718
715
719
716 log -b default -b .
720 log -b default -b .
717
721
718 $ hg log -b default -b .
722 $ hg log -b default -b .
719 changeset: 3:f5d8de11c2e2
723 changeset: 3:f5d8de11c2e2
720 branch: test
724 branch: test
721 tag: tip
725 tag: tip
722 parent: 1:d32277701ccb
726 parent: 1:d32277701ccb
723 user: test
727 user: test
724 date: Thu Jan 01 00:00:00 1970 +0000
728 date: Thu Jan 01 00:00:00 1970 +0000
725 summary: commit on test
729 summary: commit on test
726
730
727 changeset: 2:c3a4f03cc9a7
731 changeset: 2:c3a4f03cc9a7
728 parent: 0:24427303d56f
732 parent: 0:24427303d56f
729 user: test
733 user: test
730 date: Thu Jan 01 00:00:00 1970 +0000
734 date: Thu Jan 01 00:00:00 1970 +0000
731 summary: commit on default
735 summary: commit on default
732
736
733 changeset: 1:d32277701ccb
737 changeset: 1:d32277701ccb
734 branch: test
738 branch: test
735 user: test
739 user: test
736 date: Thu Jan 01 00:00:00 1970 +0000
740 date: Thu Jan 01 00:00:00 1970 +0000
737 summary: commit on test
741 summary: commit on test
738
742
739 changeset: 0:24427303d56f
743 changeset: 0:24427303d56f
740 user: test
744 user: test
741 date: Thu Jan 01 00:00:00 1970 +0000
745 date: Thu Jan 01 00:00:00 1970 +0000
742 summary: commit on default
746 summary: commit on default
743
747
744
748
745
749
746 log -b . -b test
750 log -b . -b test
747
751
748 $ hg log -b . -b test
752 $ hg log -b . -b test
749 changeset: 3:f5d8de11c2e2
753 changeset: 3:f5d8de11c2e2
750 branch: test
754 branch: test
751 tag: tip
755 tag: tip
752 parent: 1:d32277701ccb
756 parent: 1:d32277701ccb
753 user: test
757 user: test
754 date: Thu Jan 01 00:00:00 1970 +0000
758 date: Thu Jan 01 00:00:00 1970 +0000
755 summary: commit on test
759 summary: commit on test
756
760
757 changeset: 1:d32277701ccb
761 changeset: 1:d32277701ccb
758 branch: test
762 branch: test
759 user: test
763 user: test
760 date: Thu Jan 01 00:00:00 1970 +0000
764 date: Thu Jan 01 00:00:00 1970 +0000
761 summary: commit on test
765 summary: commit on test
762
766
763
767
764
768
765 log -b 2
769 log -b 2
766
770
767 $ hg log -b 2
771 $ hg log -b 2
768 changeset: 2:c3a4f03cc9a7
772 changeset: 2:c3a4f03cc9a7
769 parent: 0:24427303d56f
773 parent: 0:24427303d56f
770 user: test
774 user: test
771 date: Thu Jan 01 00:00:00 1970 +0000
775 date: Thu Jan 01 00:00:00 1970 +0000
772 summary: commit on default
776 summary: commit on default
773
777
774 changeset: 0:24427303d56f
778 changeset: 0:24427303d56f
775 user: test
779 user: test
776 date: Thu Jan 01 00:00:00 1970 +0000
780 date: Thu Jan 01 00:00:00 1970 +0000
777 summary: commit on default
781 summary: commit on default
778
782
779
783
780
784
781 log -p --cwd dir (in subdir)
785 log -p --cwd dir (in subdir)
782
786
783 $ mkdir dir
787 $ mkdir dir
784 $ hg log -p --cwd dir
788 $ hg log -p --cwd dir
785 changeset: 3:f5d8de11c2e2
789 changeset: 3:f5d8de11c2e2
786 branch: test
790 branch: test
787 tag: tip
791 tag: tip
788 parent: 1:d32277701ccb
792 parent: 1:d32277701ccb
789 user: test
793 user: test
790 date: Thu Jan 01 00:00:00 1970 +0000
794 date: Thu Jan 01 00:00:00 1970 +0000
791 summary: commit on test
795 summary: commit on test
792
796
793 diff -r d32277701ccb -r f5d8de11c2e2 c
797 diff -r d32277701ccb -r f5d8de11c2e2 c
794 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
798 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
795 +++ b/c Thu Jan 01 00:00:00 1970 +0000
799 +++ b/c Thu Jan 01 00:00:00 1970 +0000
796 @@ -0,0 +1,1 @@
800 @@ -0,0 +1,1 @@
797 +c
801 +c
798
802
799 changeset: 2:c3a4f03cc9a7
803 changeset: 2:c3a4f03cc9a7
800 parent: 0:24427303d56f
804 parent: 0:24427303d56f
801 user: test
805 user: test
802 date: Thu Jan 01 00:00:00 1970 +0000
806 date: Thu Jan 01 00:00:00 1970 +0000
803 summary: commit on default
807 summary: commit on default
804
808
805 diff -r 24427303d56f -r c3a4f03cc9a7 c
809 diff -r 24427303d56f -r c3a4f03cc9a7 c
806 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
810 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
807 +++ b/c Thu Jan 01 00:00:00 1970 +0000
811 +++ b/c Thu Jan 01 00:00:00 1970 +0000
808 @@ -0,0 +1,1 @@
812 @@ -0,0 +1,1 @@
809 +c
813 +c
810
814
811 changeset: 1:d32277701ccb
815 changeset: 1:d32277701ccb
812 branch: test
816 branch: test
813 user: test
817 user: test
814 date: Thu Jan 01 00:00:00 1970 +0000
818 date: Thu Jan 01 00:00:00 1970 +0000
815 summary: commit on test
819 summary: commit on test
816
820
817 diff -r 24427303d56f -r d32277701ccb b
821 diff -r 24427303d56f -r d32277701ccb b
818 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
822 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
819 +++ b/b Thu Jan 01 00:00:00 1970 +0000
823 +++ b/b Thu Jan 01 00:00:00 1970 +0000
820 @@ -0,0 +1,1 @@
824 @@ -0,0 +1,1 @@
821 +b
825 +b
822
826
823 changeset: 0:24427303d56f
827 changeset: 0:24427303d56f
824 user: test
828 user: test
825 date: Thu Jan 01 00:00:00 1970 +0000
829 date: Thu Jan 01 00:00:00 1970 +0000
826 summary: commit on default
830 summary: commit on default
827
831
828 diff -r 000000000000 -r 24427303d56f a
832 diff -r 000000000000 -r 24427303d56f a
829 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
833 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
830 +++ b/a Thu Jan 01 00:00:00 1970 +0000
834 +++ b/a Thu Jan 01 00:00:00 1970 +0000
831 @@ -0,0 +1,1 @@
835 @@ -0,0 +1,1 @@
832 +a
836 +a
833
837
834
838
835
839
836 log -p -R repo
840 log -p -R repo
837
841
838 $ cd dir
842 $ cd dir
839 $ hg log -p -R .. ../a
843 $ hg log -p -R .. ../a
840 changeset: 0:24427303d56f
844 changeset: 0:24427303d56f
841 user: test
845 user: test
842 date: Thu Jan 01 00:00:00 1970 +0000
846 date: Thu Jan 01 00:00:00 1970 +0000
843 summary: commit on default
847 summary: commit on default
844
848
845 diff -r 000000000000 -r 24427303d56f a
849 diff -r 000000000000 -r 24427303d56f a
846 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
850 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
847 +++ b/a Thu Jan 01 00:00:00 1970 +0000
851 +++ b/a Thu Jan 01 00:00:00 1970 +0000
848 @@ -0,0 +1,1 @@
852 @@ -0,0 +1,1 @@
849 +a
853 +a
850
854
851
855
852
856
853 $ cd ..
857 $ cd ..
854 $ hg init follow2
858 $ hg init follow2
855 $ cd follow2
859 $ cd follow2
856
860
857
861
858 # Build the following history:
862 # Build the following history:
859 # tip - o - x - o - x - x
863 # tip - o - x - o - x - x
860 # \ /
864 # \ /
861 # o - o - o - x
865 # o - o - o - x
862 # \ /
866 # \ /
863 # o
867 # o
864 #
868 #
865 # Where "o" is a revision containing "foo" and
869 # Where "o" is a revision containing "foo" and
866 # "x" is a revision without "foo"
870 # "x" is a revision without "foo"
867
871
868 $ touch init
872 $ touch init
869 $ hg ci -A -m "init, unrelated"
873 $ hg ci -A -m "init, unrelated"
870 adding init
874 adding init
871 $ echo 'foo' > init
875 $ echo 'foo' > init
872 $ hg ci -m "change, unrelated"
876 $ hg ci -m "change, unrelated"
873 $ echo 'foo' > foo
877 $ echo 'foo' > foo
874 $ hg ci -A -m "add unrelated old foo"
878 $ hg ci -A -m "add unrelated old foo"
875 adding foo
879 adding foo
876 $ hg rm foo
880 $ hg rm foo
877 $ hg ci -m "delete foo, unrelated"
881 $ hg ci -m "delete foo, unrelated"
878 $ echo 'related' > foo
882 $ echo 'related' > foo
879 $ hg ci -A -m "add foo, related"
883 $ hg ci -A -m "add foo, related"
880 adding foo
884 adding foo
881
885
882 $ hg up 0
886 $ hg up 0
883 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
887 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
884 $ touch branch
888 $ touch branch
885 $ hg ci -A -m "first branch, unrelated"
889 $ hg ci -A -m "first branch, unrelated"
886 adding branch
890 adding branch
887 created new head
891 created new head
888 $ touch foo
892 $ touch foo
889 $ hg ci -A -m "create foo, related"
893 $ hg ci -A -m "create foo, related"
890 adding foo
894 adding foo
891 $ echo 'change' > foo
895 $ echo 'change' > foo
892 $ hg ci -m "change foo, related"
896 $ hg ci -m "change foo, related"
893
897
894 $ hg up 6
898 $ hg up 6
895 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
899 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
896 $ echo 'change foo in branch' > foo
900 $ echo 'change foo in branch' > foo
897 $ hg ci -m "change foo in branch, related"
901 $ hg ci -m "change foo in branch, related"
898 created new head
902 created new head
899 $ hg merge 7
903 $ hg merge 7
900 merging foo
904 merging foo
901 warning: conflicts during merge.
905 warning: conflicts during merge.
902 merging foo failed!
906 merging foo failed!
903 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
907 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
904 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
908 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
905 [1]
909 [1]
906 $ echo 'merge 1' > foo
910 $ echo 'merge 1' > foo
907 $ hg resolve -m foo
911 $ hg resolve -m foo
908 $ hg ci -m "First merge, related"
912 $ hg ci -m "First merge, related"
909
913
910 $ hg merge 4
914 $ hg merge 4
911 merging foo
915 merging foo
912 warning: conflicts during merge.
916 warning: conflicts during merge.
913 merging foo failed!
917 merging foo failed!
914 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
918 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
915 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
919 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
916 [1]
920 [1]
917 $ echo 'merge 2' > foo
921 $ echo 'merge 2' > foo
918 $ hg resolve -m foo
922 $ hg resolve -m foo
919 $ hg ci -m "Last merge, related"
923 $ hg ci -m "Last merge, related"
920
924
921 $ hg --config "extensions.graphlog=" glog
925 $ hg --config "extensions.graphlog=" glog
922 @ changeset: 10:4dae8563d2c5
926 @ changeset: 10:4dae8563d2c5
923 |\ tag: tip
927 |\ tag: tip
924 | | parent: 9:7b35701b003e
928 | | parent: 9:7b35701b003e
925 | | parent: 4:88176d361b69
929 | | parent: 4:88176d361b69
926 | | user: test
930 | | user: test
927 | | date: Thu Jan 01 00:00:00 1970 +0000
931 | | date: Thu Jan 01 00:00:00 1970 +0000
928 | | summary: Last merge, related
932 | | summary: Last merge, related
929 | |
933 | |
930 | o changeset: 9:7b35701b003e
934 | o changeset: 9:7b35701b003e
931 | |\ parent: 8:e5416ad8a855
935 | |\ parent: 8:e5416ad8a855
932 | | | parent: 7:87fe3144dcfa
936 | | | parent: 7:87fe3144dcfa
933 | | | user: test
937 | | | user: test
934 | | | date: Thu Jan 01 00:00:00 1970 +0000
938 | | | date: Thu Jan 01 00:00:00 1970 +0000
935 | | | summary: First merge, related
939 | | | summary: First merge, related
936 | | |
940 | | |
937 | | o changeset: 8:e5416ad8a855
941 | | o changeset: 8:e5416ad8a855
938 | | | parent: 6:dc6c325fe5ee
942 | | | parent: 6:dc6c325fe5ee
939 | | | user: test
943 | | | user: test
940 | | | date: Thu Jan 01 00:00:00 1970 +0000
944 | | | date: Thu Jan 01 00:00:00 1970 +0000
941 | | | summary: change foo in branch, related
945 | | | summary: change foo in branch, related
942 | | |
946 | | |
943 | o | changeset: 7:87fe3144dcfa
947 | o | changeset: 7:87fe3144dcfa
944 | |/ user: test
948 | |/ user: test
945 | | date: Thu Jan 01 00:00:00 1970 +0000
949 | | date: Thu Jan 01 00:00:00 1970 +0000
946 | | summary: change foo, related
950 | | summary: change foo, related
947 | |
951 | |
948 | o changeset: 6:dc6c325fe5ee
952 | o changeset: 6:dc6c325fe5ee
949 | | user: test
953 | | user: test
950 | | date: Thu Jan 01 00:00:00 1970 +0000
954 | | date: Thu Jan 01 00:00:00 1970 +0000
951 | | summary: create foo, related
955 | | summary: create foo, related
952 | |
956 | |
953 | o changeset: 5:73db34516eb9
957 | o changeset: 5:73db34516eb9
954 | | parent: 0:e87515fd044a
958 | | parent: 0:e87515fd044a
955 | | user: test
959 | | user: test
956 | | date: Thu Jan 01 00:00:00 1970 +0000
960 | | date: Thu Jan 01 00:00:00 1970 +0000
957 | | summary: first branch, unrelated
961 | | summary: first branch, unrelated
958 | |
962 | |
959 o | changeset: 4:88176d361b69
963 o | changeset: 4:88176d361b69
960 | | user: test
964 | | user: test
961 | | date: Thu Jan 01 00:00:00 1970 +0000
965 | | date: Thu Jan 01 00:00:00 1970 +0000
962 | | summary: add foo, related
966 | | summary: add foo, related
963 | |
967 | |
964 o | changeset: 3:dd78ae4afb56
968 o | changeset: 3:dd78ae4afb56
965 | | user: test
969 | | user: test
966 | | date: Thu Jan 01 00:00:00 1970 +0000
970 | | date: Thu Jan 01 00:00:00 1970 +0000
967 | | summary: delete foo, unrelated
971 | | summary: delete foo, unrelated
968 | |
972 | |
969 o | changeset: 2:c4c64aedf0f7
973 o | changeset: 2:c4c64aedf0f7
970 | | user: test
974 | | user: test
971 | | date: Thu Jan 01 00:00:00 1970 +0000
975 | | date: Thu Jan 01 00:00:00 1970 +0000
972 | | summary: add unrelated old foo
976 | | summary: add unrelated old foo
973 | |
977 | |
974 o | changeset: 1:e5faa7440653
978 o | changeset: 1:e5faa7440653
975 |/ user: test
979 |/ user: test
976 | date: Thu Jan 01 00:00:00 1970 +0000
980 | date: Thu Jan 01 00:00:00 1970 +0000
977 | summary: change, unrelated
981 | summary: change, unrelated
978 |
982 |
979 o changeset: 0:e87515fd044a
983 o changeset: 0:e87515fd044a
980 user: test
984 user: test
981 date: Thu Jan 01 00:00:00 1970 +0000
985 date: Thu Jan 01 00:00:00 1970 +0000
982 summary: init, unrelated
986 summary: init, unrelated
983
987
984
988
985 $ hg --traceback log -f foo
989 $ hg --traceback log -f foo
986 changeset: 10:4dae8563d2c5
990 changeset: 10:4dae8563d2c5
987 tag: tip
991 tag: tip
988 parent: 9:7b35701b003e
992 parent: 9:7b35701b003e
989 parent: 4:88176d361b69
993 parent: 4:88176d361b69
990 user: test
994 user: test
991 date: Thu Jan 01 00:00:00 1970 +0000
995 date: Thu Jan 01 00:00:00 1970 +0000
992 summary: Last merge, related
996 summary: Last merge, related
993
997
994 changeset: 9:7b35701b003e
998 changeset: 9:7b35701b003e
995 parent: 8:e5416ad8a855
999 parent: 8:e5416ad8a855
996 parent: 7:87fe3144dcfa
1000 parent: 7:87fe3144dcfa
997 user: test
1001 user: test
998 date: Thu Jan 01 00:00:00 1970 +0000
1002 date: Thu Jan 01 00:00:00 1970 +0000
999 summary: First merge, related
1003 summary: First merge, related
1000
1004
1001 changeset: 8:e5416ad8a855
1005 changeset: 8:e5416ad8a855
1002 parent: 6:dc6c325fe5ee
1006 parent: 6:dc6c325fe5ee
1003 user: test
1007 user: test
1004 date: Thu Jan 01 00:00:00 1970 +0000
1008 date: Thu Jan 01 00:00:00 1970 +0000
1005 summary: change foo in branch, related
1009 summary: change foo in branch, related
1006
1010
1007 changeset: 7:87fe3144dcfa
1011 changeset: 7:87fe3144dcfa
1008 user: test
1012 user: test
1009 date: Thu Jan 01 00:00:00 1970 +0000
1013 date: Thu Jan 01 00:00:00 1970 +0000
1010 summary: change foo, related
1014 summary: change foo, related
1011
1015
1012 changeset: 6:dc6c325fe5ee
1016 changeset: 6:dc6c325fe5ee
1013 user: test
1017 user: test
1014 date: Thu Jan 01 00:00:00 1970 +0000
1018 date: Thu Jan 01 00:00:00 1970 +0000
1015 summary: create foo, related
1019 summary: create foo, related
1016
1020
1017 changeset: 4:88176d361b69
1021 changeset: 4:88176d361b69
1018 user: test
1022 user: test
1019 date: Thu Jan 01 00:00:00 1970 +0000
1023 date: Thu Jan 01 00:00:00 1970 +0000
1020 summary: add foo, related
1024 summary: add foo, related
1021
1025
1022
1026
1023 Also check when maxrev < lastrevfilelog
1027 Also check when maxrev < lastrevfilelog
1024
1028
1025 $ hg --traceback log -f -r4 foo
1029 $ hg --traceback log -f -r4 foo
1026 changeset: 4:88176d361b69
1030 changeset: 4:88176d361b69
1027 user: test
1031 user: test
1028 date: Thu Jan 01 00:00:00 1970 +0000
1032 date: Thu Jan 01 00:00:00 1970 +0000
1029 summary: add foo, related
1033 summary: add foo, related
1030
1034
1031
1035
1032 Issue2383: hg log showing _less_ differences than hg diff
1036 Issue2383: hg log showing _less_ differences than hg diff
1033
1037
1034 $ hg init issue2383
1038 $ hg init issue2383
1035 $ cd issue2383
1039 $ cd issue2383
1036
1040
1037 Create a test repo:
1041 Create a test repo:
1038
1042
1039 $ echo a > a
1043 $ echo a > a
1040 $ hg ci -Am0
1044 $ hg ci -Am0
1041 adding a
1045 adding a
1042 $ echo b > b
1046 $ echo b > b
1043 $ hg ci -Am1
1047 $ hg ci -Am1
1044 adding b
1048 adding b
1045 $ hg co 0
1049 $ hg co 0
1046 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1050 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1047 $ echo b > a
1051 $ echo b > a
1048 $ hg ci -m2
1052 $ hg ci -m2
1049 created new head
1053 created new head
1050
1054
1051 Merge:
1055 Merge:
1052
1056
1053 $ hg merge
1057 $ hg merge
1054 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1058 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1055 (branch merge, don't forget to commit)
1059 (branch merge, don't forget to commit)
1056
1060
1057 Make sure there's a file listed in the merge to trigger the bug:
1061 Make sure there's a file listed in the merge to trigger the bug:
1058
1062
1059 $ echo c > a
1063 $ echo c > a
1060 $ hg ci -m3
1064 $ hg ci -m3
1061
1065
1062 Two files shown here in diff:
1066 Two files shown here in diff:
1063
1067
1064 $ hg diff --rev 2:3
1068 $ hg diff --rev 2:3
1065 diff -r b09be438c43a -r 8e07aafe1edc a
1069 diff -r b09be438c43a -r 8e07aafe1edc a
1066 --- a/a Thu Jan 01 00:00:00 1970 +0000
1070 --- a/a Thu Jan 01 00:00:00 1970 +0000
1067 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1071 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1068 @@ -1,1 +1,1 @@
1072 @@ -1,1 +1,1 @@
1069 -b
1073 -b
1070 +c
1074 +c
1071 diff -r b09be438c43a -r 8e07aafe1edc b
1075 diff -r b09be438c43a -r 8e07aafe1edc b
1072 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1076 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1073 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1077 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1074 @@ -0,0 +1,1 @@
1078 @@ -0,0 +1,1 @@
1075 +b
1079 +b
1076
1080
1077 Diff here should be the same:
1081 Diff here should be the same:
1078
1082
1079 $ hg log -vpr 3
1083 $ hg log -vpr 3
1080 changeset: 3:8e07aafe1edc
1084 changeset: 3:8e07aafe1edc
1081 tag: tip
1085 tag: tip
1082 parent: 2:b09be438c43a
1086 parent: 2:b09be438c43a
1083 parent: 1:925d80f479bb
1087 parent: 1:925d80f479bb
1084 user: test
1088 user: test
1085 date: Thu Jan 01 00:00:00 1970 +0000
1089 date: Thu Jan 01 00:00:00 1970 +0000
1086 files: a
1090 files: a
1087 description:
1091 description:
1088 3
1092 3
1089
1093
1090
1094
1091 diff -r b09be438c43a -r 8e07aafe1edc a
1095 diff -r b09be438c43a -r 8e07aafe1edc a
1092 --- a/a Thu Jan 01 00:00:00 1970 +0000
1096 --- a/a Thu Jan 01 00:00:00 1970 +0000
1093 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1097 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1094 @@ -1,1 +1,1 @@
1098 @@ -1,1 +1,1 @@
1095 -b
1099 -b
1096 +c
1100 +c
1097 diff -r b09be438c43a -r 8e07aafe1edc b
1101 diff -r b09be438c43a -r 8e07aafe1edc b
1098 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1102 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1099 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1103 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1100 @@ -0,0 +1,1 @@
1104 @@ -0,0 +1,1 @@
1101 +b
1105 +b
1102
1106
1103 $ cd ..
1107 $ cd ..
1104
1108
1105 'hg log -r rev fn' when last(filelog(fn)) != rev
1109 'hg log -r rev fn' when last(filelog(fn)) != rev
1106
1110
1107 $ hg init simplelog; cd simplelog
1111 $ hg init simplelog; cd simplelog
1108 $ echo f > a
1112 $ echo f > a
1109 $ hg ci -Am'a' -d '0 0'
1113 $ hg ci -Am'a' -d '0 0'
1110 adding a
1114 adding a
1111 $ echo f >> a
1115 $ echo f >> a
1112 $ hg ci -Am'a bis' -d '1 0'
1116 $ hg ci -Am'a bis' -d '1 0'
1113
1117
1114 $ hg log -r0 a
1118 $ hg log -r0 a
1115 changeset: 0:9f758d63dcde
1119 changeset: 0:9f758d63dcde
1116 user: test
1120 user: test
1117 date: Thu Jan 01 00:00:00 1970 +0000
1121 date: Thu Jan 01 00:00:00 1970 +0000
1118 summary: a
1122 summary: a
1119
1123
General Comments 0
You need to be logged in to leave comments. Login now