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
 
 
22
from bzrlib.errors import BzrError
 
 
23
from bzrlib.trace import mutter
 
 
26
def make_readonly(filename):
 
 
27
    """Make a filename read-only."""
 
 
28
    # TODO: probably needs to be fixed for windows
 
 
29
    mod = os.stat(filename).st_mode
 
 
31
    os.chmod(filename, mod)
 
 
34
def make_writable(filename):
 
 
35
    mod = os.stat(filename).st_mode
 
 
37
    os.chmod(filename, mod)
 
 
44
    """Return a quoted filename filename
 
 
46
    This previously used backslash quoting, but that works poorly on
 
 
48
    # TODO: I'm not really sure this is the best format either.x
 
 
51
        _QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])')
 
 
53
    if _QUOTE_RE.search(f):
 
 
60
    mode = os.lstat(f)[ST_MODE]
 
 
68
        raise BzrError("can't handle file kind with mode %o of %r" % (mode, f))
 
 
71
def kind_marker(kind):
 
 
74
    elif kind == 'directory':
 
 
76
    elif kind == 'symlink':
 
 
79
        raise BzrError('invalid file kind %r' % kind)
 
 
84
    """Copy a file to a backup.
 
 
86
    Backups are named in GNU-style, with a ~ suffix.
 
 
88
    If the file is already a backup, it's not copied.
 
 
101
    outf = file(bfn, 'wb')
 
 
107
def rename(path_from, path_to):
 
 
108
    """Basically the same as os.rename() just special for win32"""
 
 
109
    if sys.platform == 'win32':
 
 
113
            if e.errno != e.ENOENT:
 
 
115
    os.rename(path_from, path_to)
 
 
122
    """True if f is an accessible directory."""
 
 
124
        return S_ISDIR(os.lstat(f)[ST_MODE])
 
 
131
    """True if f is a regular file."""
 
 
133
        return S_ISREG(os.lstat(f)[ST_MODE])
 
 
138
def is_inside(dir, fname):
 
 
139
    """True if fname is inside dir.
 
 
141
    The parameters should typically be passed to os.path.normpath first, so
 
 
142
    that . and .. and repeated slashes are eliminated, and the separators
 
 
143
    are canonical for the platform.
 
 
145
    The empty string as a dir name is taken as top-of-tree and matches 
 
 
148
    >>> is_inside('src', 'src/foo.c')
 
 
150
    >>> is_inside('src', 'srccontrol')
 
 
152
    >>> is_inside('src', 'src/a/a/a/foo.c')
 
 
154
    >>> is_inside('foo.c', 'foo.c')
 
 
156
    >>> is_inside('foo.c', '')
 
 
158
    >>> is_inside('', 'foo.c')
 
 
161
    # XXX: Most callers of this can actually do something smarter by 
 
 
162
    # looking at the inventory
 
 
169
    if dir[-1] != os.sep:
 
 
172
    return fname.startswith(dir)
 
 
175
def is_inside_any(dir_list, fname):
 
 
176
    """True if fname is inside any of given dirs."""
 
 
177
    for dirname in dir_list:
 
 
178
        if is_inside(dirname, fname):
 
 
184
def pumpfile(fromfile, tofile):
 
 
185
    """Copy contents of one file to another."""
 
 
186
    tofile.write(fromfile.read())
 
 
190
    """Return a new UUID"""
 
 
192
        return file('/proc/sys/kernel/random/uuid').readline().rstrip('\n')
 
 
194
        return chomp(os.popen('uuidgen').readline())
 
 
199
    if hasattr(f, 'tell'):
 
 
219
def fingerprint_file(f):
 
 
225
    return {'size': size,
 
 
226
            'sha1': s.hexdigest()}
 
 
230
    """Return per-user configuration directory.
 
 
232
    By default this is ~/.bzr.conf/
 
 
234
    TODO: Global option --config-dir to override this.
 
 
236
    return os.path.expanduser("~/.bzr.conf")
 
 
240
    """Calculate automatic user identification.
 
 
242
    Returns (realname, email).
 
 
244
    Only used when none is set in the environment or the id file.
 
 
246
    This previously used the FQDN as the default domain, but that can
 
 
247
    be very slow on machines where DNS is broken.  So now we simply
 
 
252
    # XXX: Any good way to get real user name on win32?
 
 
257
        w = pwd.getpwuid(uid)
 
 
258
        gecos = w.pw_gecos.decode(bzrlib.user_encoding)
 
 
259
        username = w.pw_name.decode(bzrlib.user_encoding)
 
 
260
        comma = gecos.find(',')
 
 
264
            realname = gecos[:comma]
 
 
270
        realname = username = getpass.getuser().decode(bzrlib.user_encoding)
 
 
272
    return realname, (username + '@' + socket.gethostname())
 
 
275
def _get_user_id(branch):
 
 
276
    """Return the full user id from a file or environment variable.
 
 
278
    e.g. "John Hacker <jhacker@foo.org>"
 
 
