~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Robert Collins
  • Date: 2005-10-18 13:11:57 UTC
  • mfrom: (1185.16.72) (0.2.1)
  • Revision ID: robertc@robertcollins.net-20051018131157-76a9970aa78e927e
Merged Martin.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Bazaar-NG -- distributed version control
2
 
 
 
2
#
3
3
# Copyright (C) 2005 by Canonical Ltd
4
 
 
 
4
#
5
5
# This program is free software; you can redistribute it and/or modify
6
6
# it under the terms of the GNU General Public License as published by
7
7
# the Free Software Foundation; either version 2 of the License, or
8
8
# (at your option) any later version.
9
 
 
 
9
#
10
10
# This program is distributed in the hope that it will be useful,
11
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
13
# GNU General Public License for more details.
14
 
 
 
14
#
15
15
# You should have received a copy of the GNU General Public License
16
16
# along with this program; if not, write to the Free Software
17
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
18
 
19
 
import os, types, re, time, errno, sys
 
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
 
23
import errno
 
24
import os
 
25
import re
20
26
import sha
21
 
from cStringIO import StringIO
22
 
 
23
 
from stat import S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE
24
 
 
25
 
from bzrlib.errors import BzrError
 
27
import string
 
28
import sys
 
29
import time
 
30
import types
 
31
 
 
32
import bzrlib
 
33
from bzrlib.errors import BzrError, NotBranchError
26
34
from bzrlib.trace import mutter
27
 
import bzrlib
 
35
 
28
36
 
29
37
def make_readonly(filename):
30
38
    """Make a filename read-only."""
31
 
    # TODO: probably needs to be fixed for windows
32
39
    mod = os.stat(filename).st_mode
33
40
    mod = mod & 0777555
34
41
    os.chmod(filename, mod)
51
58
    # TODO: I'm not really sure this is the best format either.x
52
59
    global _QUOTE_RE
53
60
    if _QUOTE_RE == None:
54
 
        _QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])')
 
61
        _QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/\\_~-])')
55
62
        
56
63
    if _QUOTE_RE.search(f):
57
64
        return '"' + f + '"'
67
74
        return 'directory'
68
75
    elif S_ISLNK(mode):
69
76
        return 'symlink'
 
77
    elif S_ISCHR(mode):
 
78
        return 'chardev'
 
79
    elif S_ISBLK(mode):
 
80
        return 'block'
 
81
    elif S_ISFIFO(mode):
 
82
        return 'fifo'
 
83
    elif S_ISSOCK(mode):
 
84
        return 'socket'
70
85
    else:
71
 
        raise BzrError("can't handle file kind with mode %o of %r" % (mode, f))
 
86
        return 'unknown'
72
87
 
73
88
 
74
89
def kind_marker(kind):
81
96
    else:
82
97
        raise BzrError('invalid file kind %r' % kind)
83
98
 
84
 
 
 
99
def lexists(f):
 
100
    try:
 
101
        if hasattr(os, 'lstat'):
 
102
            os.lstat(f)
 
103
        else:
 
104
            os.stat(f)
 
105
        return True
 
106
    except OSError,e:
 
107
        if e.errno == errno.ENOENT:
 
108
            return False;
 
109
        else:
 
110
            raise BzrError("lstat/stat of (%r): %r" % (f, e))
 
111
 
 
112
def normalizepath(f):
 
113
    if hasattr(os.path, 'realpath'):
 
114
        F = os.path.realpath
 
115
    else:
 
116
        F = os.path.abspath
 
117
    [p,e] = os.path.split(f)
 
118
    if e == "" or e == "." or e == "..":
 
119
        return F(f)
 
120
    else:
 
121
        return os.path.join(F(p), e)
 
122
 
 
123
if os.name == "posix":
 
124
    # In Python 2.4.2 and older, os.path.abspath and os.path.realpath
 
125
    # choke on a Unicode string containing a relative path if
 
126
    # os.getcwd() returns a non-sys.getdefaultencoding()-encoded
 
127
    # string.
 
128
    _fs_enc = sys.getfilesystemencoding()
 
129
    def abspath(path):
 
130
        return os.path.abspath(path.encode(_fs_enc)).decode(_fs_enc)
 
131
    def realpath(path):
 
132
        return os.path.realpath(path.encode(_fs_enc)).decode(_fs_enc)
 
133
else:
 
134
    # We need to use the Unicode-aware os.path.abspath and
 
135
    # os.path.realpath on Windows systems.
 
136
    abspath = os.path.abspath
 
137
    realpath = os.path.realpath
85
138
 
