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
34
from ntpath import (abspath as _nt_abspath,
36
normpath as _nt_normpath,
37
realpath as _nt_realpath,
41
from bzrlib.errors import (BzrError,
42
BzrBadParameterNotUnicode,
47
from bzrlib.trace import mutter
50
def make_readonly(filename):
51
"""Make a filename read-only."""
52
mod = os.stat(filename).st_mode
54
os.chmod(filename, mod)
57
def make_writable(filename):
58
mod = os.stat(filename).st_mode
60
os.chmod(filename, mod)
67
"""Return a quoted filename filename
69
This previously used backslash quoting, but that works poorly on
71
# TODO: I'm not really sure this is the best format either.x
74
_QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/\\_~-])')
76
if _QUOTE_RE.search(f):
83
mode = os.lstat(f)[ST_MODE]
102
def kind_marker(kind):
105
elif kind == 'directory':
107
elif kind == 'symlink':
110
raise BzrError('invalid file kind %r' % kind)
113
if hasattr(os.path, 'lexists'):
114
return os.path.lexists(f)
116
if hasattr(os, 'lstat'):
122
if e.errno == errno.ENOENT:
125
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 pumpfile(fromfile, tofile):
382
"""Copy contents of one file to another."""
385
b = fromfile.read(BUFSIZE)
391
def file_iterator(input_file, readsize=32768):
393
b = input_file.read(readsize)
400
if hasattr(f, 'tell'):
413
def sha_strings(strings):
414
"""Return the sha-1 of concatenation of strings"""
416
map(s.update, strings)
426
def fingerprint_file(f):
431
return {'size': size,
432
'sha1': s.hexdigest()}
435
def compare_files(a, b):
436
"""Returns true if equal in contents"""
447
def local_time_offset(t=None):
448
"""Return offset of local zone from GMT, either at present or at time t."""
449
# python2.3 localtime() can't take None
453
if time.localtime(t).tm_isdst and time.daylight:
456
return -time.timezone
459
def format_date(t, offset=0, timezone='original', date_fmt=None,
461
## TODO: Perhaps a global option to use either universal or local time?
462
## Or perhaps just let people set $TZ?
463
assert isinstance(t, float)
465
if timezone == 'utc':
468
elif timezone == 'original':
471
tt = time.gmtime(t + offset)
472
elif timezone == 'local':
473
tt = time.localtime(t)
474
offset = local_time_offset(t)
476
raise BzrError("unsupported timezone format %r" % timezone,
477
['options are "utc", "original", "local"'])
479
date_fmt = "%a %Y-%m-%d %H:%M:%S"
481
offset_str = ' %+03d%02d' % (offset / 3600, (offset / 60) % 60)
484
return (time.strftime(date_fmt, tt) + offset_str)
487
def compact_date(when):
488
return time.strftime('%Y%m%d%H%M%S', time.gmtime(when))
493
"""Return size of given open file."""
494
return os.fstat(f.fileno())[ST_SIZE]
497
# Define rand_bytes based on platform.
499
# Python 2.4 and later have os.urandom,
500
# but it doesn't work on some arches
502
rand_bytes = os.urandom
503
except (NotImplementedError, AttributeError):
504
# If python doesn't have os.urandom, or it doesn't work,
505
# then try to first pull random data from /dev/urandom
506
if os.path.exists("/dev/urandom"):
507
rand_bytes = file('/dev/urandom', 'rb').read
508
# Otherwise, use this hack as a last resort
510
# not well seeded, but better than nothing
515
s += chr(random.randint(0, 255))
520
ALNUM = '0123456789abcdefghijklmnopqrstuvwxyz'
522
"""Return a random string of num alphanumeric characters
524
The result only contains lowercase chars because it may be used on
525
case-insensitive filesystems.
528
for raw_byte in rand_bytes(num):
529
s += ALNUM[ord(raw_byte) % 36]
533
## TODO: We could later have path objects that remember their list
534
## decomposition (might be too tricksy though.)
537
"""Turn string into list of parts.
543
>>> splitpath('a/./b')
545
>>> splitpath('a/.b')
547
>>> splitpath('a/../b')
548
Traceback (most recent call last):
550
BzrError: sorry, '..' not allowed in path
552
assert isinstance(p, types.StringTypes)
554
# split on either delimiter because people might use either on
556
ps = re.split(r'[\\/]', p)
561
raise BzrError("sorry, %r not allowed in path" % f)
562
elif (f == '.') or (f == ''):
569
assert isinstance(p, list)
571
if (f == '..') or (f == None) or (f == ''):
572
raise BzrError("sorry, %r not allowed in path" % f)
576
def appendpath(p1, p2):
580
return pathjoin(p1, p2)
584
"""Split s into lines, but without removing the newline characters."""
585
lines = s.split('\n')
586
result = [line + '\n' for line in lines[:-1]]
588
result.append(lines[-1])
592
def hardlinks_good():
593
return sys.platform not in ('win32', 'cygwin', 'darwin')
596
def link_or_copy(src, dest):
597
"""Hardlink a file, or copy it if it can't be hardlinked."""
598
if not hardlinks_good():
603
except (OSError, IOError), e:
604
if e.errno != errno.EXDEV:
608
def delete_any(full_path):
609
"""Delete a file or directory."""
613
# We may be renaming a dangling inventory id
614
if e.errno not in (errno.EISDIR, errno.EACCES, errno.EPERM):
620
if hasattr(os, 'symlink'):
626
def contains_whitespace(s):
627
"""True if there are any whitespace characters in s."""
628
for ch in string.whitespace:
635
def contains_linebreaks(s):
636
"""True if there is any vertical whitespace in s."""
644
def relpath(base, path):
645
"""Return path relative to base, or raise exception.
647
The path may be either an absolute path or a path relative to the
648
current working directory.
650
os.path.commonprefix (python2.4) has a bad bug that it works just
651
on string prefixes, assuming that '/u' is a prefix of '/u2'. This
655
assert len(base) >= MIN_ABS_PATHLENGTH, ('Length of base must be equal or'
656
' exceed the platform minimum length (which is %d)' %
663
while len(head) >= len(base):
666
head, tail = os.path.split(head)
670
raise PathNotChild(rp, base)
678
def safe_unicode(unicode_or_utf8_string):
679
"""Coerce unicode_or_utf8_string into unicode.
681
If it is unicode, it is returned.
682
Otherwise it is decoded from utf-8. If a decoding error
683
occurs, it is wrapped as a If the decoding fails, the exception is wrapped
684
as a BzrBadParameter exception.
686
if isinstance(unicode_or_utf8_string, unicode):
687
return unicode_or_utf8_string
689
return unicode_or_utf8_string.decode('utf8')
690
except UnicodeDecodeError:
691
raise BzrBadParameterNotUnicode(unicode_or_utf8_string)
694
_platform_normalizes_filenames = False
695
if sys.platform == 'darwin':
696
_platform_normalizes_filenames = True
699
def normalizes_filenames():
700
"""Return True if this platform normalizes unicode filenames.
702
Mac OSX does, Windows/Linux do not.
704
return _platform_normalizes_filenames
707
if _platform_normalizes_filenames:
708
def unicode_filename(path):
709
"""Make sure 'path' is a properly normalized filename.
711
On platforms where the system normalizes filenames (Mac OSX),
712
you can access a file by any path which will normalize
714
Internally, bzr only supports NFC/NFKC normalization, since
715
that is the standard for XML documents.
716
So we return an normalized path, and indicate this has been
719
:return: (path, is_normalized) Return a path which can
720
access the file, and whether or not this path is
723
return unicodedata.normalize('NFKC', path), True
725
def unicode_filename(path):
726
"""Make sure 'path' is a properly normalized filename.
728
On platforms where the system does not normalize filenames
729
(Windows, Linux), you have to access a file by its exact path.
730
Internally, bzr only supports NFC/NFKC normalization, since
731
that is the standard for XML documents.
732
So we return the original path, and indicate if this is
735
:return: (path, is_normalized) Return a path which can
736
access the file, and whether or not this path is
739
return path, unicodedata.normalize('NFKC', path) == path
742
def terminal_width():
743
"""Return estimated terminal width."""
745
# TODO: Do something smart on Windows?
747
# TODO: Is there anything that gets a better update when the window
748
# is resized while the program is running? We could use the Python termcap
751
return int(os.environ['COLUMNS'])
752
except (IndexError, KeyError, ValueError):
755
def supports_executable():
756
return sys.platform != "win32"
759
_validWin32PathRE = re.compile(r'^([A-Za-z]:[/\\])?[^:<>*"?\|]*$')
762
def check_legal_path(path):
763
"""Check whether the supplied path is legal.
764
This is only required on Windows, so we don't test on other platforms
767
if sys.platform != "win32":
769
if _validWin32PathRE.match(path) is None:
770
raise IllegalPath(path)