##// END OF EJS Templates
log: abort for -d '>' or -d '<' (issue2749)
Yun Lee -
r13867:28f557e8 default
parent child Browse files
Show More
@@ -1,1563 +1,1567 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 makedir(path, notindexed):
441 def makedir(path, notindexed):
442 os.mkdir(path)
442 os.mkdir(path)
443
443
444 def unlinkpath(f):
444 def unlinkpath(f):
445 """unlink and remove the directory if it is empty"""
445 """unlink and remove the directory if it is empty"""
446 os.unlink(f)
446 os.unlink(f)
447 # try removing directories that might now be empty
447 # try removing directories that might now be empty
448 try:
448 try:
449 os.removedirs(os.path.dirname(f))
449 os.removedirs(os.path.dirname(f))
450 except OSError:
450 except OSError:
451 pass
451 pass
452
452
453 def copyfile(src, dest):
453 def copyfile(src, dest):
454 "copy a file, preserving mode and atime/mtime"
454 "copy a file, preserving mode and atime/mtime"
455 if os.path.islink(src):
455 if os.path.islink(src):
456 try:
456 try:
457 os.unlink(dest)
457 os.unlink(dest)
458 except:
458 except:
459 pass
459 pass
460 os.symlink(os.readlink(src), dest)
460 os.symlink(os.readlink(src), dest)
461 else:
461 else:
462 try:
462 try:
463 shutil.copyfile(src, dest)
463 shutil.copyfile(src, dest)
464 shutil.copymode(src, dest)
464 shutil.copymode(src, dest)
465 except shutil.Error, inst:
465 except shutil.Error, inst:
466 raise Abort(str(inst))
466 raise Abort(str(inst))
467
467
468 def copyfiles(src, dst, hardlink=None):
468 def copyfiles(src, dst, hardlink=None):
469 """Copy a directory tree using hardlinks if possible"""
469 """Copy a directory tree using hardlinks if possible"""
470
470
471 if hardlink is None:
471 if hardlink is None:
472 hardlink = (os.stat(src).st_dev ==
472 hardlink = (os.stat(src).st_dev ==
473 os.stat(os.path.dirname(dst)).st_dev)
473 os.stat(os.path.dirname(dst)).st_dev)
474
474
475 num = 0
475 num = 0
476 if os.path.isdir(src):
476 if os.path.isdir(src):
477 os.mkdir(dst)
477 os.mkdir(dst)
478 for name, kind in osutil.listdir(src):
478 for name, kind in osutil.listdir(src):
479 srcname = os.path.join(src, name)
479 srcname = os.path.join(src, name)
480 dstname = os.path.join(dst, name)
480 dstname = os.path.join(dst, name)
481 hardlink, n = copyfiles(srcname, dstname, hardlink)
481 hardlink, n = copyfiles(srcname, dstname, hardlink)
482 num += n
482 num += n
483 else:
483 else:
484 if hardlink:
484 if hardlink:
485 try:
485 try:
486 os_link(src, dst)
486 os_link(src, dst)
487 except (IOError, OSError):
487 except (IOError, OSError):
488 hardlink = False
488 hardlink = False
489 shutil.copy(src, dst)
489 shutil.copy(src, dst)
490 else:
490 else:
491 shutil.copy(src, dst)
491 shutil.copy(src, dst)
492 num += 1
492 num += 1
493
493
494 return hardlink, num
494 return hardlink, num
495
495
496 class path_auditor(object):
496 class path_auditor(object):
497 '''ensure that a filesystem path contains no banned components.
497 '''ensure that a filesystem path contains no banned components.
498 the following properties of a path are checked:
498 the following properties of a path are checked:
499
499
500 - ends with a directory separator
500 - ends with a directory separator
501 - under top-level .hg
501 - under top-level .hg
502 - starts at the root of a windows drive
502 - starts at the root of a windows drive
503 - contains ".."
503 - contains ".."
504 - traverses a symlink (e.g. a/symlink_here/b)
504 - traverses a symlink (e.g. a/symlink_here/b)
505 - inside a nested repository (a callback can be used to approve
505 - inside a nested repository (a callback can be used to approve
506 some nested repositories, e.g., subrepositories)
506 some nested repositories, e.g., subrepositories)
507 '''
507 '''
508
508
509 def __init__(self, root, callback=None):
509 def __init__(self, root, callback=None):
510 self.audited = set()
510 self.audited = set()
511 self.auditeddir = set()
511 self.auditeddir = set()
512 self.root = root
512 self.root = root
513 self.callback = callback
513 self.callback = callback
514
514
515 def __call__(self, path):
515 def __call__(self, path):
516 if path in self.audited:
516 if path in self.audited:
517 return
517 return
518 # AIX ignores "/" at end of path, others raise EISDIR.
518 # AIX ignores "/" at end of path, others raise EISDIR.
519 if endswithsep(path):
519 if endswithsep(path):
520 raise Abort(_("path ends in directory separator: %s") % path)
520 raise Abort(_("path ends in directory separator: %s") % path)
521 normpath = os.path.normcase(path)
521 normpath = os.path.normcase(path)
522 parts = splitpath(normpath)
522 parts = splitpath(normpath)
523 if (os.path.splitdrive(path)[0]
523 if (os.path.splitdrive(path)[0]
524 or parts[0].lower() in ('.hg', '.hg.', '')
524 or parts[0].lower() in ('.hg', '.hg.', '')
525 or os.pardir in parts):
525 or os.pardir in parts):
526 raise Abort(_("path contains illegal component: %s") % path)
526 raise Abort(_("path contains illegal component: %s") % path)
527 if '.hg' in path.lower():
527 if '.hg' in path.lower():
528 lparts = [p.lower() for p in parts]
528 lparts = [p.lower() for p in parts]
529 for p in '.hg', '.hg.':
529 for p in '.hg', '.hg.':
530 if p in lparts[1:]:
530 if p in lparts[1:]:
531 pos = lparts.index(p)
531 pos = lparts.index(p)
532 base = os.path.join(*parts[:pos])
532 base = os.path.join(*parts[:pos])
533 raise Abort(_('path %r is inside repo %r') % (path, base))
533 raise Abort(_('path %r is inside repo %r') % (path, base))
534 def check(prefix):
534 def check(prefix):
535 curpath = os.path.join(self.root, prefix)
535 curpath = os.path.join(self.root, prefix)
536 try:
536 try:
537 st = os.lstat(curpath)
537 st = os.lstat(curpath)
538 except OSError, err:
538 except OSError, err:
539 # EINVAL can be raised as invalid path syntax under win32.
539 # EINVAL can be raised as invalid path syntax under win32.
540 # They must be ignored for patterns can be checked too.
540 # They must be ignored for patterns can be checked too.
541 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
541 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
542 raise
542 raise
543 else:
543 else:
544 if stat.S_ISLNK(st.st_mode):
544 if stat.S_ISLNK(st.st_mode):
545 raise Abort(_('path %r traverses symbolic link %r') %
545 raise Abort(_('path %r traverses symbolic link %r') %
546 (path, prefix))
546 (path, prefix))
547 elif (stat.S_ISDIR(st.st_mode) and
547 elif (stat.S_ISDIR(st.st_mode) and
548 os.path.isdir(os.path.join(curpath, '.hg'))):
548 os.path.isdir(os.path.join(curpath, '.hg'))):
549 if not self.callback or not self.callback(curpath):
549 if not self.callback or not self.callback(curpath):
550 raise Abort(_('path %r is inside repo %r') %
550 raise Abort(_('path %r is inside repo %r') %
551 (path, prefix))
551 (path, prefix))
552 parts.pop()
552 parts.pop()
553 prefixes = []
553 prefixes = []
554 while parts:
554 while parts:
555 prefix = os.sep.join(parts)
555 prefix = os.sep.join(parts)
556 if prefix in self.auditeddir:
556 if prefix in self.auditeddir:
557 break
557 break
558 check(prefix)
558 check(prefix)
559 prefixes.append(prefix)
559 prefixes.append(prefix)
560 parts.pop()
560 parts.pop()
561
561
562 self.audited.add(path)
562 self.audited.add(path)
563 # only add prefixes to the cache after checking everything: we don't
563 # only add prefixes to the cache after checking everything: we don't
564 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
564 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
565 self.auditeddir.update(prefixes)
565 self.auditeddir.update(prefixes)
566
566
567 def lookup_reg(key, name=None, scope=None):
567 def lookup_reg(key, name=None, scope=None):
568 return None
568 return None
569
569
570 def hidewindow():
570 def hidewindow():
571 """Hide current shell window.
571 """Hide current shell window.
572
572
573 Used to hide the window opened when starting asynchronous
573 Used to hide the window opened when starting asynchronous
574 child process under Windows, unneeded on other systems.
574 child process under Windows, unneeded on other systems.
575 """
575 """
576 pass
576 pass
577
577
578 if os.name == 'nt':
578 if os.name == 'nt':
579 from windows import *
579 from windows import *
580 else:
580 else:
581 from posix import *
581 from posix import *
582
582
583 def makelock(info, pathname):
583 def makelock(info, pathname):
584 try:
584 try:
585 return os.symlink(info, pathname)
585 return os.symlink(info, pathname)
586 except OSError, why:
586 except OSError, why:
587 if why.errno == errno.EEXIST:
587 if why.errno == errno.EEXIST:
588 raise
588 raise
589 except AttributeError: # no symlink in os
589 except AttributeError: # no symlink in os
590 pass
590 pass
591
591
592 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
592 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
593 os.write(ld, info)
593 os.write(ld, info)
594 os.close(ld)
594 os.close(ld)
595
595
596 def readlock(pathname):
596 def readlock(pathname):
597 try:
597 try:
598 return os.readlink(pathname)
598 return os.readlink(pathname)
599 except OSError, why:
599 except OSError, why:
600 if why.errno not in (errno.EINVAL, errno.ENOSYS):
600 if why.errno not in (errno.EINVAL, errno.ENOSYS):
601 raise
601 raise
602 except AttributeError: # no symlink in os
602 except AttributeError: # no symlink in os
603 pass
603 pass
604 fp = posixfile(pathname)
604 fp = posixfile(pathname)
605 r = fp.read()
605 r = fp.read()
606 fp.close()
606 fp.close()
607 return r
607 return r
608
608
609 def fstat(fp):
609 def fstat(fp):
610 '''stat file object that may not have fileno method.'''
610 '''stat file object that may not have fileno method.'''
611 try:
611 try:
612 return os.fstat(fp.fileno())
612 return os.fstat(fp.fileno())
613 except AttributeError:
613 except AttributeError:
614 return os.stat(fp.name)
614 return os.stat(fp.name)
615
615
616 # File system features
616 # File system features
617
617
618 def checkcase(path):
618 def checkcase(path):
619 """
619 """
620 Check whether the given path is on a case-sensitive filesystem
620 Check whether the given path is on a case-sensitive filesystem
621
621
622 Requires a path (like /foo/.hg) ending with a foldable final
622 Requires a path (like /foo/.hg) ending with a foldable final
623 directory component.
623 directory component.
624 """
624 """
625 s1 = os.stat(path)
625 s1 = os.stat(path)
626 d, b = os.path.split(path)
626 d, b = os.path.split(path)
627 p2 = os.path.join(d, b.upper())
627 p2 = os.path.join(d, b.upper())
628 if path == p2:
628 if path == p2:
629 p2 = os.path.join(d, b.lower())
629 p2 = os.path.join(d, b.lower())
630 try:
630 try:
631 s2 = os.stat(p2)
631 s2 = os.stat(p2)
632 if s2 == s1:
632 if s2 == s1:
633 return False
633 return False
634 return True
634 return True
635 except:
635 except:
636 return True
636 return True
637
637
638 _fspathcache = {}
638 _fspathcache = {}
639 def fspath(name, root):
639 def fspath(name, root):
640 '''Get name in the case stored in the filesystem
640 '''Get name in the case stored in the filesystem
641
641
642 The name is either relative to root, or it is an absolute path starting
642 The name is either relative to root, or it is an absolute path starting
643 with root. Note that this function is unnecessary, and should not be
643 with root. Note that this function is unnecessary, and should not be
644 called, for case-sensitive filesystems (simply because it's expensive).
644 called, for case-sensitive filesystems (simply because it's expensive).
645 '''
645 '''
646 # If name is absolute, make it relative
646 # If name is absolute, make it relative
647 if name.lower().startswith(root.lower()):
647 if name.lower().startswith(root.lower()):
648 l = len(root)
648 l = len(root)
649 if name[l] == os.sep or name[l] == os.altsep:
649 if name[l] == os.sep or name[l] == os.altsep:
650 l = l + 1
650 l = l + 1
651 name = name[l:]
651 name = name[l:]
652
652
653 if not os.path.lexists(os.path.join(root, name)):
653 if not os.path.lexists(os.path.join(root, name)):
654 return None
654 return None
655
655
656 seps = os.sep
656 seps = os.sep
657 if os.altsep:
657 if os.altsep:
658 seps = seps + os.altsep
658 seps = seps + os.altsep
659 # Protect backslashes. This gets silly very quickly.
659 # Protect backslashes. This gets silly very quickly.
660 seps.replace('\\','\\\\')
660 seps.replace('\\','\\\\')
661 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
661 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
662 dir = os.path.normcase(os.path.normpath(root))
662 dir = os.path.normcase(os.path.normpath(root))
663 result = []
663 result = []
664 for part, sep in pattern.findall(name):
664 for part, sep in pattern.findall(name):
665 if sep:
665 if sep:
666 result.append(sep)
666 result.append(sep)
667 continue
667 continue
668
668
669 if dir not in _fspathcache:
669 if dir not in _fspathcache:
670 _fspathcache[dir] = os.listdir(dir)
670 _fspathcache[dir] = os.listdir(dir)
671 contents = _fspathcache[dir]
671 contents = _fspathcache[dir]
672
672
673 lpart = part.lower()
673 lpart = part.lower()
674 lenp = len(part)
674 lenp = len(part)
675 for n in contents:
675 for n in contents:
676 if lenp == len(n) and n.lower() == lpart:
676 if lenp == len(n) and n.lower() == lpart:
677 result.append(n)
677 result.append(n)
678 break
678 break
679 else:
679 else:
680 # Cannot happen, as the file exists!
680 # Cannot happen, as the file exists!
681 result.append(part)
681 result.append(part)
682 dir = os.path.join(dir, lpart)
682 dir = os.path.join(dir, lpart)
683
683
684 return ''.join(result)
684 return ''.join(result)
685
685
686 def checkexec(path):
686 def checkexec(path):
687 """
687 """
688 Check whether the given path is on a filesystem with UNIX-like exec flags
688 Check whether the given path is on a filesystem with UNIX-like exec flags
689
689
690 Requires a directory (like /foo/.hg)
690 Requires a directory (like /foo/.hg)
691 """
691 """
692
692
693 # VFAT on some Linux versions can flip mode but it doesn't persist
693 # VFAT on some Linux versions can flip mode but it doesn't persist
694 # a FS remount. Frequently we can detect it if files are created
694 # a FS remount. Frequently we can detect it if files are created
695 # with exec bit on.
695 # with exec bit on.
696
696
697 try:
697 try:
698 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
698 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
699 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
699 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
700 try:
700 try:
701 os.close(fh)
701 os.close(fh)
702 m = os.stat(fn).st_mode & 0777
702 m = os.stat(fn).st_mode & 0777
703 new_file_has_exec = m & EXECFLAGS
703 new_file_has_exec = m & EXECFLAGS
704 os.chmod(fn, m ^ EXECFLAGS)
704 os.chmod(fn, m ^ EXECFLAGS)
705 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
705 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
706 finally:
706 finally:
707 os.unlink(fn)
707 os.unlink(fn)
708 except (IOError, OSError):
708 except (IOError, OSError):
709 # we don't care, the user probably won't be able to commit anyway
709 # we don't care, the user probably won't be able to commit anyway
710 return False
710 return False
711 return not (new_file_has_exec or exec_flags_cannot_flip)
711 return not (new_file_has_exec or exec_flags_cannot_flip)
712
712
713 def checklink(path):
713 def checklink(path):
714 """check whether the given path is on a symlink-capable filesystem"""
714 """check whether the given path is on a symlink-capable filesystem"""
715 # mktemp is not racy because symlink creation will fail if the
715 # mktemp is not racy because symlink creation will fail if the
716 # file already exists
716 # file already exists
717 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
717 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
718 try:
718 try:
719 os.symlink(".", name)
719 os.symlink(".", name)
720 os.unlink(name)
720 os.unlink(name)
721 return True
721 return True
722 except (OSError, AttributeError):
722 except (OSError, AttributeError):
723 return False
723 return False
724
724
725 def checknlink(testfile):
725 def checknlink(testfile):
726 '''check whether hardlink count reporting works properly'''
726 '''check whether hardlink count reporting works properly'''
727
727
728 # testfile may be open, so we need a separate file for checking to
728 # testfile may be open, so we need a separate file for checking to
729 # work around issue2543 (or testfile may get lost on Samba shares)
729 # work around issue2543 (or testfile may get lost on Samba shares)
730 f1 = testfile + ".hgtmp1"
730 f1 = testfile + ".hgtmp1"
731 if os.path.lexists(f1):
731 if os.path.lexists(f1):
732 return False
732 return False
733 try:
733 try:
734 posixfile(f1, 'w').close()
734 posixfile(f1, 'w').close()
735 except IOError:
735 except IOError:
736 return False
736 return False
737
737
738 f2 = testfile + ".hgtmp2"
738 f2 = testfile + ".hgtmp2"
739 fd = None
739 fd = None
740 try:
740 try:
741 try:
741 try:
742 os_link(f1, f2)
742 os_link(f1, f2)
743 except OSError:
743 except OSError:
744 return False
744 return False
745
745
746 # nlinks() may behave differently for files on Windows shares if
746 # nlinks() may behave differently for files on Windows shares if
747 # the file is open.
747 # the file is open.
748 fd = posixfile(f2)
748 fd = posixfile(f2)
749 return nlinks(f2) > 1
749 return nlinks(f2) > 1
750 finally:
750 finally:
751 if fd is not None:
751 if fd is not None:
752 fd.close()
752 fd.close()
753 for f in (f1, f2):
753 for f in (f1, f2):
754 try:
754 try:
755 os.unlink(f)
755 os.unlink(f)
756 except OSError:
756 except OSError:
757 pass
757 pass
758
758
759 return False
759 return False
760
760
761 def endswithsep(path):
761 def endswithsep(path):
762 '''Check path ends with os.sep or os.altsep.'''
762 '''Check path ends with os.sep or os.altsep.'''
763 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
763 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
764
764
765 def splitpath(path):
765 def splitpath(path):
766 '''Split path by os.sep.
766 '''Split path by os.sep.
767 Note that this function does not use os.altsep because this is
767 Note that this function does not use os.altsep because this is
768 an alternative of simple "xxx.split(os.sep)".
768 an alternative of simple "xxx.split(os.sep)".
769 It is recommended to use os.path.normpath() before using this
769 It is recommended to use os.path.normpath() before using this
770 function if need.'''
770 function if need.'''
771 return path.split(os.sep)
771 return path.split(os.sep)
772
772
773 def gui():
773 def gui():
774 '''Are we running in a GUI?'''
774 '''Are we running in a GUI?'''
775 if sys.platform == 'darwin':
775 if sys.platform == 'darwin':
776 if 'SSH_CONNECTION' in os.environ:
776 if 'SSH_CONNECTION' in os.environ:
777 # handle SSH access to a box where the user is logged in
777 # handle SSH access to a box where the user is logged in
778 return False
778 return False
779 elif getattr(osutil, 'isgui', None):
779 elif getattr(osutil, 'isgui', None):
780 # check if a CoreGraphics session is available
780 # check if a CoreGraphics session is available
781 return osutil.isgui()
781 return osutil.isgui()
782 else:
782 else:
783 # pure build; use a safe default
783 # pure build; use a safe default
784 return True
784 return True
785 else:
785 else:
786 return os.name == "nt" or os.environ.get("DISPLAY")
786 return os.name == "nt" or os.environ.get("DISPLAY")
787
787
788 def mktempcopy(name, emptyok=False, createmode=None):
788 def mktempcopy(name, emptyok=False, createmode=None):
789 """Create a temporary file with the same contents from name
789 """Create a temporary file with the same contents from name
790
790
791 The permission bits are copied from the original file.
791 The permission bits are copied from the original file.
792
792
793 If the temporary file is going to be truncated immediately, you
793 If the temporary file is going to be truncated immediately, you
794 can use emptyok=True as an optimization.
794 can use emptyok=True as an optimization.
795
795
796 Returns the name of the temporary file.
796 Returns the name of the temporary file.
797 """
797 """
798 d, fn = os.path.split(name)
798 d, fn = os.path.split(name)
799 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
799 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
800 os.close(fd)
800 os.close(fd)
801 # Temporary files are created with mode 0600, which is usually not
801 # Temporary files are created with mode 0600, which is usually not
802 # what we want. If the original file already exists, just copy
802 # what we want. If the original file already exists, just copy
803 # its mode. Otherwise, manually obey umask.
803 # its mode. Otherwise, manually obey umask.
804 try:
804 try:
805 st_mode = os.lstat(name).st_mode & 0777
805 st_mode = os.lstat(name).st_mode & 0777
806 except OSError, inst:
806 except OSError, inst:
807 if inst.errno != errno.ENOENT:
807 if inst.errno != errno.ENOENT:
808 raise
808 raise
809 st_mode = createmode
809 st_mode = createmode
810 if st_mode is None:
810 if st_mode is None:
811 st_mode = ~umask
811 st_mode = ~umask
812 st_mode &= 0666
812 st_mode &= 0666
813 os.chmod(temp, st_mode)
813 os.chmod(temp, st_mode)
814 if emptyok:
814 if emptyok:
815 return temp
815 return temp
816 try:
816 try:
817 try:
817 try:
818 ifp = posixfile(name, "rb")
818 ifp = posixfile(name, "rb")
819 except IOError, inst:
819 except IOError, inst:
820 if inst.errno == errno.ENOENT:
820 if inst.errno == errno.ENOENT:
821 return temp
821 return temp
822 if not getattr(inst, 'filename', None):
822 if not getattr(inst, 'filename', None):
823 inst.filename = name
823 inst.filename = name
824 raise
824 raise
825 ofp = posixfile(temp, "wb")
825 ofp = posixfile(temp, "wb")
826 for chunk in filechunkiter(ifp):
826 for chunk in filechunkiter(ifp):
827 ofp.write(chunk)
827 ofp.write(chunk)
828 ifp.close()
828 ifp.close()
829 ofp.close()
829 ofp.close()
830 except:
830 except:
831 try: os.unlink(temp)
831 try: os.unlink(temp)
832 except: pass
832 except: pass
833 raise
833 raise
834 return temp
834 return temp
835
835
836 class atomictempfile(object):
836 class atomictempfile(object):
837 """file-like object that atomically updates a file
837 """file-like object that atomically updates a file
838
838
839 All writes will be redirected to a temporary copy of the original
839 All writes will be redirected to a temporary copy of the original
840 file. When rename is called, the copy is renamed to the original
840 file. When rename is called, the copy is renamed to the original
841 name, making the changes visible.
841 name, making the changes visible.
842 """
842 """
843 def __init__(self, name, mode='w+b', createmode=None):
843 def __init__(self, name, mode='w+b', createmode=None):
844 self.__name = name
844 self.__name = name
845 self._fp = None
845 self._fp = None
846 self.temp = mktempcopy(name, emptyok=('w' in mode),
846 self.temp = mktempcopy(name, emptyok=('w' in mode),
847 createmode=createmode)
847 createmode=createmode)
848 self._fp = posixfile(self.temp, mode)
848 self._fp = posixfile(self.temp, mode)
849
849
850 def __getattr__(self, name):
850 def __getattr__(self, name):
851 return getattr(self._fp, name)
851 return getattr(self._fp, name)
852
852
853 def rename(self):
853 def rename(self):
854 if not self._fp.closed:
854 if not self._fp.closed:
855 self._fp.close()
855 self._fp.close()
856 rename(self.temp, localpath(self.__name))
856 rename(self.temp, localpath(self.__name))
857
857
858 def close(self):
858 def close(self):
859 if not self._fp:
859 if not self._fp:
860 return
860 return
861 if not self._fp.closed:
861 if not self._fp.closed:
862 try:
862 try:
863 os.unlink(self.temp)
863 os.unlink(self.temp)
864 except: pass
864 except: pass
865 self._fp.close()
865 self._fp.close()
866
866
867 def __del__(self):
867 def __del__(self):
868 self.close()
868 self.close()
869
869
870 def makedirs(name, mode=None):
870 def makedirs(name, mode=None):
871 """recursive directory creation with parent mode inheritance"""
871 """recursive directory creation with parent mode inheritance"""
872 parent = os.path.abspath(os.path.dirname(name))
872 parent = os.path.abspath(os.path.dirname(name))
873 try:
873 try:
874 os.mkdir(name)
874 os.mkdir(name)
875 if mode is not None:
875 if mode is not None:
876 os.chmod(name, mode)
876 os.chmod(name, mode)
877 return
877 return
878 except OSError, err:
878 except OSError, err:
879 if err.errno == errno.EEXIST:
879 if err.errno == errno.EEXIST:
880 return
880 return
881 if not name or parent == name or err.errno != errno.ENOENT:
881 if not name or parent == name or err.errno != errno.ENOENT:
882 raise
882 raise
883 makedirs(parent, mode)
883 makedirs(parent, mode)
884 makedirs(name, mode)
884 makedirs(name, mode)
885
885
886 class opener(object):
886 class opener(object):
887 """Open files relative to a base directory
887 """Open files relative to a base directory
888
888
889 This class is used to hide the details of COW semantics and
889 This class is used to hide the details of COW semantics and
890 remote file access from higher level code.
890 remote file access from higher level code.
891 """
891 """
892 def __init__(self, base, audit=True):
892 def __init__(self, base, audit=True):
893 self.base = base
893 self.base = base
894 if audit:
894 if audit:
895 self.auditor = path_auditor(base)
895 self.auditor = path_auditor(base)
896 else:
896 else:
897 self.auditor = always
897 self.auditor = always
898 self.createmode = None
898 self.createmode = None
899 self._trustnlink = None
899 self._trustnlink = None
900
900
901 @propertycache
901 @propertycache
902 def _can_symlink(self):
902 def _can_symlink(self):
903 return checklink(self.base)
903 return checklink(self.base)
904
904
905 def _fixfilemode(self, name):
905 def _fixfilemode(self, name):
906 if self.createmode is None:
906 if self.createmode is None:
907 return
907 return
908 os.chmod(name, self.createmode & 0666)
908 os.chmod(name, self.createmode & 0666)
909
909
910 def __call__(self, path, mode="r", text=False, atomictemp=False):
910 def __call__(self, path, mode="r", text=False, atomictemp=False):
911 self.auditor(path)
911 self.auditor(path)
912 f = os.path.join(self.base, path)
912 f = os.path.join(self.base, path)
913
913
914 if not text and "b" not in mode:
914 if not text and "b" not in mode:
915 mode += "b" # for that other OS
915 mode += "b" # for that other OS
916
916
917 nlink = -1
917 nlink = -1
918 dirname, basename = os.path.split(f)
918 dirname, basename = os.path.split(f)
919 # If basename is empty, then the path is malformed because it points
919 # If basename is empty, then the path is malformed because it points
920 # to a directory. Let the posixfile() call below raise IOError.
920 # to a directory. Let the posixfile() call below raise IOError.
921 if basename and mode not in ('r', 'rb'):
921 if basename and mode not in ('r', 'rb'):
922 if atomictemp:
922 if atomictemp:
923 if not os.path.isdir(dirname):
923 if not os.path.isdir(dirname):
924 makedirs(dirname, self.createmode)
924 makedirs(dirname, self.createmode)
925 return atomictempfile(f, mode, self.createmode)
925 return atomictempfile(f, mode, self.createmode)
926 try:
926 try:
927 if 'w' in mode:
927 if 'w' in mode:
928 unlink(f)
928 unlink(f)
929 nlink = 0
929 nlink = 0
930 else:
930 else:
931 # nlinks() may behave differently for files on Windows
931 # nlinks() may behave differently for files on Windows
932 # shares if the file is open.
932 # shares if the file is open.
933 fd = posixfile(f)
933 fd = posixfile(f)
934 nlink = nlinks(f)
934 nlink = nlinks(f)
935 if nlink < 1:
935 if nlink < 1:
936 nlink = 2 # force mktempcopy (issue1922)
936 nlink = 2 # force mktempcopy (issue1922)
937 fd.close()
937 fd.close()
938 except (OSError, IOError), e:
938 except (OSError, IOError), e:
939 if e.errno != errno.ENOENT:
939 if e.errno != errno.ENOENT:
940 raise
940 raise
941 nlink = 0
941 nlink = 0
942 if not os.path.isdir(dirname):
942 if not os.path.isdir(dirname):
943 makedirs(dirname, self.createmode)
943 makedirs(dirname, self.createmode)
944 if nlink > 0:
944 if nlink > 0:
945 if self._trustnlink is None:
945 if self._trustnlink is None:
946 self._trustnlink = nlink > 1 or checknlink(f)
946 self._trustnlink = nlink > 1 or checknlink(f)
947 if nlink > 1 or not self._trustnlink:
947 if nlink > 1 or not self._trustnlink:
948 rename(mktempcopy(f), f)
948 rename(mktempcopy(f), f)
949 fp = posixfile(f, mode)
949 fp = posixfile(f, mode)
950 if nlink == 0:
950 if nlink == 0:
951 self._fixfilemode(f)
951 self._fixfilemode(f)
952 return fp
952 return fp
953
953
954 def symlink(self, src, dst):
954 def symlink(self, src, dst):
955 self.auditor(dst)
955 self.auditor(dst)
956 linkname = os.path.join(self.base, dst)
956 linkname = os.path.join(self.base, dst)
957 try:
957 try:
958 os.unlink(linkname)
958 os.unlink(linkname)
959 except OSError:
959 except OSError:
960 pass
960 pass
961
961
962 dirname = os.path.dirname(linkname)
962 dirname = os.path.dirname(linkname)
963 if not os.path.exists(dirname):
963 if not os.path.exists(dirname):
964 makedirs(dirname, self.createmode)
964 makedirs(dirname, self.createmode)
965
965
966 if self._can_symlink:
966 if self._can_symlink:
967 try:
967 try:
968 os.symlink(src, linkname)
968 os.symlink(src, linkname)
969 except OSError, err:
969 except OSError, err:
970 raise OSError(err.errno, _('could not symlink to %r: %s') %
970 raise OSError(err.errno, _('could not symlink to %r: %s') %
971 (src, err.strerror), linkname)
971 (src, err.strerror), linkname)
972 else:
972 else:
973 f = self(dst, "w")
973 f = self(dst, "w")
974 f.write(src)
974 f.write(src)
975 f.close()
975 f.close()
976 self._fixfilemode(dst)
976 self._fixfilemode(dst)
977
977
978 class chunkbuffer(object):
978 class chunkbuffer(object):
979 """Allow arbitrary sized chunks of data to be efficiently read from an
979 """Allow arbitrary sized chunks of data to be efficiently read from an
980 iterator over chunks of arbitrary size."""
980 iterator over chunks of arbitrary size."""
981
981
982 def __init__(self, in_iter):
982 def __init__(self, in_iter):
983 """in_iter is the iterator that's iterating over the input chunks.
983 """in_iter is the iterator that's iterating over the input chunks.
984 targetsize is how big a buffer to try to maintain."""
984 targetsize is how big a buffer to try to maintain."""
985 def splitbig(chunks):
985 def splitbig(chunks):
986 for chunk in chunks:
986 for chunk in chunks:
987 if len(chunk) > 2**20:
987 if len(chunk) > 2**20:
988 pos = 0
988 pos = 0
989 while pos < len(chunk):
989 while pos < len(chunk):
990 end = pos + 2 ** 18
990 end = pos + 2 ** 18
991 yield chunk[pos:end]
991 yield chunk[pos:end]
992 pos = end
992 pos = end
993 else:
993 else:
994 yield chunk
994 yield chunk
995 self.iter = splitbig(in_iter)
995 self.iter = splitbig(in_iter)
996 self._queue = []
996 self._queue = []
997
997
998 def read(self, l):
998 def read(self, l):
999 """Read L bytes of data from the iterator of chunks of data.
999 """Read L bytes of data from the iterator of chunks of data.
1000 Returns less than L bytes if the iterator runs dry."""
1000 Returns less than L bytes if the iterator runs dry."""
1001 left = l
1001 left = l
1002 buf = ''
1002 buf = ''
1003 queue = self._queue
1003 queue = self._queue
1004 while left > 0:
1004 while left > 0:
1005 # refill the queue
1005 # refill the queue
1006 if not queue:
1006 if not queue:
1007 target = 2**18
1007 target = 2**18
1008 for chunk in self.iter:
1008 for chunk in self.iter:
1009 queue.append(chunk)
1009 queue.append(chunk)
1010 target -= len(chunk)
1010 target -= len(chunk)
1011 if target <= 0:
1011 if target <= 0:
1012 break
1012 break
1013 if not queue:
1013 if not queue:
1014 break
1014 break
1015
1015
1016 chunk = queue.pop(0)
1016 chunk = queue.pop(0)
1017 left -= len(chunk)
1017 left -= len(chunk)
1018 if left < 0:
1018 if left < 0:
1019 queue.insert(0, chunk[left:])
1019 queue.insert(0, chunk[left:])
1020 buf += chunk[:left]
1020 buf += chunk[:left]
1021 else:
1021 else:
1022 buf += chunk
1022 buf += chunk
1023
1023
1024 return buf
1024 return buf
1025
1025
1026 def filechunkiter(f, size=65536, limit=None):
1026 def filechunkiter(f, size=65536, limit=None):
1027 """Create a generator that produces the data in the file size
1027 """Create a generator that produces the data in the file size
1028 (default 65536) bytes at a time, up to optional limit (default is
1028 (default 65536) bytes at a time, up to optional limit (default is
1029 to read all data). Chunks may be less than size bytes if the
1029 to read all data). Chunks may be less than size bytes if the
1030 chunk is the last chunk in the file, or the file is a socket or
1030 chunk is the last chunk in the file, or the file is a socket or
1031 some other type of file that sometimes reads less data than is
1031 some other type of file that sometimes reads less data than is
1032 requested."""
1032 requested."""
1033 assert size >= 0
1033 assert size >= 0
1034 assert limit is None or limit >= 0
1034 assert limit is None or limit >= 0
1035 while True:
1035 while True:
1036 if limit is None:
1036 if limit is None:
1037 nbytes = size
1037 nbytes = size
1038 else:
1038 else:
1039 nbytes = min(limit, size)
1039 nbytes = min(limit, size)
1040 s = nbytes and f.read(nbytes)
1040 s = nbytes and f.read(nbytes)
1041 if not s:
1041 if not s:
1042 break
1042 break
1043 if limit:
1043 if limit:
1044 limit -= len(s)
1044 limit -= len(s)
1045 yield s
1045 yield s
1046
1046
1047 def makedate():
1047 def makedate():
1048 lt = time.localtime()
1048 lt = time.localtime()
1049 if lt[8] == 1 and time.daylight:
1049 if lt[8] == 1 and time.daylight:
1050 tz = time.altzone
1050 tz = time.altzone
1051 else:
1051 else:
1052 tz = time.timezone
1052 tz = time.timezone
1053 t = time.mktime(lt)
1053 t = time.mktime(lt)
1054 if t < 0:
1054 if t < 0:
1055 hint = _("check your clock")
1055 hint = _("check your clock")
1056 raise Abort(_("negative timestamp: %d") % t, hint=hint)
1056 raise Abort(_("negative timestamp: %d") % t, hint=hint)
1057 return t, tz
1057 return t, tz
1058
1058
1059 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1059 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1060 """represent a (unixtime, offset) tuple as a localized time.
1060 """represent a (unixtime, offset) tuple as a localized time.
1061 unixtime is seconds since the epoch, and offset is the time zone's
1061 unixtime is seconds since the epoch, and offset is the time zone's
1062 number of seconds away from UTC. if timezone is false, do not
1062 number of seconds away from UTC. if timezone is false, do not
1063 append time zone to string."""
1063 append time zone to string."""
1064 t, tz = date or makedate()
1064 t, tz = date or makedate()
1065 if t < 0:
1065 if t < 0:
1066 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1066 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1067 tz = 0
1067 tz = 0
1068 if "%1" in format or "%2" in format:
1068 if "%1" in format or "%2" in format:
1069 sign = (tz > 0) and "-" or "+"
1069 sign = (tz > 0) and "-" or "+"
1070 minutes = abs(tz) // 60
1070 minutes = abs(tz) // 60
1071 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1071 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1072 format = format.replace("%2", "%02d" % (minutes % 60))
1072 format = format.replace("%2", "%02d" % (minutes % 60))
1073 s = time.strftime(format, time.gmtime(float(t) - tz))
1073 s = time.strftime(format, time.gmtime(float(t) - tz))
1074 return s
1074 return s
1075
1075
1076 def shortdate(date=None):
1076 def shortdate(date=None):
1077 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1077 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1078 return datestr(date, format='%Y-%m-%d')
1078 return datestr(date, format='%Y-%m-%d')
1079
1079
1080 def strdate(string, format, defaults=[]):
1080 def strdate(string, format, defaults=[]):
1081 """parse a localized time string and return a (unixtime, offset) tuple.
1081 """parse a localized time string and return a (unixtime, offset) tuple.
1082 if the string cannot be parsed, ValueError is raised."""
1082 if the string cannot be parsed, ValueError is raised."""
1083 def timezone(string):
1083 def timezone(string):
1084 tz = string.split()[-1]
1084 tz = string.split()[-1]
1085 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1085 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1086 sign = (tz[0] == "+") and 1 or -1
1086 sign = (tz[0] == "+") and 1 or -1
1087 hours = int(tz[1:3])
1087 hours = int(tz[1:3])
1088 minutes = int(tz[3:5])
1088 minutes = int(tz[3:5])
1089 return -sign * (hours * 60 + minutes) * 60
1089 return -sign * (hours * 60 + minutes) * 60
1090 if tz == "GMT" or tz == "UTC":
1090 if tz == "GMT" or tz == "UTC":
1091 return 0
1091 return 0
1092 return None
1092 return None
1093
1093
1094 # NOTE: unixtime = localunixtime + offset
1094 # NOTE: unixtime = localunixtime + offset
1095 offset, date = timezone(string), string
1095 offset, date = timezone(string), string
1096 if offset is not None:
1096 if offset is not None:
1097 date = " ".join(string.split()[:-1])
1097 date = " ".join(string.split()[:-1])
1098
1098
1099 # add missing elements from defaults
1099 # add missing elements from defaults
1100 usenow = False # default to using biased defaults
1100 usenow = False # default to using biased defaults
1101 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1101 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1102 found = [True for p in part if ("%"+p) in format]
1102 found = [True for p in part if ("%"+p) in format]
1103 if not found:
1103 if not found:
1104 date += "@" + defaults[part][usenow]
1104 date += "@" + defaults[part][usenow]
1105 format += "@%" + part[0]
1105 format += "@%" + part[0]
1106 else:
1106 else:
1107 # We've found a specific time element, less specific time
1107 # We've found a specific time element, less specific time
1108 # elements are relative to today
1108 # elements are relative to today
1109 usenow = True
1109 usenow = True
1110
1110
1111 timetuple = time.strptime(date, format)
1111 timetuple = time.strptime(date, format)
1112 localunixtime = int(calendar.timegm(timetuple))
1112 localunixtime = int(calendar.timegm(timetuple))
1113 if offset is None:
1113 if offset is None:
1114 # local timezone
1114 # local timezone
1115 unixtime = int(time.mktime(timetuple))
1115 unixtime = int(time.mktime(timetuple))
1116 offset = unixtime - localunixtime
1116 offset = unixtime - localunixtime
1117 else:
1117 else:
1118 unixtime = localunixtime + offset
1118 unixtime = localunixtime + offset
1119 return unixtime, offset
1119 return unixtime, offset
1120
1120
1121 def parsedate(date, formats=None, bias={}):
1121 def parsedate(date, formats=None, bias={}):
1122 """parse a localized date/time and return a (unixtime, offset) tuple.
1122 """parse a localized date/time and return a (unixtime, offset) tuple.
1123
1123
1124 The date may be a "unixtime offset" string or in one of the specified
1124 The date may be a "unixtime offset" string or in one of the specified
1125 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1125 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1126 """
1126 """
1127 if not date:
1127 if not date:
1128 return 0, 0
1128 return 0, 0
1129 if isinstance(date, tuple) and len(date) == 2:
1129 if isinstance(date, tuple) and len(date) == 2:
1130 return date
1130 return date
1131 if not formats:
1131 if not formats:
1132 formats = defaultdateformats
1132 formats = defaultdateformats
1133 date = date.strip()
1133 date = date.strip()
1134 try:
1134 try:
1135 when, offset = map(int, date.split(' '))
1135 when, offset = map(int, date.split(' '))
1136 except ValueError:
1136 except ValueError:
1137 # fill out defaults
1137 # fill out defaults
1138 now = makedate()
1138 now = makedate()
1139 defaults = {}
1139 defaults = {}
1140 nowmap = {}
1140 nowmap = {}
1141 for part in ("d", "mb", "yY", "HI", "M", "S"):
1141 for part in ("d", "mb", "yY", "HI", "M", "S"):
1142 # this piece is for rounding the specific end of unknowns
1142 # this piece is for rounding the specific end of unknowns
1143 b = bias.get(part)
1143 b = bias.get(part)
1144 if b is None:
1144 if b is None:
1145 if part[0] in "HMS":
1145 if part[0] in "HMS":
1146 b = "00"
1146 b = "00"
1147 else:
1147 else:
1148 b = "0"
1148 b = "0"
1149
1149
1150 # this piece is for matching the generic end to today's date
1150 # this piece is for matching the generic end to today's date
1151 n = datestr(now, "%" + part[0])
1151 n = datestr(now, "%" + part[0])
1152
1152
1153 defaults[part] = (b, n)
1153 defaults[part] = (b, n)
1154
1154
1155 for format in formats:
1155 for format in formats:
1156 try:
1156 try:
1157 when, offset = strdate(date, format, defaults)
1157 when, offset = strdate(date, format, defaults)
1158 except (ValueError, OverflowError):
1158 except (ValueError, OverflowError):
1159 pass
1159 pass
1160 else:
1160 else:
1161 break
1161 break
1162 else:
1162 else:
1163 raise Abort(_('invalid date: %r') % date)
1163 raise Abort(_('invalid date: %r') % date)
1164 # validate explicit (probably user-specified) date and
1164 # validate explicit (probably user-specified) date and
1165 # time zone offset. values must fit in signed 32 bits for
1165 # time zone offset. values must fit in signed 32 bits for
1166 # current 32-bit linux runtimes. timezones go from UTC-12
1166 # current 32-bit linux runtimes. timezones go from UTC-12
1167 # to UTC+14
1167 # to UTC+14
1168 if abs(when) > 0x7fffffff:
1168 if abs(when) > 0x7fffffff:
1169 raise Abort(_('date exceeds 32 bits: %d') % when)
1169 raise Abort(_('date exceeds 32 bits: %d') % when)
1170 if when < 0:
1170 if when < 0:
1171 raise Abort(_('negative date value: %d') % when)
1171 raise Abort(_('negative date value: %d') % when)
1172 if offset < -50400 or offset > 43200:
1172 if offset < -50400 or offset > 43200:
1173 raise Abort(_('impossible time zone offset: %d') % offset)
1173 raise Abort(_('impossible time zone offset: %d') % offset)
1174 return when, offset
1174 return when, offset
1175
1175
1176 def matchdate(date):
1176 def matchdate(date):
1177 """Return a function that matches a given date match specifier
1177 """Return a function that matches a given date match specifier
1178
1178
1179 Formats include:
1179 Formats include:
1180
1180
1181 '{date}' match a given date to the accuracy provided
1181 '{date}' match a given date to the accuracy provided
1182
1182
1183 '<{date}' on or before a given date
1183 '<{date}' on or before a given date
1184
1184
1185 '>{date}' on or after a given date
1185 '>{date}' on or after a given date
1186
1186
1187 >>> p1 = parsedate("10:29:59")
1187 >>> p1 = parsedate("10:29:59")
1188 >>> p2 = parsedate("10:30:00")
1188 >>> p2 = parsedate("10:30:00")
1189 >>> p3 = parsedate("10:30:59")
1189 >>> p3 = parsedate("10:30:59")
1190 >>> p4 = parsedate("10:31:00")
1190 >>> p4 = parsedate("10:31:00")
1191 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1191 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1192 >>> f = matchdate("10:30")
1192 >>> f = matchdate("10:30")
1193 >>> f(p1[0])
1193 >>> f(p1[0])
1194 False
1194 False
1195 >>> f(p2[0])
1195 >>> f(p2[0])
1196 True
1196 True
1197 >>> f(p3[0])
1197 >>> f(p3[0])
1198 True
1198 True
1199 >>> f(p4[0])
1199 >>> f(p4[0])
1200 False
1200 False
1201 >>> f(p5[0])
1201 >>> f(p5[0])
1202 False
1202 False
1203 """
1203 """
1204
1204
1205 def lower(date):
1205 def lower(date):
1206 d = dict(mb="1", d="1")
1206 d = dict(mb="1", d="1")
1207 return parsedate(date, extendeddateformats, d)[0]
1207 return parsedate(date, extendeddateformats, d)[0]
1208
1208
1209 def upper(date):
1209 def upper(date):
1210 d = dict(mb="12", HI="23", M="59", S="59")
1210 d = dict(mb="12", HI="23", M="59", S="59")
1211 for days in ("31", "30", "29"):
1211 for days in ("31", "30", "29"):
1212 try:
1212 try:
1213 d["d"] = days
1213 d["d"] = days
1214 return parsedate(date, extendeddateformats, d)[0]
1214 return parsedate(date, extendeddateformats, d)[0]
1215 except:
1215 except:
1216 pass
1216 pass
1217 d["d"] = "28"
1217 d["d"] = "28"
1218 return parsedate(date, extendeddateformats, d)[0]
1218 return parsedate(date, extendeddateformats, d)[0]
1219
1219
1220 date = date.strip()
1220 date = date.strip()
1221
1221
1222 if not date:
1222 if not date:
1223 raise Abort(_("dates cannot consist entirely of whitespace"))
1223 raise Abort(_("dates cannot consist entirely of whitespace"))
1224 elif date[0] == "<":
1224 elif date[0] == "<":
1225 if not date[1:]:
1226 raise Abort(_("invalid day spec. use '<{datetime}' "))
1225 when = upper(date[1:])
1227 when = upper(date[1:])
1226 return lambda x: x <= when
1228 return lambda x: x <= when
1227 elif date[0] == ">":
1229 elif date[0] == ">":
1230 if not date[1:]:
1231 raise Abort(_("invalid day spec. use '>{datetime}' "))
1228 when = lower(date[1:])
1232 when = lower(date[1:])
1229 return lambda x: x >= when
1233 return lambda x: x >= when
1230 elif date[0] == "-":
1234 elif date[0] == "-":
1231 try:
1235 try:
1232 days = int(date[1:])
1236 days = int(date[1:])
1233 except ValueError:
1237 except ValueError:
1234 raise Abort(_("invalid day spec: %s") % date[1:])
1238 raise Abort(_("invalid day spec: %s") % date[1:])
1235 when = makedate()[0] - days * 3600 * 24
1239 when = makedate()[0] - days * 3600 * 24
1236 return lambda x: x >= when
1240 return lambda x: x >= when
1237 elif " to " in date:
1241 elif " to " in date:
1238 a, b = date.split(" to ")
1242 a, b = date.split(" to ")
1239 start, stop = lower(a), upper(b)
1243 start, stop = lower(a), upper(b)
1240 return lambda x: x >= start and x <= stop
1244 return lambda x: x >= start and x <= stop
1241 else:
1245 else:
1242 start, stop = lower(date), upper(date)
1246 start, stop = lower(date), upper(date)
1243 return lambda x: x >= start and x <= stop
1247 return lambda x: x >= start and x <= stop
1244
1248
1245 def shortuser(user):
1249 def shortuser(user):
1246 """Return a short representation of a user name or email address."""
1250 """Return a short representation of a user name or email address."""
1247 f = user.find('@')
1251 f = user.find('@')
1248 if f >= 0:
1252 if f >= 0:
1249 user = user[:f]
1253 user = user[:f]
1250 f = user.find('<')
1254 f = user.find('<')
1251 if f >= 0:
1255 if f >= 0:
1252 user = user[f + 1:]
1256 user = user[f + 1:]
1253 f = user.find(' ')
1257 f = user.find(' ')
1254 if f >= 0:
1258 if f >= 0:
1255 user = user[:f]
1259 user = user[:f]
1256 f = user.find('.')
1260 f = user.find('.')
1257 if f >= 0:
1261 if f >= 0:
1258 user = user[:f]
1262 user = user[:f]
1259 return user
1263 return user
1260
1264
1261 def email(author):
1265 def email(author):
1262 '''get email of author.'''
1266 '''get email of author.'''
1263 r = author.find('>')
1267 r = author.find('>')
1264 if r == -1:
1268 if r == -1:
1265 r = None
1269 r = None
1266 return author[author.find('<') + 1:r]
1270 return author[author.find('<') + 1:r]
1267
1271
1268 def _ellipsis(text, maxlength):
1272 def _ellipsis(text, maxlength):
1269 if len(text) <= maxlength:
1273 if len(text) <= maxlength:
1270 return text, False
1274 return text, False
1271 else:
1275 else:
1272 return "%s..." % (text[:maxlength - 3]), True
1276 return "%s..." % (text[:maxlength - 3]), True
1273
1277
1274 def ellipsis(text, maxlength=400):
1278 def ellipsis(text, maxlength=400):
1275 """Trim string to at most maxlength (default: 400) characters."""
1279 """Trim string to at most maxlength (default: 400) characters."""
1276 try:
1280 try:
1277 # use unicode not to split at intermediate multi-byte sequence
1281 # use unicode not to split at intermediate multi-byte sequence
1278 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1282 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1279 maxlength)
1283 maxlength)
1280 if not truncated:
1284 if not truncated:
1281 return text
1285 return text
1282 return utext.encode(encoding.encoding)
1286 return utext.encode(encoding.encoding)
1283 except (UnicodeDecodeError, UnicodeEncodeError):
1287 except (UnicodeDecodeError, UnicodeEncodeError):
1284 return _ellipsis(text, maxlength)[0]
1288 return _ellipsis(text, maxlength)[0]
1285
1289
1286 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1290 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1287 '''yield every hg repository under path, recursively.'''
1291 '''yield every hg repository under path, recursively.'''
1288 def errhandler(err):
1292 def errhandler(err):
1289 if err.filename == path:
1293 if err.filename == path:
1290 raise err
1294 raise err
1291 if followsym and hasattr(os.path, 'samestat'):
1295 if followsym and hasattr(os.path, 'samestat'):
1292 def _add_dir_if_not_there(dirlst, dirname):
1296 def _add_dir_if_not_there(dirlst, dirname):
1293 match = False
1297 match = False
1294 samestat = os.path.samestat
1298 samestat = os.path.samestat
1295 dirstat = os.stat(dirname)
1299 dirstat = os.stat(dirname)
1296 for lstdirstat in dirlst:
1300 for lstdirstat in dirlst:
1297 if samestat(dirstat, lstdirstat):
1301 if samestat(dirstat, lstdirstat):
1298 match = True
1302 match = True
1299 break
1303 break
1300 if not match:
1304 if not match:
1301 dirlst.append(dirstat)
1305 dirlst.append(dirstat)
1302 return not match
1306 return not match
1303 else:
1307 else:
1304 followsym = False
1308 followsym = False
1305
1309
1306 if (seen_dirs is None) and followsym:
1310 if (seen_dirs is None) and followsym:
1307 seen_dirs = []
1311 seen_dirs = []
1308 _add_dir_if_not_there(seen_dirs, path)
1312 _add_dir_if_not_there(seen_dirs, path)
1309 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1313 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1310 dirs.sort()
1314 dirs.sort()
1311 if '.hg' in dirs:
1315 if '.hg' in dirs:
1312 yield root # found a repository
1316 yield root # found a repository
1313 qroot = os.path.join(root, '.hg', 'patches')
1317 qroot = os.path.join(root, '.hg', 'patches')
1314 if os.path.isdir(os.path.join(qroot, '.hg')):
1318 if os.path.isdir(os.path.join(qroot, '.hg')):
1315 yield qroot # we have a patch queue repo here
1319 yield qroot # we have a patch queue repo here
1316 if recurse:
1320 if recurse:
1317 # avoid recursing inside the .hg directory
1321 # avoid recursing inside the .hg directory
1318 dirs.remove('.hg')
1322 dirs.remove('.hg')
1319 else:
1323 else:
1320 dirs[:] = [] # don't descend further
1324 dirs[:] = [] # don't descend further
1321 elif followsym:
1325 elif followsym:
1322 newdirs = []
1326 newdirs = []
1323 for d in dirs:
1327 for d in dirs:
1324 fname = os.path.join(root, d)
1328 fname = os.path.join(root, d)
1325 if _add_dir_if_not_there(seen_dirs, fname):
1329 if _add_dir_if_not_there(seen_dirs, fname):
1326 if os.path.islink(fname):
1330 if os.path.islink(fname):
1327 for hgname in walkrepos(fname, True, seen_dirs):
1331 for hgname in walkrepos(fname, True, seen_dirs):
1328 yield hgname
1332 yield hgname
1329 else:
1333 else:
1330 newdirs.append(d)
1334 newdirs.append(d)
1331 dirs[:] = newdirs
1335 dirs[:] = newdirs
1332
1336
1333 _rcpath = None
1337 _rcpath = None
1334
1338
1335 def os_rcpath():
1339 def os_rcpath():
1336 '''return default os-specific hgrc search path'''
1340 '''return default os-specific hgrc search path'''
1337 path = system_rcpath()
1341 path = system_rcpath()
1338 path.extend(user_rcpath())
1342 path.extend(user_rcpath())
1339 path = [os.path.normpath(f) for f in path]
1343 path = [os.path.normpath(f) for f in path]
1340 return path
1344 return path
1341
1345
1342 def rcpath():
1346 def rcpath():
1343 '''return hgrc search path. if env var HGRCPATH is set, use it.
1347 '''return hgrc search path. if env var HGRCPATH is set, use it.
1344 for each item in path, if directory, use files ending in .rc,
1348 for each item in path, if directory, use files ending in .rc,
1345 else use item.
1349 else use item.
1346 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1350 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1347 if no HGRCPATH, use default os-specific path.'''
1351 if no HGRCPATH, use default os-specific path.'''
1348 global _rcpath
1352 global _rcpath
1349 if _rcpath is None:
1353 if _rcpath is None:
1350 if 'HGRCPATH' in os.environ:
1354 if 'HGRCPATH' in os.environ:
1351 _rcpath = []
1355 _rcpath = []
1352 for p in os.environ['HGRCPATH'].split(os.pathsep):
1356 for p in os.environ['HGRCPATH'].split(os.pathsep):
1353 if not p:
1357 if not p:
1354 continue
1358 continue
1355 p = expandpath(p)
1359 p = expandpath(p)
1356 if os.path.isdir(p):
1360 if os.path.isdir(p):
1357 for f, kind in osutil.listdir(p):
1361 for f, kind in osutil.listdir(p):
1358 if f.endswith('.rc'):
1362 if f.endswith('.rc'):
1359 _rcpath.append(os.path.join(p, f))
1363 _rcpath.append(os.path.join(p, f))
1360 else:
1364 else:
1361 _rcpath.append(p)
1365 _rcpath.append(p)
1362 else:
1366 else:
1363 _rcpath = os_rcpath()
1367 _rcpath = os_rcpath()
1364 return _rcpath
1368 return _rcpath
1365
1369
1366 def bytecount(nbytes):
1370 def bytecount(nbytes):
1367 '''return byte count formatted as readable string, with units'''
1371 '''return byte count formatted as readable string, with units'''
1368
1372
1369 units = (
1373 units = (
1370 (100, 1 << 30, _('%.0f GB')),
1374 (100, 1 << 30, _('%.0f GB')),
1371 (10, 1 << 30, _('%.1f GB')),
1375 (10, 1 << 30, _('%.1f GB')),
1372 (1, 1 << 30, _('%.2f GB')),
1376 (1, 1 << 30, _('%.2f GB')),
1373 (100, 1 << 20, _('%.0f MB')),
1377 (100, 1 << 20, _('%.0f MB')),
1374 (10, 1 << 20, _('%.1f MB')),
1378 (10, 1 << 20, _('%.1f MB')),
1375 (1, 1 << 20, _('%.2f MB')),
1379 (1, 1 << 20, _('%.2f MB')),
1376 (100, 1 << 10, _('%.0f KB')),
1380 (100, 1 << 10, _('%.0f KB')),
1377 (10, 1 << 10, _('%.1f KB')),
1381 (10, 1 << 10, _('%.1f KB')),
1378 (1, 1 << 10, _('%.2f KB')),
1382 (1, 1 << 10, _('%.2f KB')),
1379 (1, 1, _('%.0f bytes')),
1383 (1, 1, _('%.0f bytes')),
1380 )
1384 )
1381
1385
1382 for multiplier, divisor, format in units:
1386 for multiplier, divisor, format in units:
1383 if nbytes >= divisor * multiplier:
1387 if nbytes >= divisor * multiplier:
1384 return format % (nbytes / float(divisor))
1388 return format % (nbytes / float(divisor))
1385 return units[-1][2] % nbytes
1389 return units[-1][2] % nbytes
1386
1390
1387 def uirepr(s):
1391 def uirepr(s):
1388 # Avoid double backslash in Windows path repr()
1392 # Avoid double backslash in Windows path repr()
1389 return repr(s).replace('\\\\', '\\')
1393 return repr(s).replace('\\\\', '\\')
1390
1394
1391 # delay import of textwrap
1395 # delay import of textwrap
1392 def MBTextWrapper(**kwargs):
1396 def MBTextWrapper(**kwargs):
1393 class tw(textwrap.TextWrapper):
1397 class tw(textwrap.TextWrapper):
1394 """
1398 """
1395 Extend TextWrapper for double-width characters.
1399 Extend TextWrapper for double-width characters.
1396
1400
1397 Some Asian characters use two terminal columns instead of one.
1401 Some Asian characters use two terminal columns instead of one.
1398 A good example of this behavior can be seen with u'\u65e5\u672c',
1402 A good example of this behavior can be seen with u'\u65e5\u672c',
1399 the two Japanese characters for "Japan":
1403 the two Japanese characters for "Japan":
1400 len() returns 2, but when printed to a terminal, they eat 4 columns.
1404 len() returns 2, but when printed to a terminal, they eat 4 columns.
1401
1405
1402 (Note that this has nothing to do whatsoever with unicode
1406 (Note that this has nothing to do whatsoever with unicode
1403 representation, or encoding of the underlying string)
1407 representation, or encoding of the underlying string)
1404 """
1408 """
1405 def __init__(self, **kwargs):
1409 def __init__(self, **kwargs):
1406 textwrap.TextWrapper.__init__(self, **kwargs)
1410 textwrap.TextWrapper.__init__(self, **kwargs)
1407
1411
1408 def _cutdown(self, str, space_left):
1412 def _cutdown(self, str, space_left):
1409 l = 0
1413 l = 0
1410 ucstr = unicode(str, encoding.encoding)
1414 ucstr = unicode(str, encoding.encoding)
1411 colwidth = unicodedata.east_asian_width
1415 colwidth = unicodedata.east_asian_width
1412 for i in xrange(len(ucstr)):
1416 for i in xrange(len(ucstr)):
1413 l += colwidth(ucstr[i]) in 'WFA' and 2 or 1
1417 l += colwidth(ucstr[i]) in 'WFA' and 2 or 1
1414 if space_left < l:
1418 if space_left < l:
1415 return (ucstr[:i].encode(encoding.encoding),
1419 return (ucstr[:i].encode(encoding.encoding),
1416 ucstr[i:].encode(encoding.encoding))
1420 ucstr[i:].encode(encoding.encoding))
1417 return str, ''
1421 return str, ''
1418
1422
1419 # overriding of base class
1423 # overriding of base class
1420 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1424 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1421 space_left = max(width - cur_len, 1)
1425 space_left = max(width - cur_len, 1)
1422
1426
1423 if self.break_long_words:
1427 if self.break_long_words:
1424 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1428 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1425 cur_line.append(cut)
1429 cur_line.append(cut)
1426 reversed_chunks[-1] = res
1430 reversed_chunks[-1] = res
1427 elif not cur_line:
1431 elif not cur_line:
1428 cur_line.append(reversed_chunks.pop())
1432 cur_line.append(reversed_chunks.pop())
1429
1433
1430 global MBTextWrapper
1434 global MBTextWrapper
1431 MBTextWrapper = tw
1435 MBTextWrapper = tw
1432 return tw(**kwargs)
1436 return tw(**kwargs)
1433
1437
1434 def wrap(line, width, initindent='', hangindent=''):
1438 def wrap(line, width, initindent='', hangindent=''):
1435 maxindent = max(len(hangindent), len(initindent))
1439 maxindent = max(len(hangindent), len(initindent))
1436 if width <= maxindent:
1440 if width <= maxindent:
1437 # adjust for weird terminal size
1441 # adjust for weird terminal size
1438 width = max(78, maxindent + 1)
1442 width = max(78, maxindent + 1)
1439 wrapper = MBTextWrapper(width=width,
1443 wrapper = MBTextWrapper(width=width,
1440 initial_indent=initindent,
1444 initial_indent=initindent,
1441 subsequent_indent=hangindent)
1445 subsequent_indent=hangindent)
1442 return wrapper.fill(line)
1446 return wrapper.fill(line)
1443
1447
1444 def iterlines(iterator):
1448 def iterlines(iterator):
1445 for chunk in iterator:
1449 for chunk in iterator:
1446 for line in chunk.splitlines():
1450 for line in chunk.splitlines():
1447 yield line
1451 yield line
1448
1452
1449 def expandpath(path):
1453 def expandpath(path):
1450 return os.path.expanduser(os.path.expandvars(path))
1454 return os.path.expanduser(os.path.expandvars(path))
1451
1455
1452 def hgcmd():
1456 def hgcmd():
1453 """Return the command used to execute current hg
1457 """Return the command used to execute current hg
1454
1458
1455 This is different from hgexecutable() because on Windows we want
1459 This is different from hgexecutable() because on Windows we want
1456 to avoid things opening new shell windows like batch files, so we
1460 to avoid things opening new shell windows like batch files, so we
1457 get either the python call or current executable.
1461 get either the python call or current executable.
1458 """
1462 """
1459 if main_is_frozen():
1463 if main_is_frozen():
1460 return [sys.executable]
1464 return [sys.executable]
1461 return gethgcmd()
1465 return gethgcmd()
1462
1466
1463 def rundetached(args, condfn):
1467 def rundetached(args, condfn):
1464 """Execute the argument list in a detached process.
1468 """Execute the argument list in a detached process.
1465
1469
1466 condfn is a callable which is called repeatedly and should return
1470 condfn is a callable which is called repeatedly and should return
1467 True once the child process is known to have started successfully.
1471 True once the child process is known to have started successfully.
1468 At this point, the child process PID is returned. If the child
1472 At this point, the child process PID is returned. If the child
1469 process fails to start or finishes before condfn() evaluates to
1473 process fails to start or finishes before condfn() evaluates to
1470 True, return -1.
1474 True, return -1.
1471 """
1475 """
1472 # Windows case is easier because the child process is either
1476 # Windows case is easier because the child process is either
1473 # successfully starting and validating the condition or exiting
1477 # successfully starting and validating the condition or exiting
1474 # on failure. We just poll on its PID. On Unix, if the child
1478 # on failure. We just poll on its PID. On Unix, if the child
1475 # process fails to start, it will be left in a zombie state until
1479 # process fails to start, it will be left in a zombie state until
1476 # the parent wait on it, which we cannot do since we expect a long
1480 # the parent wait on it, which we cannot do since we expect a long
1477 # running process on success. Instead we listen for SIGCHLD telling
1481 # running process on success. Instead we listen for SIGCHLD telling
1478 # us our child process terminated.
1482 # us our child process terminated.
1479 terminated = set()
1483 terminated = set()
1480 def handler(signum, frame):
1484 def handler(signum, frame):
1481 terminated.add(os.wait())
1485 terminated.add(os.wait())
1482 prevhandler = None
1486 prevhandler = None
1483 if hasattr(signal, 'SIGCHLD'):
1487 if hasattr(signal, 'SIGCHLD'):
1484 prevhandler = signal.signal(signal.SIGCHLD, handler)
1488 prevhandler = signal.signal(signal.SIGCHLD, handler)
1485 try:
1489 try:
1486 pid = spawndetached(args)
1490 pid = spawndetached(args)
1487 while not condfn():
1491 while not condfn():
1488 if ((pid in terminated or not testpid(pid))
1492 if ((pid in terminated or not testpid(pid))
1489 and not condfn()):
1493 and not condfn()):
1490 return -1
1494 return -1
1491 time.sleep(0.1)
1495 time.sleep(0.1)
1492 return pid
1496 return pid
1493 finally:
1497 finally:
1494 if prevhandler is not None:
1498 if prevhandler is not None:
1495 signal.signal(signal.SIGCHLD, prevhandler)
1499 signal.signal(signal.SIGCHLD, prevhandler)
1496
1500
1497 try:
1501 try:
1498 any, all = any, all
1502 any, all = any, all
1499 except NameError:
1503 except NameError:
1500 def any(iterable):
1504 def any(iterable):
1501 for i in iterable:
1505 for i in iterable:
1502 if i:
1506 if i:
1503 return True
1507 return True
1504 return False
1508 return False
1505
1509
1506 def all(iterable):
1510 def all(iterable):
1507 for i in iterable:
1511 for i in iterable:
1508 if not i:
1512 if not i:
1509 return False
1513 return False
1510 return True
1514 return True
1511
1515
1512 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1516 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1513 """Return the result of interpolating items in the mapping into string s.
1517 """Return the result of interpolating items in the mapping into string s.
1514
1518
1515 prefix is a single character string, or a two character string with
1519 prefix is a single character string, or a two character string with
1516 a backslash as the first character if the prefix needs to be escaped in
1520 a backslash as the first character if the prefix needs to be escaped in
1517 a regular expression.
1521 a regular expression.
1518
1522
1519 fn is an optional function that will be applied to the replacement text
1523 fn is an optional function that will be applied to the replacement text
1520 just before replacement.
1524 just before replacement.
1521
1525
1522 escape_prefix is an optional flag that allows using doubled prefix for
1526 escape_prefix is an optional flag that allows using doubled prefix for
1523 its escaping.
1527 its escaping.
1524 """
1528 """
1525 fn = fn or (lambda s: s)
1529 fn = fn or (lambda s: s)
1526 patterns = '|'.join(mapping.keys())
1530 patterns = '|'.join(mapping.keys())
1527 if escape_prefix:
1531 if escape_prefix:
1528 patterns += '|' + prefix
1532 patterns += '|' + prefix
1529 if len(prefix) > 1:
1533 if len(prefix) > 1:
1530 prefix_char = prefix[1:]
1534 prefix_char = prefix[1:]
1531 else:
1535 else:
1532 prefix_char = prefix
1536 prefix_char = prefix
1533 mapping[prefix_char] = prefix_char
1537 mapping[prefix_char] = prefix_char
1534 r = re.compile(r'%s(%s)' % (prefix, patterns))
1538 r = re.compile(r'%s(%s)' % (prefix, patterns))
1535 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1539 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1536
1540
1537 def getport(port):
1541 def getport(port):
1538 """Return the port for a given network service.
1542 """Return the port for a given network service.
1539
1543
1540 If port is an integer, it's returned as is. If it's a string, it's
1544 If port is an integer, it's returned as is. If it's a string, it's
1541 looked up using socket.getservbyname(). If there's no matching
1545 looked up using socket.getservbyname(). If there's no matching
1542 service, util.Abort is raised.
1546 service, util.Abort is raised.
1543 """
1547 """
1544 try:
1548 try:
1545 return int(port)
1549 return int(port)
1546 except ValueError:
1550 except ValueError:
1547 pass
1551 pass
1548
1552
1549 try:
1553 try:
1550 return socket.getservbyname(port)
1554 return socket.getservbyname(port)
1551 except socket.error:
1555 except socket.error:
1552 raise Abort(_("no port number associated with service '%s'") % port)
1556 raise Abort(_("no port number associated with service '%s'") % port)
1553
1557
1554 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1558 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1555 '0': False, 'no': False, 'false': False, 'off': False,
1559 '0': False, 'no': False, 'false': False, 'off': False,
1556 'never': False}
1560 'never': False}
1557
1561
1558 def parsebool(s):
1562 def parsebool(s):
1559 """Parse s into a boolean.
1563 """Parse s into a boolean.
1560
1564
1561 If s is not a valid boolean, returns None.
1565 If s is not a valid boolean, returns None.
1562 """
1566 """
1563 return _booleans.get(s.lower(), None)
1567 return _booleans.get(s.lower(), None)
@@ -1,1123 +1,1135 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 log -d " " (whitespaces only)
516
516
517 $ hg log -d " "
517 $ hg log -d " "
518 abort: dates cannot consist entirely of whitespace
518 abort: dates cannot consist entirely of whitespace
519 [255]
519 [255]
520
520
521 log -d -1
521 log -d -1
522
522
523 $ hg log -d -1
523 $ hg log -d -1
524
524
525 log -d ">"
526
527 $ hg log -d ">"
528 abort: invalid day spec. use '>{datetime}'
529 [255]
530
531 log -d "<"
532
533 $ hg log -d "<"
534 abort: invalid day spec. use '<{datetime}'
535 [255]
536
525
537
526 log -p -l2 --color=always
538 log -p -l2 --color=always
527
539
528 $ hg --config extensions.color= --config color.mode=ansi \
540 $ hg --config extensions.color= --config color.mode=ansi \
529 > log -p -l2 --color=always
541 > log -p -l2 --color=always
530 \x1b[0;33mchangeset: 6:2404bbcab562\x1b[0m (esc)
542 \x1b[0;33mchangeset: 6:2404bbcab562\x1b[0m (esc)
531 tag: tip
543 tag: tip
532 user: test
544 user: test
533 date: Thu Jan 01 00:00:01 1970 +0000
545 date: Thu Jan 01 00:00:01 1970 +0000
534 summary: b1.1
546 summary: b1.1
535
547
536 \x1b[0;1mdiff -r 302e9dd6890d -r 2404bbcab562 b1\x1b[0m (esc)
548 \x1b[0;1mdiff -r 302e9dd6890d -r 2404bbcab562 b1\x1b[0m (esc)
537 \x1b[0;31;1m--- a/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
549 \x1b[0;31;1m--- a/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)
550 \x1b[0;32;1m+++ b/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
539 \x1b[0;35m@@ -1,1 +1,2 @@\x1b[0m (esc)
551 \x1b[0;35m@@ -1,1 +1,2 @@\x1b[0m (esc)
540 b1
552 b1
541 \x1b[0;32m+postm\x1b[0m (esc)
553 \x1b[0;32m+postm\x1b[0m (esc)
542
554
543 \x1b[0;33mchangeset: 5:302e9dd6890d\x1b[0m (esc)
555 \x1b[0;33mchangeset: 5:302e9dd6890d\x1b[0m (esc)
544 parent: 3:e62f78d544b4
556 parent: 3:e62f78d544b4
545 parent: 4:ddb82e70d1a1
557 parent: 4:ddb82e70d1a1
546 user: test
558 user: test
547 date: Thu Jan 01 00:00:01 1970 +0000
559 date: Thu Jan 01 00:00:01 1970 +0000
548 summary: m12
560 summary: m12
549
561
550 \x1b[0;1mdiff -r e62f78d544b4 -r 302e9dd6890d b2\x1b[0m (esc)
562 \x1b[0;1mdiff -r e62f78d544b4 -r 302e9dd6890d b2\x1b[0m (esc)
551 \x1b[0;31;1m--- /dev/null Thu Jan 01 00:00:00 1970 +0000\x1b[0m (esc)
563 \x1b[0;31;1m--- /dev/null Thu Jan 01 00:00:00 1970 +0000\x1b[0m (esc)
552 \x1b[0;32;1m+++ b/b2 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
564 \x1b[0;32;1m+++ b/b2 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
553 \x1b[0;35m@@ -0,0 +1,1 @@\x1b[0m (esc)
565 \x1b[0;35m@@ -0,0 +1,1 @@\x1b[0m (esc)
554 \x1b[0;32m+b2\x1b[0m (esc)
566 \x1b[0;32m+b2\x1b[0m (esc)
555
567
556
568
557
569
558 log -r tip --stat
570 log -r tip --stat
559
571
560 $ hg log -r tip --stat
572 $ hg log -r tip --stat
561 changeset: 6:2404bbcab562
573 changeset: 6:2404bbcab562
562 tag: tip
574 tag: tip
563 user: test
575 user: test
564 date: Thu Jan 01 00:00:01 1970 +0000
576 date: Thu Jan 01 00:00:01 1970 +0000
565 summary: b1.1
577 summary: b1.1
566
578
567 b1 | 1 +
579 b1 | 1 +
568 1 files changed, 1 insertions(+), 0 deletions(-)
580 1 files changed, 1 insertions(+), 0 deletions(-)
569
581
570
582
571 $ cd ..
583 $ cd ..
572
584
573 $ hg init usertest
585 $ hg init usertest
574 $ cd usertest
586 $ cd usertest
575
587
576 $ echo a > a
588 $ echo a > a
577 $ hg ci -A -m "a" -u "User One <user1@example.org>"
589 $ hg ci -A -m "a" -u "User One <user1@example.org>"
578 adding a
590 adding a
579 $ echo b > b
591 $ echo b > b
580 $ hg ci -A -m "b" -u "User Two <user2@example.org>"
592 $ hg ci -A -m "b" -u "User Two <user2@example.org>"
581 adding b
593 adding b
582
594
583 $ hg log -u "User One <user1@example.org>"
595 $ hg log -u "User One <user1@example.org>"
584 changeset: 0:29a4c94f1924
596 changeset: 0:29a4c94f1924
585 user: User One <user1@example.org>
597 user: User One <user1@example.org>
586 date: Thu Jan 01 00:00:00 1970 +0000
598 date: Thu Jan 01 00:00:00 1970 +0000
587 summary: a
599 summary: a
588
600
589 $ hg log -u "user1" -u "user2"
601 $ hg log -u "user1" -u "user2"
590 changeset: 1:e834b5e69c0e
602 changeset: 1:e834b5e69c0e
591 tag: tip
603 tag: tip
592 user: User Two <user2@example.org>
604 user: User Two <user2@example.org>
593 date: Thu Jan 01 00:00:00 1970 +0000
605 date: Thu Jan 01 00:00:00 1970 +0000
594 summary: b
606 summary: b
595
607
596 changeset: 0:29a4c94f1924
608 changeset: 0:29a4c94f1924
597 user: User One <user1@example.org>
609 user: User One <user1@example.org>
598 date: Thu Jan 01 00:00:00 1970 +0000
610 date: Thu Jan 01 00:00:00 1970 +0000
599 summary: a
611 summary: a
600
612
601 $ hg log -u "user3"
613 $ hg log -u "user3"
602
614
603 $ cd ..
615 $ cd ..
604
616
605 $ hg init branches
617 $ hg init branches
606 $ cd branches
618 $ cd branches
607
619
608 $ echo a > a
620 $ echo a > a
609 $ hg ci -A -m "commit on default"
621 $ hg ci -A -m "commit on default"
610 adding a
622 adding a
611 $ hg branch test
623 $ hg branch test
612 marked working directory as branch test
624 marked working directory as branch test
613 $ echo b > b
625 $ echo b > b
614 $ hg ci -A -m "commit on test"
626 $ hg ci -A -m "commit on test"
615 adding b
627 adding b
616
628
617 $ hg up default
629 $ hg up default
618 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
630 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
619 $ echo c > c
631 $ echo c > c
620 $ hg ci -A -m "commit on default"
632 $ hg ci -A -m "commit on default"
621 adding c
633 adding c
622 $ hg up test
634 $ hg up test
623 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
635 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
624 $ echo c > c
636 $ echo c > c
625 $ hg ci -A -m "commit on test"
637 $ hg ci -A -m "commit on test"
626 adding c
638 adding c
627
639
628
640
629 log -b default
641 log -b default
630
642
631 $ hg log -b default
643 $ hg log -b default
632 changeset: 2:c3a4f03cc9a7
644 changeset: 2:c3a4f03cc9a7
633 parent: 0:24427303d56f
645 parent: 0:24427303d56f
634 user: test
646 user: test
635 date: Thu Jan 01 00:00:00 1970 +0000
647 date: Thu Jan 01 00:00:00 1970 +0000
636 summary: commit on default
648 summary: commit on default
637
649
638 changeset: 0:24427303d56f
650 changeset: 0:24427303d56f
639 user: test
651 user: test
640 date: Thu Jan 01 00:00:00 1970 +0000
652 date: Thu Jan 01 00:00:00 1970 +0000
641 summary: commit on default
653 summary: commit on default
642
654
643
655
644
656
645 log -b test
657 log -b test
646
658
647 $ hg log -b test
659 $ hg log -b test
648 changeset: 3:f5d8de11c2e2
660 changeset: 3:f5d8de11c2e2
649 branch: test
661 branch: test
650 tag: tip
662 tag: tip
651 parent: 1:d32277701ccb
663 parent: 1:d32277701ccb
652 user: test
664 user: test
653 date: Thu Jan 01 00:00:00 1970 +0000
665 date: Thu Jan 01 00:00:00 1970 +0000
654 summary: commit on test
666 summary: commit on test
655
667
656 changeset: 1:d32277701ccb
668 changeset: 1:d32277701ccb
657 branch: test
669 branch: test
658 user: test
670 user: test
659 date: Thu Jan 01 00:00:00 1970 +0000
671 date: Thu Jan 01 00:00:00 1970 +0000
660 summary: commit on test
672 summary: commit on test
661
673
662
674
663
675
664 log -b dummy
676 log -b dummy
665
677
666 $ hg log -b dummy
678 $ hg log -b dummy
667 abort: unknown revision 'dummy'!
679 abort: unknown revision 'dummy'!
668 [255]
680 [255]
669
681
670
682
671 log -b .
683 log -b .
672
684
673 $ hg log -b .
685 $ hg log -b .
674 changeset: 3:f5d8de11c2e2
686 changeset: 3:f5d8de11c2e2
675 branch: test
687 branch: test
676 tag: tip
688 tag: tip
677 parent: 1:d32277701ccb
689 parent: 1:d32277701ccb
678 user: test
690 user: test
679 date: Thu Jan 01 00:00:00 1970 +0000
691 date: Thu Jan 01 00:00:00 1970 +0000
680 summary: commit on test
692 summary: commit on test
681
693
682 changeset: 1:d32277701ccb
694 changeset: 1:d32277701ccb
683 branch: test
695 branch: test
684 user: test
696 user: test
685 date: Thu Jan 01 00:00:00 1970 +0000
697 date: Thu Jan 01 00:00:00 1970 +0000
686 summary: commit on test
698 summary: commit on test
687
699
688
700
689
701
690 log -b default -b test
702 log -b default -b test
691
703
692 $ hg log -b default -b test
704 $ hg log -b default -b test
693 changeset: 3:f5d8de11c2e2
705 changeset: 3:f5d8de11c2e2
694 branch: test
706 branch: test
695 tag: tip
707 tag: tip
696 parent: 1:d32277701ccb
708 parent: 1:d32277701ccb
697 user: test
709 user: test
698 date: Thu Jan 01 00:00:00 1970 +0000
710 date: Thu Jan 01 00:00:00 1970 +0000
699 summary: commit on test
711 summary: commit on test
700
712
701 changeset: 2:c3a4f03cc9a7
713 changeset: 2:c3a4f03cc9a7
702 parent: 0:24427303d56f
714 parent: 0:24427303d56f
703 user: test
715 user: test
704 date: Thu Jan 01 00:00:00 1970 +0000
716 date: Thu Jan 01 00:00:00 1970 +0000
705 summary: commit on default
717 summary: commit on default
706
718
707 changeset: 1:d32277701ccb
719 changeset: 1:d32277701ccb
708 branch: test
720 branch: test
709 user: test
721 user: test
710 date: Thu Jan 01 00:00:00 1970 +0000
722 date: Thu Jan 01 00:00:00 1970 +0000
711 summary: commit on test
723 summary: commit on test
712
724
713 changeset: 0:24427303d56f
725 changeset: 0:24427303d56f
714 user: test
726 user: test
715 date: Thu Jan 01 00:00:00 1970 +0000
727 date: Thu Jan 01 00:00:00 1970 +0000
716 summary: commit on default
728 summary: commit on default
717
729
718
730
719
731
720 log -b default -b .
732 log -b default -b .
721
733
722 $ hg log -b default -b .
734 $ hg log -b default -b .
723 changeset: 3:f5d8de11c2e2
735 changeset: 3:f5d8de11c2e2
724 branch: test
736 branch: test
725 tag: tip
737 tag: tip
726 parent: 1:d32277701ccb
738 parent: 1:d32277701ccb
727 user: test
739 user: test
728 date: Thu Jan 01 00:00:00 1970 +0000
740 date: Thu Jan 01 00:00:00 1970 +0000
729 summary: commit on test
741 summary: commit on test
730
742
731 changeset: 2:c3a4f03cc9a7
743 changeset: 2:c3a4f03cc9a7
732 parent: 0:24427303d56f
744 parent: 0:24427303d56f
733 user: test
745 user: test
734 date: Thu Jan 01 00:00:00 1970 +0000
746 date: Thu Jan 01 00:00:00 1970 +0000
735 summary: commit on default
747 summary: commit on default
736
748
737 changeset: 1:d32277701ccb
749 changeset: 1:d32277701ccb
738 branch: test
750 branch: test
739 user: test
751 user: test
740 date: Thu Jan 01 00:00:00 1970 +0000
752 date: Thu Jan 01 00:00:00 1970 +0000
741 summary: commit on test
753 summary: commit on test
742
754
743 changeset: 0:24427303d56f
755 changeset: 0:24427303d56f
744 user: test
756 user: test
745 date: Thu Jan 01 00:00:00 1970 +0000
757 date: Thu Jan 01 00:00:00 1970 +0000
746 summary: commit on default
758 summary: commit on default
747
759
748
760
749
761
750 log -b . -b test
762 log -b . -b test
751
763
752 $ hg log -b . -b test
764 $ hg log -b . -b test
753 changeset: 3:f5d8de11c2e2
765 changeset: 3:f5d8de11c2e2
754 branch: test
766 branch: test
755 tag: tip
767 tag: tip
756 parent: 1:d32277701ccb
768 parent: 1:d32277701ccb
757 user: test
769 user: test
758 date: Thu Jan 01 00:00:00 1970 +0000
770 date: Thu Jan 01 00:00:00 1970 +0000
759 summary: commit on test
771 summary: commit on test
760
772
761 changeset: 1:d32277701ccb
773 changeset: 1:d32277701ccb
762 branch: test
774 branch: test
763 user: test
775 user: test
764 date: Thu Jan 01 00:00:00 1970 +0000
776 date: Thu Jan 01 00:00:00 1970 +0000
765 summary: commit on test
777 summary: commit on test
766
778
767
779
768
780
769 log -b 2
781 log -b 2
770
782
771 $ hg log -b 2
783 $ hg log -b 2
772 changeset: 2:c3a4f03cc9a7
784 changeset: 2:c3a4f03cc9a7
773 parent: 0:24427303d56f
785 parent: 0:24427303d56f
774 user: test
786 user: test
775 date: Thu Jan 01 00:00:00 1970 +0000
787 date: Thu Jan 01 00:00:00 1970 +0000
776 summary: commit on default
788 summary: commit on default
777
789
778 changeset: 0:24427303d56f
790 changeset: 0:24427303d56f
779 user: test
791 user: test
780 date: Thu Jan 01 00:00:00 1970 +0000
792 date: Thu Jan 01 00:00:00 1970 +0000
781 summary: commit on default
793 summary: commit on default
782
794
783
795
784
796
785 log -p --cwd dir (in subdir)
797 log -p --cwd dir (in subdir)
786
798
787 $ mkdir dir
799 $ mkdir dir
788 $ hg log -p --cwd dir
800 $ hg log -p --cwd dir
789 changeset: 3:f5d8de11c2e2
801 changeset: 3:f5d8de11c2e2
790 branch: test
802 branch: test
791 tag: tip
803 tag: tip
792 parent: 1:d32277701ccb
804 parent: 1:d32277701ccb
793 user: test
805 user: test
794 date: Thu Jan 01 00:00:00 1970 +0000
806 date: Thu Jan 01 00:00:00 1970 +0000
795 summary: commit on test
807 summary: commit on test
796
808
797 diff -r d32277701ccb -r f5d8de11c2e2 c
809 diff -r d32277701ccb -r f5d8de11c2e2 c
798 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
810 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
799 +++ b/c Thu Jan 01 00:00:00 1970 +0000
811 +++ b/c Thu Jan 01 00:00:00 1970 +0000
800 @@ -0,0 +1,1 @@
812 @@ -0,0 +1,1 @@
801 +c
813 +c
802
814
803 changeset: 2:c3a4f03cc9a7
815 changeset: 2:c3a4f03cc9a7
804 parent: 0:24427303d56f
816 parent: 0:24427303d56f
805 user: test
817 user: test
806 date: Thu Jan 01 00:00:00 1970 +0000
818 date: Thu Jan 01 00:00:00 1970 +0000
807 summary: commit on default
819 summary: commit on default
808
820
809 diff -r 24427303d56f -r c3a4f03cc9a7 c
821 diff -r 24427303d56f -r c3a4f03cc9a7 c
810 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
822 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
811 +++ b/c Thu Jan 01 00:00:00 1970 +0000
823 +++ b/c Thu Jan 01 00:00:00 1970 +0000
812 @@ -0,0 +1,1 @@
824 @@ -0,0 +1,1 @@
813 +c
825 +c
814
826
815 changeset: 1:d32277701ccb
827 changeset: 1:d32277701ccb
816 branch: test
828 branch: test
817 user: test
829 user: test
818 date: Thu Jan 01 00:00:00 1970 +0000
830 date: Thu Jan 01 00:00:00 1970 +0000
819 summary: commit on test
831 summary: commit on test
820
832
821 diff -r 24427303d56f -r d32277701ccb b
833 diff -r 24427303d56f -r d32277701ccb b
822 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
834 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
823 +++ b/b Thu Jan 01 00:00:00 1970 +0000
835 +++ b/b Thu Jan 01 00:00:00 1970 +0000
824 @@ -0,0 +1,1 @@
836 @@ -0,0 +1,1 @@
825 +b
837 +b
826
838
827 changeset: 0:24427303d56f
839 changeset: 0:24427303d56f
828 user: test
840 user: test
829 date: Thu Jan 01 00:00:00 1970 +0000
841 date: Thu Jan 01 00:00:00 1970 +0000
830 summary: commit on default
842 summary: commit on default
831
843
832 diff -r 000000000000 -r 24427303d56f a
844 diff -r 000000000000 -r 24427303d56f a
833 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
845 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
834 +++ b/a Thu Jan 01 00:00:00 1970 +0000
846 +++ b/a Thu Jan 01 00:00:00 1970 +0000
835 @@ -0,0 +1,1 @@
847 @@ -0,0 +1,1 @@
836 +a
848 +a
837
849
838
850
839
851
840 log -p -R repo
852 log -p -R repo
841
853
842 $ cd dir
854 $ cd dir
843 $ hg log -p -R .. ../a
855 $ hg log -p -R .. ../a
844 changeset: 0:24427303d56f
856 changeset: 0:24427303d56f
845 user: test
857 user: test
846 date: Thu Jan 01 00:00:00 1970 +0000
858 date: Thu Jan 01 00:00:00 1970 +0000
847 summary: commit on default
859 summary: commit on default
848
860
849 diff -r 000000000000 -r 24427303d56f a
861 diff -r 000000000000 -r 24427303d56f a
850 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
862 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
851 +++ b/a Thu Jan 01 00:00:00 1970 +0000
863 +++ b/a Thu Jan 01 00:00:00 1970 +0000
852 @@ -0,0 +1,1 @@
864 @@ -0,0 +1,1 @@
853 +a
865 +a
854
866
855
867
856
868
857 $ cd ..
869 $ cd ..
858 $ hg init follow2
870 $ hg init follow2
859 $ cd follow2
871 $ cd follow2
860
872
861
873
862 # Build the following history:
874 # Build the following history:
863 # tip - o - x - o - x - x
875 # tip - o - x - o - x - x
864 # \ /
876 # \ /
865 # o - o - o - x
877 # o - o - o - x
866 # \ /
878 # \ /
867 # o
879 # o
868 #
880 #
869 # Where "o" is a revision containing "foo" and
881 # Where "o" is a revision containing "foo" and
870 # "x" is a revision without "foo"
882 # "x" is a revision without "foo"
871
883
872 $ touch init
884 $ touch init
873 $ hg ci -A -m "init, unrelated"
885 $ hg ci -A -m "init, unrelated"
874 adding init
886 adding init
875 $ echo 'foo' > init
887 $ echo 'foo' > init
876 $ hg ci -m "change, unrelated"
888 $ hg ci -m "change, unrelated"
877 $ echo 'foo' > foo
889 $ echo 'foo' > foo
878 $ hg ci -A -m "add unrelated old foo"
890 $ hg ci -A -m "add unrelated old foo"
879 adding foo
891 adding foo
880 $ hg rm foo
892 $ hg rm foo
881 $ hg ci -m "delete foo, unrelated"
893 $ hg ci -m "delete foo, unrelated"
882 $ echo 'related' > foo
894 $ echo 'related' > foo
883 $ hg ci -A -m "add foo, related"
895 $ hg ci -A -m "add foo, related"
884 adding foo
896 adding foo
885
897
886 $ hg up 0
898 $ hg up 0
887 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
899 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
888 $ touch branch
900 $ touch branch
889 $ hg ci -A -m "first branch, unrelated"
901 $ hg ci -A -m "first branch, unrelated"
890 adding branch
902 adding branch
891 created new head
903 created new head
892 $ touch foo
904 $ touch foo
893 $ hg ci -A -m "create foo, related"
905 $ hg ci -A -m "create foo, related"
894 adding foo
906 adding foo
895 $ echo 'change' > foo
907 $ echo 'change' > foo
896 $ hg ci -m "change foo, related"
908 $ hg ci -m "change foo, related"
897
909
898 $ hg up 6
910 $ hg up 6
899 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
911 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
900 $ echo 'change foo in branch' > foo
912 $ echo 'change foo in branch' > foo
901 $ hg ci -m "change foo in branch, related"
913 $ hg ci -m "change foo in branch, related"
902 created new head
914 created new head
903 $ hg merge 7
915 $ hg merge 7
904 merging foo
916 merging foo
905 warning: conflicts during merge.
917 warning: conflicts during merge.
906 merging foo failed!
918 merging foo failed!
907 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
919 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
908 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
920 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
909 [1]
921 [1]
910 $ echo 'merge 1' > foo
922 $ echo 'merge 1' > foo
911 $ hg resolve -m foo
923 $ hg resolve -m foo
912 $ hg ci -m "First merge, related"
924 $ hg ci -m "First merge, related"
913
925
914 $ hg merge 4
926 $ hg merge 4
915 merging foo
927 merging foo
916 warning: conflicts during merge.
928 warning: conflicts during merge.
917 merging foo failed!
929 merging foo failed!
918 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
930 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
919 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
931 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
920 [1]
932 [1]
921 $ echo 'merge 2' > foo
933 $ echo 'merge 2' > foo
922 $ hg resolve -m foo
934 $ hg resolve -m foo
923 $ hg ci -m "Last merge, related"
935 $ hg ci -m "Last merge, related"
924
936
925 $ hg --config "extensions.graphlog=" glog
937 $ hg --config "extensions.graphlog=" glog
926 @ changeset: 10:4dae8563d2c5
938 @ changeset: 10:4dae8563d2c5
927 |\ tag: tip
939 |\ tag: tip
928 | | parent: 9:7b35701b003e
940 | | parent: 9:7b35701b003e
929 | | parent: 4:88176d361b69
941 | | parent: 4:88176d361b69
930 | | user: test
942 | | user: test
931 | | date: Thu Jan 01 00:00:00 1970 +0000
943 | | date: Thu Jan 01 00:00:00 1970 +0000
932 | | summary: Last merge, related
944 | | summary: Last merge, related
933 | |
945 | |
934 | o changeset: 9:7b35701b003e
946 | o changeset: 9:7b35701b003e
935 | |\ parent: 8:e5416ad8a855
947 | |\ parent: 8:e5416ad8a855
936 | | | parent: 7:87fe3144dcfa
948 | | | parent: 7:87fe3144dcfa
937 | | | user: test
949 | | | user: test
938 | | | date: Thu Jan 01 00:00:00 1970 +0000
950 | | | date: Thu Jan 01 00:00:00 1970 +0000
939 | | | summary: First merge, related
951 | | | summary: First merge, related
940 | | |
952 | | |
941 | | o changeset: 8:e5416ad8a855
953 | | o changeset: 8:e5416ad8a855
942 | | | parent: 6:dc6c325fe5ee
954 | | | parent: 6:dc6c325fe5ee
943 | | | user: test
955 | | | user: test
944 | | | date: Thu Jan 01 00:00:00 1970 +0000
956 | | | date: Thu Jan 01 00:00:00 1970 +0000
945 | | | summary: change foo in branch, related
957 | | | summary: change foo in branch, related
946 | | |
958 | | |
947 | o | changeset: 7:87fe3144dcfa
959 | o | changeset: 7:87fe3144dcfa
948 | |/ user: test
960 | |/ user: test
949 | | date: Thu Jan 01 00:00:00 1970 +0000
961 | | date: Thu Jan 01 00:00:00 1970 +0000
950 | | summary: change foo, related
962 | | summary: change foo, related
951 | |
963 | |
952 | o changeset: 6:dc6c325fe5ee
964 | o changeset: 6:dc6c325fe5ee
953 | | user: test
965 | | user: test
954 | | date: Thu Jan 01 00:00:00 1970 +0000
966 | | date: Thu Jan 01 00:00:00 1970 +0000
955 | | summary: create foo, related
967 | | summary: create foo, related
956 | |
968 | |
957 | o changeset: 5:73db34516eb9
969 | o changeset: 5:73db34516eb9
958 | | parent: 0:e87515fd044a
970 | | parent: 0:e87515fd044a
959 | | user: test
971 | | user: test
960 | | date: Thu Jan 01 00:00:00 1970 +0000
972 | | date: Thu Jan 01 00:00:00 1970 +0000
961 | | summary: first branch, unrelated
973 | | summary: first branch, unrelated
962 | |
974 | |
963 o | changeset: 4:88176d361b69
975 o | changeset: 4:88176d361b69
964 | | user: test
976 | | user: test
965 | | date: Thu Jan 01 00:00:00 1970 +0000
977 | | date: Thu Jan 01 00:00:00 1970 +0000
966 | | summary: add foo, related
978 | | summary: add foo, related
967 | |
979 | |
968 o | changeset: 3:dd78ae4afb56
980 o | changeset: 3:dd78ae4afb56
969 | | user: test
981 | | user: test
970 | | date: Thu Jan 01 00:00:00 1970 +0000
982 | | date: Thu Jan 01 00:00:00 1970 +0000
971 | | summary: delete foo, unrelated
983 | | summary: delete foo, unrelated
972 | |
984 | |
973 o | changeset: 2:c4c64aedf0f7
985 o | changeset: 2:c4c64aedf0f7
974 | | user: test
986 | | user: test
975 | | date: Thu Jan 01 00:00:00 1970 +0000
987 | | date: Thu Jan 01 00:00:00 1970 +0000
976 | | summary: add unrelated old foo
988 | | summary: add unrelated old foo
977 | |
989 | |
978 o | changeset: 1:e5faa7440653
990 o | changeset: 1:e5faa7440653
979 |/ user: test
991 |/ user: test
980 | date: Thu Jan 01 00:00:00 1970 +0000
992 | date: Thu Jan 01 00:00:00 1970 +0000
981 | summary: change, unrelated
993 | summary: change, unrelated
982 |
994 |
983 o changeset: 0:e87515fd044a
995 o changeset: 0:e87515fd044a
984 user: test
996 user: test
985 date: Thu Jan 01 00:00:00 1970 +0000
997 date: Thu Jan 01 00:00:00 1970 +0000
986 summary: init, unrelated
998 summary: init, unrelated
987
999
988
1000
989 $ hg --traceback log -f foo
1001 $ hg --traceback log -f foo
990 changeset: 10:4dae8563d2c5
1002 changeset: 10:4dae8563d2c5
991 tag: tip
1003 tag: tip
992 parent: 9:7b35701b003e
1004 parent: 9:7b35701b003e
993 parent: 4:88176d361b69
1005 parent: 4:88176d361b69
994 user: test
1006 user: test
995 date: Thu Jan 01 00:00:00 1970 +0000
1007 date: Thu Jan 01 00:00:00 1970 +0000
996 summary: Last merge, related
1008 summary: Last merge, related
997
1009
998 changeset: 9:7b35701b003e
1010 changeset: 9:7b35701b003e
999 parent: 8:e5416ad8a855
1011 parent: 8:e5416ad8a855
1000 parent: 7:87fe3144dcfa
1012 parent: 7:87fe3144dcfa
1001 user: test
1013 user: test
1002 date: Thu Jan 01 00:00:00 1970 +0000
1014 date: Thu Jan 01 00:00:00 1970 +0000
1003 summary: First merge, related
1015 summary: First merge, related
1004
1016
1005 changeset: 8:e5416ad8a855
1017 changeset: 8:e5416ad8a855
1006 parent: 6:dc6c325fe5ee
1018 parent: 6:dc6c325fe5ee
1007 user: test
1019 user: test
1008 date: Thu Jan 01 00:00:00 1970 +0000
1020 date: Thu Jan 01 00:00:00 1970 +0000
1009 summary: change foo in branch, related
1021 summary: change foo in branch, related
1010
1022
1011 changeset: 7:87fe3144dcfa
1023 changeset: 7:87fe3144dcfa
1012 user: test
1024 user: test
1013 date: Thu Jan 01 00:00:00 1970 +0000
1025 date: Thu Jan 01 00:00:00 1970 +0000
1014 summary: change foo, related
1026 summary: change foo, related
1015
1027
1016 changeset: 6:dc6c325fe5ee
1028 changeset: 6:dc6c325fe5ee
1017 user: test
1029 user: test
1018 date: Thu Jan 01 00:00:00 1970 +0000
1030 date: Thu Jan 01 00:00:00 1970 +0000
1019 summary: create foo, related
1031 summary: create foo, related
1020
1032
1021 changeset: 4:88176d361b69
1033 changeset: 4:88176d361b69
1022 user: test
1034 user: test
1023 date: Thu Jan 01 00:00:00 1970 +0000
1035 date: Thu Jan 01 00:00:00 1970 +0000
1024 summary: add foo, related
1036 summary: add foo, related
1025
1037
1026
1038
1027 Also check when maxrev < lastrevfilelog
1039 Also check when maxrev < lastrevfilelog
1028
1040
1029 $ hg --traceback log -f -r4 foo
1041 $ hg --traceback log -f -r4 foo
1030 changeset: 4:88176d361b69
1042 changeset: 4:88176d361b69
1031 user: test
1043 user: test
1032 date: Thu Jan 01 00:00:00 1970 +0000
1044 date: Thu Jan 01 00:00:00 1970 +0000
1033 summary: add foo, related
1045 summary: add foo, related
1034
1046
1035
1047
1036 Issue2383: hg log showing _less_ differences than hg diff
1048 Issue2383: hg log showing _less_ differences than hg diff
1037
1049
1038 $ hg init issue2383
1050 $ hg init issue2383
1039 $ cd issue2383
1051 $ cd issue2383
1040
1052
1041 Create a test repo:
1053 Create a test repo:
1042
1054
1043 $ echo a > a
1055 $ echo a > a
1044 $ hg ci -Am0
1056 $ hg ci -Am0
1045 adding a
1057 adding a
1046 $ echo b > b
1058 $ echo b > b
1047 $ hg ci -Am1
1059 $ hg ci -Am1
1048 adding b
1060 adding b
1049 $ hg co 0
1061 $ hg co 0
1050 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1062 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1051 $ echo b > a
1063 $ echo b > a
1052 $ hg ci -m2
1064 $ hg ci -m2
1053 created new head
1065 created new head
1054
1066
1055 Merge:
1067 Merge:
1056
1068
1057 $ hg merge
1069 $ hg merge
1058 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1070 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1059 (branch merge, don't forget to commit)
1071 (branch merge, don't forget to commit)
1060
1072
1061 Make sure there's a file listed in the merge to trigger the bug:
1073 Make sure there's a file listed in the merge to trigger the bug:
1062
1074
1063 $ echo c > a
1075 $ echo c > a
1064 $ hg ci -m3
1076 $ hg ci -m3
1065
1077
1066 Two files shown here in diff:
1078 Two files shown here in diff:
1067
1079
1068 $ hg diff --rev 2:3
1080 $ hg diff --rev 2:3
1069 diff -r b09be438c43a -r 8e07aafe1edc a
1081 diff -r b09be438c43a -r 8e07aafe1edc a
1070 --- a/a Thu Jan 01 00:00:00 1970 +0000
1082 --- a/a Thu Jan 01 00:00:00 1970 +0000
1071 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1083 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1072 @@ -1,1 +1,1 @@
1084 @@ -1,1 +1,1 @@
1073 -b
1085 -b
1074 +c
1086 +c
1075 diff -r b09be438c43a -r 8e07aafe1edc b
1087 diff -r b09be438c43a -r 8e07aafe1edc b
1076 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1088 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1077 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1089 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1078 @@ -0,0 +1,1 @@
1090 @@ -0,0 +1,1 @@
1079 +b
1091 +b
1080
1092
1081 Diff here should be the same:
1093 Diff here should be the same:
1082
1094
1083 $ hg log -vpr 3
1095 $ hg log -vpr 3
1084 changeset: 3:8e07aafe1edc
1096 changeset: 3:8e07aafe1edc
1085 tag: tip
1097 tag: tip
1086 parent: 2:b09be438c43a
1098 parent: 2:b09be438c43a
1087 parent: 1:925d80f479bb
1099 parent: 1:925d80f479bb
1088 user: test
1100 user: test
1089 date: Thu Jan 01 00:00:00 1970 +0000
1101 date: Thu Jan 01 00:00:00 1970 +0000
1090 files: a
1102 files: a
1091 description:
1103 description:
1092 3
1104 3
1093
1105
1094
1106
1095 diff -r b09be438c43a -r 8e07aafe1edc a
1107 diff -r b09be438c43a -r 8e07aafe1edc a
1096 --- a/a Thu Jan 01 00:00:00 1970 +0000
1108 --- a/a Thu Jan 01 00:00:00 1970 +0000
1097 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1109 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1098 @@ -1,1 +1,1 @@
1110 @@ -1,1 +1,1 @@
1099 -b
1111 -b
1100 +c
1112 +c
1101 diff -r b09be438c43a -r 8e07aafe1edc b
1113 diff -r b09be438c43a -r 8e07aafe1edc b
1102 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1114 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1103 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1115 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1104 @@ -0,0 +1,1 @@
1116 @@ -0,0 +1,1 @@
1105 +b
1117 +b
1106
1118
1107 $ cd ..
1119 $ cd ..
1108
1120
1109 'hg log -r rev fn' when last(filelog(fn)) != rev
1121 'hg log -r rev fn' when last(filelog(fn)) != rev
1110
1122
1111 $ hg init simplelog; cd simplelog
1123 $ hg init simplelog; cd simplelog
1112 $ echo f > a
1124 $ echo f > a
1113 $ hg ci -Am'a' -d '0 0'
1125 $ hg ci -Am'a' -d '0 0'
1114 adding a
1126 adding a
1115 $ echo f >> a
1127 $ echo f >> a
1116 $ hg ci -Am'a bis' -d '1 0'
1128 $ hg ci -Am'a bis' -d '1 0'
1117
1129
1118 $ hg log -r0 a
1130 $ hg log -r0 a
1119 changeset: 0:9f758d63dcde
1131 changeset: 0:9f758d63dcde
1120 user: test
1132 user: test
1121 date: Thu Jan 01 00:00:00 1970 +0000
1133 date: Thu Jan 01 00:00:00 1970 +0000
1122 summary: a
1134 summary: a
1123
1135
General Comments 0
You need to be logged in to leave comments. Login now