86
139
def backup_file(fn):
87
140
    """Copy a file to a backup.
94
147
        return
95
148
    bfn = fn + '~'
96
149
 
 
150
    if has_symlinks() and os.path.islink(fn):
 
151
        target = os.readlink(fn)
 
152
        os.symlink(target, bfn)
 
153
        return
97
154
    inf = file(fn, 'rb')
98
155
    try:
99
156
        content = inf.read()
106
163
    finally:
107
164
        outf.close()
108
165
 
109
 
def rename(path_from, path_to):
110
 
    """Basically the same as os.rename() just special for win32"""
111
 
    if sys.platform == 'win32':
112
 
        try:
113
 
            os.remove(path_to)
114
 
        except OSError, e:
115
 
            if e.errno != e.ENOENT:
116
 
                raise
117
 
    os.rename(path_from, path_to)
118
 
 
119
 
 
120
 
 
 
166
if os.name == 'nt':
 
167
    import shutil
 
168
    rename = shutil.move
 
169
else:
 
170
    rename = os.rename
121
171
 
122
172
 
123
173
def isdir(f):
128
178
        return False
129
179
 
130
180
 
131
 
 
132
181
def isfile(f):
133
182
    """True if f is a regular file."""
134
183
    try:
136
185
    except OSError:
137
186
        return False
138
187
 
 
188
def islink(f):
 
189
    """True if f is a symlink."""
 
190
    try:
 
191
        return S_ISLNK(os.lstat(f)[ST_MODE])
 
192
    except OSError:
 
193
        return False
139
194
 
140
195
def is_inside(dir, fname):
141
196
    """True if fname is inside dir.
147
202
    The empty string as a dir name is taken as top-of-tree and matches 
148
203
    everything.
149
204
    
150
 
    >>> is_inside('src', 'src/foo.c')
 
205
    >>> is_inside('src', os.path.join('src', 'foo.c'))
151
206
    True
152
207
    >>> is_inside('src', 'srccontrol')
153
208
    False
154
 
    >>> is_inside('src', 'src/a/a/a/foo.c')
 
209
    >>> is_inside('src', os.path.join('src', 'a', 'a', 'a', 'foo.c'))
155
210
    True
156
211
    >>> is_inside('foo.c', 'foo.c')
157
212
    True
167
222
    
168
223
    if dir == '':
169
224
        return True
170
 
    
 
225
 
171
226
    if dir[-1] != os.sep:
172
227
        dir += os.sep
173
 
    
 
228
 
174
229
    return fname.startswith(dir)
175
230
 
176
231
 
188
243
    tofile.write(fromfile.read())
189
244
 
190
245
 
191
 
def uuid():
192
 
    """Return a new UUID"""
193
 
    try:
194
 
        return file('/proc/sys/kernel/random/uuid').readline().rstrip('\n')
195
 
    except IOError:
196
 
        return chomp(os.popen('uuidgen').readline())
197
 
 
198
 
 
199
246
def sha_file(f):
200
247
    if hasattr(f, 'tell'):
201
248
        assert f.tell() == 0
223
270
    return s.hexdigest()
224
271
 
225
272
 
226
 
 
227
273
def fingerprint_file(f):
228
274
    s = sha.new()
229
275
    b = f.read()
233
279
            'sha1': s.hexdigest()}
234
280
 
235
281
 
236
 
def config_dir():
237
 
    """Return per-user configuration directory.
238
 
 
239
 
    By default this is ~/.bzr.conf/
240
 
    
241
 
    TODO: Global option --config-dir to override this.
242
 
    """
243
 
    return os.path.expanduser("~/.bzr.conf")
244
 
 
245
 
 
246
 
def _auto_user_id():
247
 
    """Calculate automatic user identification.
248
 
 
249
 
    Returns (realname, email).
250
 
 
251
 
    Only used when none is set in the environment or the id file.
252
 
 
253
 
    This previously used the FQDN as the default domain, but that can
254
 
    be very slow on machines where DNS is broken.  So now we simply
255
 
    use the hostname.
256
 
    """
257
 
    import socket
258
 
 
259
 
    # XXX: Any good way to get real user name on win32?
260
 
 
261
 
    try:
262
 
        import pwd
263
 
        uid = os.getuid()
264
 
        w = pwd.getpwuid(uid)
265
 
        gecos = w.pw_gecos.decode(bzrlib.user_encoding)
266
 
        username = w.pw_name.decode(bzrlib.user_encoding)
267
 
        comma = gecos.find(',')
268
 
        if comma == -1:
269
 
            realname = gecos
270
 
        else:
271
 
            realname = gecos[:comma]
272
 
        if not realname:
273
 
            realname = username
274
 
 
275
 
    except ImportError:
