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
import os, types, re, time, errno, sys
20
from stat import (S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE,
21
S_ISCHR, S_ISBLK, S_ISFIFO, S_ISSOCK)
23
from bzrlib.errors import BzrError
24
from bzrlib.trace import mutter
27
def make_readonly(filename):
28
"""Make a filename read-only."""
29
# TODO: probably needs to be fixed for windows
30
mod = os.stat(filename).st_mode
32
os.chmod(filename, mod)
35
def make_writable(filename):
36
mod = os.stat(filename).st_mode
38
os.chmod(filename, mod)
45
"""Return a quoted filename filename
47
This previously used backslash quoting, but that works poorly on
49
# TODO: I'm not really sure this is the best format either.x
52
_QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])')
54
if _QUOTE_RE.search(f):
61
mode = os.lstat(f)[ST_MODE]
80
def kind_marker(kind):
83
elif kind == 'directory':
85
elif kind == 'symlink':
88
raise BzrError('invalid file kind %r' % kind)
93
"""Copy a file to a backup.
95
Backups are named in GNU-style, with a ~ suffix.
97
If the file is already a backup, it's not copied.
110
outf = file(bfn, 'wb')
116
def rename(path_from, path_to):
117
"""Basically the same as os.rename() just special for win32"""
118
if sys.platform == 'win32':
122
if e.errno != e.ENOENT:
124
os.rename(path_from, path_to)
131
"""True if f is an accessible directory."""
133
return S_ISDIR(os.lstat(f)[ST_MODE])
140
"""True if f is a regular file."""
142
return S_ISREG(os.lstat(f)[ST_MODE])
147
def is_inside(dir, fname):
148
"""True if fname is inside dir.
150
The parameters should typically be passed to os.path.normpath first, so
151
that . and .. and repeated slashes are eliminated, and the separators
152
are canonical for the platform.
154
The empty string as a dir name is taken as top-of-tree and matches
157
>>> is_inside('src', 'src/foo.c')
159
>>> is_inside('src', 'srccontrol')
161
>>> is_inside('src', 'src/a/a/a/foo.c')
163
>>> is_inside('foo.c', 'foo.c')
165
>>> is_inside('foo.c', '')
167
>>> is_inside('', 'foo.c')
170
# XXX: Most callers of this can actually do something smarter by
171
# looking at the inventory
178
if dir[-1] != os.sep:
181
return fname.startswith(dir)
184
def is_inside_any(dir_list, fname):
185
"""True if fname is inside any of given dirs."""
186
for dirname in dir_list:
187
if is_inside(dirname, fname):
193
def pumpfile(fromfile, tofile):
194
"""Copy contents of one file to another."""
195
tofile.write(fromfile.read())
199
"""Return a new UUID"""
201
return file('/proc/sys/kernel/random/uuid').readline().rstrip('\n')
203
return chomp(os.popen('uuidgen').readline())
208
if hasattr(f, 'tell'):
228
def fingerprint_file(f):
234
return {'size': size,
235
'sha1': s.hexdigest()}
239
"""Return per-user configuration directory.
241
By default this is ~/.bzr.conf/
243
TODO: Global option --config-dir to override this.
245
return os.path.expanduser("~/.bzr.conf")
249
"""Calculate automatic user identification.
251
Returns (realname, email).
253
Only used when none is set in the environment or the id file.
255
This previously used the FQDN as the default domain, but that can
256
be very slow on machines where DNS is broken. So now we simply
261
# XXX: Any good way to get real user name on win32?
266
w = pwd.getpwuid(uid)
267
gecos = w.pw_gecos.decode(bzrlib.user_encoding)
268
username = w.pw_name.decode(bzrlib.user_encoding)
269
comma = gecos.find(',')
273
realname = gecos[:comma]
279
realname = username = getpass.getuser().decode(bzrlib.user_encoding)
281
return realname, (username + '@' + socket.gethostname())
284
def _get_user_id(branch):
285
"""Return the full user id from a file or environment variable.
287
e.g. "John Hacker <jhacker@foo.org>"
290
A branch to use for a per-branch configuration, or None.
292
The following are searched in order:
295
2. .bzr/email for this branch.
299
v = os.environ.get('BZREMAIL')
301
return v.decode(bzrlib.user_encoding)
305
return (branch.controlfile("email", "r")
307
.decode(bzrlib.user_encoding)
310
if e.errno != errno.ENOENT:
316
return (open(os.path.join(config_dir(), "email"))
318
.decode(bzrlib.user_encoding)
321
if e.errno != errno.ENOENT:
324
v = os.environ.get('EMAIL')
326
return v.decode(bzrlib.user_encoding)
331
def username(branch):
332
"""Return email-style username.
334
Something similar to 'Martin Pool <mbp@sourcefrog.net>'
336
TODO: Check it's reasonably well-formed.
338
v = _get_user_id(branch)
342
name, email = _auto_user_id()
344
return '%s <%s>' % (name, email)
349
def user_email(branch):
350
"""Return just the email component of a username."""
351
e = _get_user_id(branch)
353
m = re.search(r'[\w+.-]+@[\w+.-]+', e)
355
raise BzrError("%r doesn't seem to contain a reasonable email address" % e)
358
return _auto_user_id()[1]
362
def compare_files(a, b):
363
"""Returns true if equal in contents"""
375
def local_time_offset(t=None):
376
"""Return offset of local zone from GMT, either at present or at time t."""
377
# python2.3 localtime() can't take None
381
if time.localtime(t).tm_isdst and time.daylight:
384
return -time.timezone
387
def format_date(t, offset=0, timezone='original'):
388
## TODO: Perhaps a global option to use either universal or local time?
389
## Or perhaps just let people set $TZ?
390
assert isinstance(t, float)
392
if timezone == 'utc':
395
elif timezone == 'original':
398
tt = time.gmtime(t + offset)
399
elif timezone == 'local':
400
tt = time.localtime(t)
401
offset = local_time_offset(t)
403
raise BzrError("unsupported timezone format %r" % timezone,
404
['options are "utc", "original", "local"'])
406
return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
407
+ ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
410
def compact_date(when):
411
return time.strftime('%Y%m%d%H%M%S', time.gmtime(when))
416
"""Return size of given open file."""
417
return os.fstat(f.fileno())[ST_SIZE]
419
# Define rand_bytes based on platform.
421
# Python 2.4 and later have os.urandom,
422
# but it doesn't work on some arches
424
rand_bytes = os.urandom
425
except (NotImplementedError, AttributeError):
426
# If python doesn't have os.urandom, or it doesn't work,
427
# then try to first pull random data from /dev/urandom
428
if os.path.exists("/dev/urandom"):
429
rand_bytes = file('/dev/urandom', 'rb').read
430
# Otherwise, use this hack as a last resort
432
# not well seeded, but better than nothing
437
s += chr(random.randint(0, 255))
441
## TODO: We could later have path objects that remember their list
442
## decomposition (might be too tricksy though.)
445
"""Turn string into list of parts.
451
>>> splitpath('a/./b')
453
>>> splitpath('a/.b')
455
>>> splitpath('a/../b')
456
Traceback (most recent call last):
458
BzrError: sorry, '..' not allowed in path
460
assert isinstance(p, types.StringTypes)
462
# split on either delimiter because people might use either on
464
ps = re.split(r'[\\/]', p)
469
raise BzrError("sorry, %r not allowed in path" % f)
470
elif (f == '.') or (f == ''):
477
assert isinstance(p, list)
479
if (f == '..') or (f == None) or (f == ''):
480
raise BzrError("sorry, %r not allowed in path" % f)
481
return os.path.join(*p)
484
def appendpath(p1, p2):
488
return os.path.join(p1, p2)
491
def extern_command(cmd, ignore_errors = False):
492
mutter('external command: %s' % `cmd`)
494
if not ignore_errors:
495
raise BzrError('command failed')
498
def _read_config_value(name):
499
"""Read a config value from the file ~/.bzr.conf/<name>
500
Return None if the file does not exist"""
502
f = file(os.path.join(config_dir(), name), "r")
503
return f.read().decode(bzrlib.user_encoding).rstrip("\r\n")
505
if e.errno == errno.ENOENT: