1
# Bazaar-NG -- distributed version control
3
# Copyright (C) 2005 by Canonical Ltd
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 2 of the License, or
8
# (at your option) any later version.
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
from shutil import copyfile
20
from stat import (S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE,
21
S_ISCHR, S_ISBLK, S_ISFIFO, S_ISSOCK)
22
from cStringIO import StringIO
35
from ntpath import (abspath as _nt_abspath,
37
normpath as _nt_normpath,
38
realpath as _nt_realpath,
42
from bzrlib.errors import (BzrError,
43
BzrBadParameterNotUnicode,
48
from bzrlib.symbol_versioning import *
49
from bzrlib.trace import mutter
50
import bzrlib.win32console
53
def make_readonly(filename):
54
"""Make a filename read-only."""
55
mod = os.stat(filename).st_mode
57
os.chmod(filename, mod)
60
def make_writable(filename):
61
mod = os.stat(filename).st_mode
63
os.chmod(filename, mod)
70
"""Return a quoted filename filename
72
This previously used backslash quoting, but that works poorly on
74
# TODO: I'm not really sure this is the best format either.x
77
_QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/\\_~-])')
79
if _QUOTE_RE.search(f):
86
stat.S_IFDIR:'directory',
87
stat.S_IFCHR:'chardev',
91
stat.S_IFLNK:'symlink',
92
stat.S_IFSOCK:'socket',
94
def file_kind(f, _formats=_formats, _unknown='unknown', _lstat=os.lstat):
96
return _formats[_lstat(f).st_mode & 0170000]
101
def kind_marker(kind):
104
elif kind == 'directory':
106
elif kind == 'symlink':
109
raise BzrError('invalid file kind %r' % kind)
111
lexists = getattr(os.path, 'lexists', None)
115
if hasattr(os, 'lstat'):
121
if e.errno == errno.ENOENT:
124
raise BzrError("lstat/stat of (%r): %r" % (f, e))
127
def fancy_rename(old, new, rename_func, unlink_func):
128
"""A fancy rename, when you don't have atomic rename.
130
:param old: The old path, to rename from
131
:param new: The new path, to rename to
132
:param rename_func: The potentially non-atomic rename function
133
:param unlink_func: A way to delete the target file if the full rename succeeds
136
# sftp rename doesn't allow overwriting, so play tricks:
138
base = os.path.basename(new)
139
dirname = os.path.dirname(new)
140
tmp_name = u'tmp.%s.%.9f.%d.%s' % (base, time.time(), os.getpid(), rand_chars(10))
141
tmp_name = pathjoin(dirname, tmp_name)
143
# Rename the file out of the way, but keep track if it didn't exist
144
# We don't want to grab just any exception
145
# something like EACCES should prevent us from continuing
146
# The downside is that the rename_func has to throw an exception
147
# with an errno = ENOENT, or NoSuchFile
150
rename_func(new, tmp_name)
151
except (NoSuchFile,), e:
154
# RBC 20060103 abstraction leakage: the paramiko SFTP clients rename
155
# function raises an IOError with errno == None when a rename fails.
156
# This then gets caught here.
157
if e.errno not in (None, errno.ENOENT, errno.ENOTDIR):
160
if (not hasattr(e, 'errno')
161
or e.errno not in (errno.ENOENT, errno.ENOTDIR)):
168
# This may throw an exception, in which case success will
170
rename_func(old, new)
174
# If the file used to exist, rename it back into place
175
# otherwise just delete it from the tmp location
177
unlink_func(tmp_name)
179
rename_func(tmp_name, new)
182
# In Python 2.4.2 and older, os.path.abspath and os.path.realpath
183
# choke on a Unicode string containing a relative path if
184
# os.getcwd() returns a non-sys.getdefaultencoding()-encoded
186
_fs_enc = sys.getfilesystemencoding()
187
def _posix_abspath(path):
188
return os.path.abspath(path.encode(_fs_enc)).decode(_fs_enc)
189
# jam 20060426 This is another possibility which mimics
190
# os.path.abspath, only uses unicode characters instead
191
# if not os.path.isabs(path):
192
# return os.path.join(os.getcwdu(), path)
196
def _posix_realpath(path):
197
return os.path.realpath(path.encode(_fs_enc)).decode(_fs_enc)
200
def _win32_abspath(path):
201
return _nt_abspath(path.encode(_fs_enc)).decode(_fs_enc).replace('\\', '/')
204
def _win32_realpath(path):
205
return _nt_realpath(path.encode(_fs_enc)).decode(_fs_enc).replace('\\', '/')
208
def _win32_pathjoin(*args):
209
return _nt_join(*args).replace('\\', '/')
212
def _win32_normpath(path):
213
return _nt_normpath(path).replace('\\', '/')
217
return os.getcwdu().replace('\\', '/')
220
def _win32_mkdtemp(*args, **kwargs):
221
return tempfile.mkdtemp(*args, **kwargs).replace('\\', '/')
224
def _win32_rename(old, new):
225
fancy_rename(old, new, rename_func=os.rename, unlink_func=os.unlink)
228
# Default is to just use the python builtins, but these can be rebound on
229
# particular platforms.
230
abspath = _posix_abspath
231
realpath = _posix_realpath
232
pathjoin = os.path.join
233
normpath = os.path.normpath
235
mkdtemp = tempfile.mkdtemp
237
dirname = os.path.dirname
238
basename = os.path.basename
239
rmtree = shutil.rmtree
241
MIN_ABS_PATHLENGTH = 1
244
if sys.platform == 'win32':
245
abspath = _win32_abspath
246
realpath = _win32_realpath
247
pathjoin = _win32_pathjoin
248
normpath = _win32_normpath
249
getcwd = _win32_getcwd
250
mkdtemp = _win32_mkdtemp
251
rename = _win32_rename
253
MIN_ABS_PATHLENGTH = 3
255
def _win32_delete_readonly(function, path, excinfo):
256
"""Error handler for shutil.rmtree function [for win32]
257
Helps to remove files and dirs marked as read-only.
259
type_, value = excinfo[:2]
260
if function in (os.remove, os.rmdir) \
261
and type_ == OSError \
262
and value.errno == errno.EACCES:
263
bzrlib.osutils.make_writable(path)
268
def rmtree(path, ignore_errors=False, onerror=_win32_delete_readonly):
269
"""Replacer for shutil.rmtree: could remove readonly dirs/files"""
270
return shutil.rmtree(path, ignore_errors, onerror)
273
def normalizepath(f):
274
if hasattr(os.path, 'realpath'):
278
[p,e] = os.path.split(f)
279
if e == "" or e == "." or e == "..":
282
return pathjoin(F(p), e)
286
"""Copy a file to a backup.
288
Backups are named in GNU-style, with a ~ suffix.
290
If the file is already a backup, it's not copied.
296
if has_symlinks() and os.path.islink(fn):
297
target = os.readlink(fn)
298
os.symlink(target, bfn)
306
outf = file(bfn, 'wb')
314
"""True if f is an accessible directory."""
316
return S_ISDIR(os.lstat(f)[ST_MODE])
322
"""True if f is a regular file."""
324
return S_ISREG(os.lstat(f)[ST_MODE])
329
"""True if f is a symlink."""
331
return S_ISLNK(os.lstat(f)[ST_MODE])
335
def is_inside(dir, fname):
336
"""True if fname is inside dir.
338
The parameters should typically be passed to osutils.normpath first, so
339
that . and .. and repeated slashes are eliminated, and the separators
340
are canonical for the platform.
342
The empty string as a dir name is taken as top-of-tree and matches
345
>>> is_inside('src', pathjoin('src', 'foo.c'))
347
>>> is_inside('src', 'srccontrol')
349
>>> is_inside('src', pathjoin('src', 'a', 'a', 'a', 'foo.c'))
351
>>> is_inside('foo.c', 'foo.c')
353
>>> is_inside('foo.c', '')
355
>>> is_inside('', 'foo.c')
358
# XXX: Most callers of this can actually do something smarter by
359
# looking at the inventory
369
return fname.startswith(dir)
372
def is_inside_any(dir_list, fname):
373
"""True if fname is inside any of given dirs."""
374
for dirname in dir_list:
375
if is_inside(dirname, fname):
381
def is_inside_or_parent_of_any(dir_list, fname):
382
"""True if fname is a child or a parent of any of the given files."""
383
for dirname in dir_list:
384
if is_inside(dirname, fname) or is_inside(fname, dirname):
390
def pumpfile(fromfile, tofile):
391
"""Copy contents of one file to another."""
394
b = fromfile.read(BUFSIZE)
400
def file_iterator(input_file, readsize=32768):
402
b = input_file.read(readsize)
409
if hasattr(f, 'tell'):
422
def sha_strings(strings):
423
"""Return the sha-1 of concatenation of strings"""
425
map(s.update, strings)
435
def fingerprint_file(f):
440
return {'size': size,
441
'sha1': s.hexdigest()}
444
def compare_files(a, b):
445
"""Returns true if equal in contents"""
456
def local_time_offset(t=None):
457
"""Return offset of local zone from GMT, either at present or at time t."""
458
# python2.3 localtime() can't take None
462
if time.localtime(t).tm_isdst and time.daylight:
465
return -time.timezone
468
def format_date(t, offset=0, timezone='original', date_fmt=None,
470
## TODO: Perhaps a global option to use either universal or local time?
471
## Or perhaps just let people set $TZ?
472
assert isinstance(t, float)
474
if timezone == 'utc':
477
elif timezone == 'original':
480
tt = time.gmtime(t + offset)
481
elif timezone == 'local':
482
tt = time.localtime(t)
483
offset = local_time_offset(t)
485
raise BzrError("unsupported timezone format %r" % timezone,
486
['options are "utc", "original", "local"'])
488
date_fmt = "%a %Y-%m-%d %H:%M:%S"
490
offset_str = ' %+03d%02d' % (offset / 3600, (offset / 60) % 60)
493
return (time.strftime(date_fmt, tt) + offset_str)
496
def compact_date(when):
497
return time.strftime('%Y%m%d%H%M%S', time.gmtime(when))
502
"""Return size of given open file."""
503
return os.fstat(f.fileno())[ST_SIZE]
506
# Define rand_bytes based on platform.
508
# Python 2.4 and later have os.urandom,
509
# but it doesn't work on some arches
511
rand_bytes = os.urandom
512
except (NotImplementedError, AttributeError):
513
# If python doesn't have os.urandom, or it doesn't work,
514
# then try to first pull random data from /dev/urandom
515
if os.path.exists("/dev/urandom"):
516
rand_bytes = file('/dev/urandom', 'rb').read
517
# Otherwise, use this hack as a last resort
519
# not well seeded, but better than nothing
524
s += chr(random.randint(0, 255))
529
ALNUM = '0123456789abcdefghijklmnopqrstuvwxyz'
531
"""Return a random string of num alphanumeric characters
533
The result only contains lowercase chars because it may be used on
534
case-insensitive filesystems.
537
for raw_byte in rand_bytes(num):
538
s += ALNUM[ord(raw_byte) % 36]
542
## TODO: We could later have path objects that remember their list
543
## decomposition (might be too tricksy though.)
546
"""Turn string into list of parts.
552
>>> splitpath('a/./b')
554
>>> splitpath('a/.b')
556
>>> splitpath('a/../b')
557
Traceback (most recent call last):
559
BzrError: sorry, '..' not allowed in path
561
assert isinstance(p, types.StringTypes)
563
# split on either delimiter because people might use either on
565
ps = re.split(r'[\\/]', p)
570
raise BzrError("sorry, %r not allowed in path" % f)
571
elif (f == '.') or (f == ''):
578
assert isinstance(p, list)
580
if (f == '..') or (f == None) or (f == ''):
581
raise BzrError("sorry, %r not allowed in path" % f)
585
@deprecated_function(zero_nine)
586
def appendpath(p1, p2):
590
return pathjoin(p1, p2)
594
"""Split s into lines, but without removing the newline characters."""
595
lines = s.split('\n')
596
result = [line + '\n' for line in lines[:-1]]
598
result.append(lines[-1])
602
def hardlinks_good():
603
return sys.platform not in ('win32', 'cygwin', 'darwin')
606
def link_or_copy(src, dest):
607
"""Hardlink a file, or copy it if it can't be hardlinked."""
608
if not hardlinks_good():
613
except (OSError, IOError), e:
614
if e.errno != errno.EXDEV:
618
def delete_any(full_path):
619
"""Delete a file or directory."""
623
# We may be renaming a dangling inventory id
624
if e.errno not in (errno.EISDIR, errno.EACCES, errno.EPERM):
630
if hasattr(os, 'symlink'):
636
def contains_whitespace(s):
637
"""True if there are any whitespace characters in s."""
638
for ch in string.whitespace:
645
def contains_linebreaks(s):
646
"""True if there is any vertical whitespace in s."""
654
def relpath(base, path):
655
"""Return path relative to base, or raise exception.
657
The path may be either an absolute path or a path relative to the
658
current working directory.
660
os.path.commonprefix (python2.4) has a bad bug that it works just
661
on string prefixes, assuming that '/u' is a prefix of '/u2'. This
665
assert len(base) >= MIN_ABS_PATHLENGTH, ('Length of base must be equal or'
666
' exceed the platform minimum length (which is %d)' %
673
while len(head) >= len(base):
676
head, tail = os.path.split(head)
680
raise PathNotChild(rp, base)
688
def safe_unicode(unicode_or_utf8_string):
689
"""Coerce unicode_or_utf8_string into unicode.
691
If it is unicode, it is returned.
692
Otherwise it is decoded from utf-8. If a decoding error
693
occurs, it is wrapped as a If the decoding fails, the exception is wrapped
694
as a BzrBadParameter exception.
696
if isinstance(unicode_or_utf8_string, unicode):
697
return unicode_or_utf8_string
699
return unicode_or_utf8_string.decode('utf8')
700
except UnicodeDecodeError:
701
raise BzrBadParameterNotUnicode(unicode_or_utf8_string)
704
_platform_normalizes_filenames = False
705
if sys.platform == 'darwin':
706
_platform_normalizes_filenames = True
709
def normalizes_filenames():
710
"""Return True if this platform normalizes unicode filenames.
712
Mac OSX does, Windows/Linux do not.
714
return _platform_normalizes_filenames
717
if _platform_normalizes_filenames:
718
def unicode_filename(path):
719
"""Make sure 'path' is a properly normalized filename.
721
On platforms where the system normalizes filenames (Mac OSX),
722
you can access a file by any path which will normalize
724
Internally, bzr only supports NFC/NFKC normalization, since
725
that is the standard for XML documents.
726
So we return an normalized path, and indicate this has been
729
:return: (path, is_normalized) Return a path which can
730
access the file, and whether or not this path is
733
return unicodedata.normalize('NFKC', path), True
735
def unicode_filename(path):
736
"""Make sure 'path' is a properly normalized filename.
738
On platforms where the system does not normalize filenames
739
(Windows, Linux), you have to access a file by its exact path.
740
Internally, bzr only supports NFC/NFKC normalization, since
741
that is the standard for XML documents.
742
So we return the original path, and indicate if this is
745
:return: (path, is_normalized) Return a path which can
746
access the file, and whether or not this path is
749
return path, unicodedata.normalize('NFKC', path) == path
752
def terminal_width():
753
"""Return estimated terminal width."""
754
if sys.platform == 'win32':
755
import bzrlib.win32console
756
return bzrlib.win32console.get_console_size()[0]
759
import struct, fcntl, termios
760
s = struct.pack('HHHH', 0, 0, 0, 0)
761
x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
762
width = struct.unpack('HHHH', x)[1]
767
width = int(os.environ['COLUMNS'])
775
def supports_executable():
776
return sys.platform != "win32"
779
_validWin32PathRE = re.compile(r'^([A-Za-z]:[/\\])?[^:<>*"?\|]*$')
782
def check_legal_path(path):
783
"""Check whether the supplied path is legal.
784
This is only required on Windows, so we don't test on other platforms
787
if sys.platform != "win32":
789
if _validWin32PathRE.match(path) is None:
790
raise IllegalPath(path)