276
 
        import getpass
277
 
        realname = username = getpass.getuser().decode(bzrlib.user_encoding)
278
 
 
279
 
    return realname, (username + '@' + socket.gethostname())
280
 
 
281
 
 
282
 
def _get_user_id(branch):
283
 
    """Return the full user id from a file or environment variable.
284
 
 
285
 
    e.g. "John Hacker <jhacker@foo.org>"
286
 
 
287
 
    branch
288
 
        A branch to use for a per-branch configuration, or None.
289
 
 
290
 
    The following are searched in order:
291
 
 
292
 
    1. $BZREMAIL
293
 
    2. .bzr/email for this branch.
294
 
    3. ~/.bzr.conf/email
295
 
    4. $EMAIL
296
 
    """
297
 
    v = os.environ.get('BZREMAIL')
298
 
    if v:
299
 
        return v.decode(bzrlib.user_encoding)
300
 
 
301
 
    if branch:
302
 
        try:
303
 
            return (branch.controlfile("email", "r") 
304
 
                    .read()
305
 
                    .decode(bzrlib.user_encoding)
306
 
                    .rstrip("\r\n"))
307
 
        except IOError, e:
308
 
            if e.errno != errno.ENOENT:
309
 
                raise
310
 
        except BzrError, e:
311
 
            pass
312
 
    
313
 
    try:
314
 
        return (open(os.path.join(config_dir(), "email"))
315
 
                .read()
316
 
                .decode(bzrlib.user_encoding)
317
 
                .rstrip("\r\n"))
318
 
    except IOError, e:
319
 
        if e.errno != errno.ENOENT:
320
 
            raise e
321
 
 
322
 
    v = os.environ.get('EMAIL')
323
 
    if v:
324
 
        return v.decode(bzrlib.user_encoding)
325
 
    else:    
326
 
        return None
327
 
 
328
 
 
329
 
def username(branch):
330
 
    """Return email-style username.
331
 
 
332
 
    Something similar to 'Martin Pool <mbp@sourcefrog.net>'
333
 
 
334
 
    TODO: Check it's reasonably well-formed.
335
 
    """
336
 
    v = _get_user_id(branch)
337
 
    if v:
338
 
        return v
339
 
    
340
 
    name, email = _auto_user_id()
341
 
    if name:
342
 
        return '%s <%s>' % (name, email)
343
 
    else:
344
 
        return email
345
 
 
346
 
 
347
 
def user_email(branch):
348
 
    """Return just the email component of a username."""
349
 
    e = _get_user_id(branch)
350
 
    if e:
351
 
        m = re.search(r'[\w+.-]+@[\w+.-]+', e)
352
 
        if not m:
353
 
            raise BzrError("%r doesn't seem to contain a reasonable email address" % e)
354
 
        return m.group(0)
355
 
 
356
 
    return _auto_user_id()[1]
357
 
    
358
 
 
359
 
 
360
282
def compare_files(a, b):
361
283
    """Returns true if equal in contents"""
362
284
    BUFSIZE = 4096
369
291
            return True
370
292
 
371
293
 
372
 
 
373
294
def local_time_offset(t=None):
374
295
    """Return offset of local zone from GMT, either at present or at time t."""
375
296
    # python2.3 localtime() can't take None
382
303
        return -time.timezone
383
304
 
384
305
    
385
 
def format_date(t, offset=0, timezone='original'):
 
306
def format_date(t, offset=0, timezone='original', date_fmt=None, 
 
307
                show_offset=True):
386
308
    ## TODO: Perhaps a global option to use either universal or local time?
387
309
    ## Or perhaps just let people set $TZ?
388
310
    assert isinstance(t, float)
400
322
    else:
401
323
        raise BzrError("unsupported timezone format %r" % timezone,
402
324
                       ['options are "utc", "original", "local"'])
403
 
 
404
 
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
405
 
            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
 
325
    if date_fmt is None:
 
326
        date_fmt = "%a %Y-%m-%d %H:%M:%S"
 
327
    if show_offset:
 
328
        offset_str = ' %+03d%02d' % (offset / 3600, (offset / 60) % 60)
 
329
    else:
 
330
        offset_str = ''
 
331
    return (time.strftime(date_fmt, tt) +  offset_str)
406
332
 
407
333
 
408
334
def compact_date(when):
414
340
    """Return size of given open file."""
415
341
    return os.fstat(f.fileno())[ST_SIZE]
416
342
 
417
 
 
418
 
if hasattr(os, 'urandom'): # python 2.4 and later
 
343
# Define rand_bytes based on platform.
 
344
try:
 
345
    # Python 2.4 and later have os.urandom,
 
346
    # but it doesn't work on some arches
 
347
    os.urandom(1)
419
348
    rand_bytes = os.urandom
420
 
elif sys.platform == 'linux2':
421
 
    rand_bytes = file('/dev/urandom', 'rb').read
422
 
else:
423
 
    # not well seeded, but better than nothing
424
 
    def rand_bytes(n):
425
 
        import random
426
 
        s = ''
427
 
        while n:
428
 
            s += chr(random.randint(0, 255))
429
 
            n -= 1
430
 
        return s
431
 
 
 
349
except (NotImplementedError, AttributeError):
 
350
    # If python doesn't have os.urandom, or it doesn't work,
 
351
    # then try to first pull random data from /dev/urandom
 
352
    if os.path.exists("/dev/urandom"):
 
353
        rand_bytes = file('/dev/urandom', 'rb').read
 
354
    # Otherwise, use this hack as a last resort
 
355
    else:
 
356
        # not well seeded, but better than nothing
 
357
        def rand_bytes(n):
 
358
            import random
 
359
            s = ''
 
360
            while n:
 
361
                s += chr(random.randint(0, 255))
 
362
                n -= 1
 
363
            return s
432
364
 
433
365
## TODO: We could later have path objects that remember their list
434
366
## decomposition (might be too tricksy though.)
480
412
        return os.path.join(p1, p2)
481
413
    
482
414
 
483
 
def extern_command(cmd, ignore_errors = False):
484
 
    mutter('external command: %s' % `cmd`)
485
 
    if os.system(cmd):
486
 
        if not ignore_errors:
487
 
            raise BzrError('command failed')
488
 
 
489
 
 
490
 
def _read_config_value(name):
491
 
    """Read a config value from the file ~/.bzr.conf/<name>
492
 
    Return None if the file does not exist"""
493
 
    try:
494
 
        f = file(os.path.join(config_dir(), name), "r")
495
 
        return f.read().decode(bzrlib.user_encoding).rstrip("\r\n")
496
 
    except IOError, e:
497
 
        if e.errno == errno.ENOENT:
498
 
            return None
499
 
        raise
500
 
 
501
 
 
502
 
 
503
415
def split_lines(s):
504
416
    """Split s into lines, but without removing the newline characters."""
505
417
    return StringIO(s).readlines()
506
 
    
 
418
 
 
419
 
 
420
def hardlinks_good():
 
421
    return sys.platform not in ('win32', 'cygwin', 'darwin')
 
422
 
 
423
 
 
424
def link_or_copy(src, dest):
 
425
    """Hardlink a file, or copy it if it can't be hardlinked."""
 
426
    if not hardlinks_good():
 
427
        copyfile(src, dest)
 
428
        return
 
429
    try:
 
430
        os.link(src, dest)
 
431
    except (OSError, IOError), e:
 
432
        if e.errno != errno.EXDEV:
 
433
            raise
 
434
        copyfile(src, dest)
 
435
 
 
436
 
 
437
def has_symlinks():
 
438
    if hasattr(os, 'symlink'):
 
439
        return True
 
440
    else:
 
441
        return False
 
442
        
 
443
 
 
444
def contains_whitespace(s):
 
445
    """True if there are any whitespace characters in s."""
 
446
    for ch in string.whitespace:
 
447
        if ch in s:
 
448
            return True
 
449
    else:
 
450
        return False
 
451
 
 
452
 
 
453
def contains_linebreaks(s):
 
454
    """True if there is any vertical whitespace in s."""
 
455
    for ch in '\f\n\r':
 
456
        if ch in s:
 
457
            return True
 
458
    else:
 
459
        return False
 
460
 
 
461
 
 
462
def relpath(base, path):
 
463
    """Return path relative to base, or raise exception.
 
464
 
 
465
    The path may be either an absolute path or a path relative to the
 
466
    current working directory.
 
467
 
 
468
    os.path.commonprefix (python2.4) has a bad bug that it works just
 
469
    on string prefixes, assuming that '/u' is a prefix of '/u2'.  This
 
470
    avoids that problem."""
 
471
    rp = abspath(path)
 
472
 
 
473
    s = []
 
474
    head = rp
 
475
    while len(head) >= len(base):
 
476
        if head == base:
 
477
            break
 
478
        head, tail = os.path.split(head)
 
479
        if tail:
 
480
            s.insert(0, tail)
 
481
    else:
 
482
        # XXX This should raise a NotChildPath exception, as its not tied
 
483
        # to branch anymore.
 
484
        raise NotBranchError("path %r is not within branch %r" % (rp, base))
 
485
 
 
486
    return os.sep.join(s)