281
        A branch to use for a per-branch configuration, or None.
 
 
283
    The following are searched in order:
 
 
286
    2. .bzr/email for this branch.
 
 
290
    v = os.environ.get('BZREMAIL')
 
 
292
        return v.decode(bzrlib.user_encoding)
 
 
296
            return (branch.controlfile("email", "r") 
 
 
298
                    .decode(bzrlib.user_encoding)
 
 
301
            if e.errno != errno.ENOENT:
 
 
307
        return (open(os.path.join(config_dir(), "email"))
 
 
309
                .decode(bzrlib.user_encoding)
 
 
312
        if e.errno != errno.ENOENT:
 
 
315
    v = os.environ.get('EMAIL')
 
 
317
        return v.decode(bzrlib.user_encoding)
 
 
322
def username(branch):
 
 
323
    """Return email-style username.
 
 
325
    Something similar to 'Martin Pool <mbp@sourcefrog.net>'
 
 
327
    TODO: Check it's reasonably well-formed.
 
 
329
    v = _get_user_id(branch)
 
 
333
    name, email = _auto_user_id()
 
 
335
        return '%s <%s>' % (name, email)
 
 
340
def user_email(branch):
 
 
341
    """Return just the email component of a username."""
 
 
342
    e = _get_user_id(branch)
 
 
344
        m = re.search(r'[\w+.-]+@[\w+.-]+', e)
 
 
346
            raise BzrError("%r doesn't seem to contain a reasonable email address" % e)
 
 
349
    return _auto_user_id()[1]
 
 
353
def compare_files(a, b):
 
 
354
    """Returns true if equal in contents"""
 
 
366
def local_time_offset(t=None):
 
 
367
    """Return offset of local zone from GMT, either at present or at time t."""
 
 
368
    # python2.3 localtime() can't take None
 
 
372
    if time.localtime(t).tm_isdst and time.daylight:
 
 
375
        return -time.timezone
 
 
378
def format_date(t, offset=0, timezone='original'):
 
 
379
    ## TODO: Perhaps a global option to use either universal or local time?
 
 
380
    ## Or perhaps just let people set $TZ?
 
 
381
    assert isinstance(t, float)
 
 
383
    if timezone == 'utc':
 
 
386
    elif timezone == 'original':
 
 
389
        tt = time.gmtime(t + offset)
 
 
390
    elif timezone == 'local':
 
 
391
        tt = time.localtime(t)
 
 
392
        offset = local_time_offset(t)
 
 
394
        raise BzrError("unsupported timezone format %r" % timezone,
 
 
395
                       ['options are "utc", "original", "local"'])
 
 
397
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
 
 
398
            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
 
 
401
def compact_date(when):
 
 
402
    return time.strftime('%Y%m%d%H%M%S', time.gmtime(when))
 
 
407
    """Return size of given open file."""
 
 
408
    return os.fstat(f.fileno())[ST_SIZE]
 
 
411
if hasattr(os, 'urandom'): # python 2.4 and later
 
 
412
    rand_bytes = os.urandom
 
 
413
elif sys.platform == 'linux2':
 
 
414
    rand_bytes = file('/dev/urandom', 'rb').read
 
 
416
    # not well seeded, but better than nothing
 
 
421
            s += chr(random.randint(0, 255))
 
 
426
## TODO: We could later have path objects that remember their list
 
 
427
## decomposition (might be too tricksy though.)
 
 
430
    """Turn string into list of parts.
 
 
436
    >>> splitpath('a/./b')
 
 
438
    >>> splitpath('a/.b')
 
 
440
    >>> splitpath('a/../b')
 
 
441
    Traceback (most recent call last):
 
 
443
    BzrError: sorry, '..' not allowed in path
 
 
445
    assert isinstance(p, types.StringTypes)
 
 
447
    # split on either delimiter because people might use either on
 
 
449
    ps = re.split(r'[\\/]', p)
 
 
454
            raise BzrError("sorry, %r not allowed in path" % f)
 
 
455
        elif (f == '.') or (f == ''):
 
 
462
    assert isinstance(p, list)
 
 
464
        if (f == '..') or (f == None) or (f == ''):
 
 
465
            raise BzrError("sorry, %r not allowed in path" % f)
 
 
466
    return os.path.join(*p)
 
 
469
def appendpath(p1, p2):
 
 
473
        return os.path.join(p1, p2)
 
 
476
def extern_command(cmd, ignore_errors = False):
 
 
477
    mutter('external command: %s' % `cmd`)
 
 
479
        if not ignore_errors:
 
 
480
            raise BzrError('command failed')
 
 
483
def _read_config_value(name):
 
 
484
    """Read a config value from the file ~/.bzr.conf/<name>
 
 
485
    Return None if the file does not exist"""
 
 
487
        f = file(os.path.join(config_dir(), name), "r")
 
 
488
        return f.read().decode(bzrlib.user_encoding).rstrip("\r\n")
 
 
490
        if e.errno == errno.ENOENT: