~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Robert Collins
  • Date: 2005-09-27 06:15:35 UTC
  • mto: (1092.3.4)
  • mto: This revision was merged to the branch mainline in revision 1391.
  • Revision ID: robertc@robertcollins.net-20050927061535-6321f7191439d108
print test case exceptions after the test log

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, types
20
 
from stat import S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE
21
 
 
22
 
from errors import bailout
 
19
from stat import (S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE,
 
20
                  S_ISCHR, S_ISBLK, S_ISFIFO, S_ISSOCK)
 
21
import errno
 
22
import os
 
23
import re
 
24
import sha
 
25
import sys
 
26
import time
 
27
import types
 
28
 
 
29
import bzrlib
 
30
from bzrlib.errors import BzrError
 
31
from bzrlib.trace import mutter
 
32
 
23
33
 
24
34
def make_readonly(filename):
25
35
    """Make a filename read-only."""
26
 
    # TODO: probably needs to be fixed for windows
27
36
    mod = os.stat(filename).st_mode
28
37
    mod = mod & 0777555
29
38
    os.chmod(filename, mod)
35
44
    os.chmod(filename, mod)
36
45
 
37
46
 
38
 
_QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])')
 
47
_QUOTE_RE = None
 
48
 
 
49
 
39
50
def quotefn(f):
40
 
    """Return shell-quoted filename"""
41
 
    ## We could be a bit more terse by using double-quotes etc
42
 
    f = _QUOTE_RE.sub(r'\\\1', f)
43
 
    if f[0] == '~':
44
 
        f[0:1] = r'\~' 
45
 
    return f
 
51
    """Return a quoted filename filename
 
52
 
 
53
    This previously used backslash quoting, but that works poorly on
 
54
    Windows."""
 
55
    # TODO: I'm not really sure this is the best format either.x
 
56
    global _QUOTE_RE
 
57
    if _QUOTE_RE == None:
 
58
        _QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/\\_~-])')
 
59
        
 
60
    if _QUOTE_RE.search(f):
 
61
        return '"' + f + '"'
 
62
    else:
 
63
        return f
46
64
 
47
65
 
48
66
def file_kind(f):
53
71
        return 'directory'
54
72
    elif S_ISLNK(mode):
55
73
        return 'symlink'
56
 
    else:
57
 
        bailout("can't handle file kind with mode %o of %r" % (mode, f)) 
58
 
 
 
74
    elif S_ISCHR(mode):
 
75
        return 'chardev'
 
76
    elif S_ISBLK(mode):
 
77
        return 'block'
 
78
    elif S_ISFIFO(mode):
 
79
        return 'fifo'
 
80
    elif S_ISSOCK(mode):
 
81
        return 'socket'
 
82
    else:
 
83
        return 'unknown'
 
84
 
 
85
 
 
86
def kind_marker(kind):
 
87
    if kind == 'file':
 
88
        return ''
 
89
    elif kind == 'directory':
 
90
        return '/'
 
91
    elif kind == 'symlink':
 
92
        return '@'
 
93
    else:
 
94
        raise BzrError('invalid file kind %r' % kind)
 
95
 
 
96
 
 
97
def backup_file(fn):
 
98
    """Copy a file to a backup.
 
99
 
 
100
    Backups are named in GNU-style, with a ~ suffix.
 
101
 
 
102
    If the file is already a backup, it's not copied.
 
103
    """
 
104
    if fn[-1] == '~':
 
105
        return
 
106
    bfn = fn + '~'
 
107
 
 
108
    inf = file(fn, 'rb')
 
109
    try:
 
110
        content = inf.read()
 
111
    finally:
 
112
        inf.close()
 
113
    
 
114
    outf = file(bfn, 'wb')
 
115
    try:
 
116
        outf.write(content)
 
117
    finally:
 
118
        outf.close()
 
119
 
 
120
if os.name == 'nt':
 
121
    import shutil
 
122
    rename = shutil.move
 
123
else:
 
124
    rename = os.rename
59
125
 
60
126
 
61
127
def isdir(f):
66
132
        return False
67
133
 
68
134
 
69
 
 
70
135
def isfile(f):
71
136
    """True if f is a regular file."""
72
137
    try:
75
140
        return False
76
141
 
77
142
 
 
143
def is_inside(dir, fname):
 
144
    """True if fname is inside dir.
 
145
    
 
146
    The parameters should typically be passed to os.path.normpath first, so
 
147
    that . and .. and repeated slashes are eliminated, and the separators
 
148
    are canonical for the platform.
 
149
    
 
150
    The empty string as a dir name is taken as top-of-tree and matches 
 
151
    everything.
 
152
    
 
153
    >>> is_inside('src', os.path.join('src', 'foo.c'))
 
154
    True
 
155
    >>> is_inside('src', 'srccontrol')
 
156
    False
 
157
    >>> is_inside('src', os.path.join('src', 'a', 'a', 'a', 'foo.c'))
 
158
    True
 
159
    >>> is_inside('foo.c', 'foo.c')
 
160
    True
 
161
    >>> is_inside('foo.c', '')
 
162
    False
 
163
    >>> is_inside('', 'foo.c')
 
164
    True
 
165
    """
 
166
    # XXX: Most callers of this can actually do something smarter by 
 
167
    # looking at the inventory
 
168
    if dir == fname:
 
169
        return True
 
170
    
 
171
    if dir == '':
 
172
        return True
 
173
 
 
174
    if dir[-1] != os.sep:
 
175
        dir += os.sep
 
176
 
 
177
    return fname.startswith(dir)
 
178
 
 
179
 
 
180
def is_inside_any(dir_list, fname):
 
181
    """True if fname is inside any of given dirs."""
 
182
    for dirname in dir_list:
 
183
        if is_inside(dirname, fname):
 
184
            return True
 
185
    else:
 
186
        return False
 
187
 
 
188
 
78
189
def pumpfile(fromfile, tofile):
79
190
    """Copy contents of one file to another."""
80
191
    tofile.write(fromfile.read())
81
192
 
82
193
 
83
 
def uuid():
84
 
    """Return a new UUID"""
85
 
    
86
 
    ## XXX: Could alternatively read /proc/sys/kernel/random/uuid on
87
 
    ## Linux, but we need something portable for other systems;
88
 
    ## preferably an implementation in Python.
89
 
    try:
90
 
        return chomp(file('/proc/sys/kernel/random/uuid').readline())
91
 
    except IOError:
92
 
        return chomp(os.popen('uuidgen').readline())
93
 
 
94
 
 
95
 
def chomp(s):
96
 
    if s and (s[-1] == '\n'):
97
 
        return s[:-1]
98
 
    else:
99
 
        return s
100
 
 
101
 
 
102
194
def sha_file(f):
103
 
    import sha
104
 
    ## TODO: Maybe read in chunks to handle big files
105
195
    if hasattr(f, 'tell'):
106
196
        assert f.tell() == 0
107
197
    s = sha.new()
108
 
    s.update(f.read())
 
198
    BUFSIZE = 128<<10
 
199
    while True:
 
200
        b = f.read(BUFSIZE)
 
201
        if not b:
 
202
            break
 
203
        s.update(b)
109
204
    return s.hexdigest()
110
205
 
111
206
 
112
207
def sha_string(f):
113
 
    import sha
114
208
    s = sha.new()
115
209
    s.update(f)
116
210
    return s.hexdigest()
117
211
 
118
212
 
119
 
 
120
 
def username():
121
 
    """Return email-style username.
122
 
 
123
 
    Something similar to 'Martin Pool <mbp@sourcefrog.net>'
124
 
 
125
 
    :todo: Check it's reasonably well-formed.
126
 
 
127
 
    :todo: Allow taking it from a dotfile to help people on windows
128
 
           who can't easily set variables.
129
 
 
130
 
    :todo: Cope without pwd module, which is only on unix. 
131
 
    """
132
 
    e = os.environ.get('BZREMAIL') or os.environ.get('EMAIL')
133
 
    if e: return e
134
 
 
 
213
def fingerprint_file(f):
 
214
    s = sha.new()
 
215
    b = f.read()
 
216
    s.update(b)
 
217
    size = len(b)
 
218
    return {'size': size,
 
219
            'sha1': s.hexdigest()}
 
220
 
 
221
 
 
222
def config_dir():
 
223
    """Return per-user configuration directory.
 
224
 
 
225
    By default this is ~/.bzr.conf/
 
226
    
 
227
    TODO: Global option --config-dir to override this.
 
228
    """
 
229
    return os.path.join(os.path.expanduser("~"), ".bzr.conf")
 
230
 
 
231
 
 
232
def _auto_user_id():
 
233
    """Calculate automatic user identification.
 
234
 
 
235
    Returns (realname, email).
 
236
 
 
237
    Only used when none is set in the environment or the id file.
 
238
 
 
239
    This previously used the FQDN as the default domain, but that can
 
240
    be very slow on machines where DNS is broken.  So now we simply
 
241
    use the hostname.
 
242
    """
135
243
    import socket
136
 
    
 
244
 
 
245
    # XXX: Any good way to get real user name on win32?
 
246
 
137
247
    try:
138
248
        import pwd
139
249
        uid = os.getuid()
140
250
        w = pwd.getpwuid(uid)
141
 
        gecos = w.pw_gecos
 
251
        gecos = w.pw_gecos.decode(bzrlib.user_encoding)
 
252
        username = w.pw_name.decode(bzrlib.user_encoding)
142
253
        comma = gecos.find(',')
143
254
        if comma == -1:
144
255
            realname = gecos
145
256
        else:
146
257
            realname = gecos[:comma]
147
 
        return '%s <%s@%s>' % (realname, w.pw_name, socket.getfqdn())
 
258
        if not realname:
 
259
            realname = username
 
260
 
148
261
    except ImportError:
149
 
        pass
150
 
 
151
 
    import getpass, socket
152
 
    return '<%s@%s>' % (getpass.getuser(), socket.getfqdn())
153
 
 
154
 
 
155
 
def user_email():
 
262
        import getpass
 
263
        realname = username = getpass.getuser().decode(bzrlib.user_encoding)
 
264
 
 
265
    return realname, (username + '@' + socket.gethostname())
 
266
 
 
267
 
 
268
def _get_user_id(branch):
 
269
    """Return the full user id from a file or environment variable.
 
270
 
 
271
    e.g. "John Hacker <jhacker@foo.org>"
 
272
 
 
273
    branch
 
274
        A branch to use for a per-branch configuration, or None.
 
275
 
 
276
    The following are searched in order:
 
277
 
 
278
    1. $BZREMAIL
 
279
    2. .bzr/email for this branch.
 
280
    3. ~/.bzr.conf/email
 
281
    4. $EMAIL
 
282
    """
 
283
    v = os.environ.get('BZREMAIL')
 
284
    if v:
 
285
        return v.decode(bzrlib.user_encoding)
 
286
 
 
287
    if branch:
 
288
        try:
 
289
            return (branch.controlfile("email", "r") 
 
290
                    .read()
 
291
                    .decode(bzrlib.user_encoding)
 
292
                    .rstrip("\r\n"))
 
293
        except IOError, e:
 
294
            if e.errno != errno.ENOENT:
 
295
                raise
 
296
        except BzrError, e:
 
297
            pass
 
298
    
 
299
    try:
 
300
        return (open(os.path.join(config_dir(), "email"))
 
301
                .read()
 
302
                .decode(bzrlib.user_encoding)
 
303
                .rstrip("\r\n"))
 
304
    except IOError, e:
 
305
        if e.errno != errno.ENOENT:
 
306
            raise e
 
307
 
 
308
    v = os.environ.get('EMAIL')
 
309
    if v:
 
310
        return v.decode(bzrlib.user_encoding)
 
311
    else:    
 
312
        return None
 
313
 
 
314
 
 
315
def username(branch):
 
316
    """Return email-style username.
 
317
 
 
318
    Something similar to 'Martin Pool <mbp@sourcefrog.net>'
 
319
 
 
320
    TODO: Check it's reasonably well-formed.
 
321
    """
 
322
    v = _get_user_id(branch)
 
323
    if v:
 
324
        return v
 
325
    
 
326
    name, email = _auto_user_id()
 
327
    if name:
 
328
        return '%s <%s>' % (name, email)
 
329
    else:
 
330
        return email
 
331
 
 
332
 
 
333
def user_email(branch):
156
334
    """Return just the email component of a username."""
157
 
    e = os.environ.get('BZREMAIL') or os.environ.get('EMAIL')
 
335
    e = _get_user_id(branch)
158
336
    if e:
159
 
        import re
160
337
        m = re.search(r'[\w+.-]+@[\w+.-]+', e)
161
338
        if not m:
162
 
            bailout('%r is not a reasonable email address' % e)
 
339
            raise BzrError("%r doesn't seem to contain "
 
340
                           "a reasonable email address" % e)
163
341
        return m.group(0)
164
342
 
165
 
 
166
 
    import getpass, socket
167
 
    return '%s@%s' % (getpass.getuser(), socket.getfqdn())
168
 
 
169
 
    
 
343
    return _auto_user_id()[1]
170
344
 
171
345
 
172
346
def compare_files(a, b):
173
347
    """Returns true if equal in contents"""
174
 
    # TODO: don't read the whole thing in one go.
175
348
    BUFSIZE = 4096
176
349
    while True:
177
350
        ai = a.read(BUFSIZE)
182
355
            return True
183
356
 
184
357
 
185
 
 
186
358
def local_time_offset(t=None):
187
359
    """Return offset of local zone from GMT, either at present or at time t."""
188
360
    # python2.3 localtime() can't take None
189
 
    if t is None:
 
361
    if t == None:
190
362
        t = time.time()
191
363
        
192
364
    if time.localtime(t).tm_isdst and time.daylight:
198
370
def format_date(t, offset=0, timezone='original'):
199
371
    ## TODO: Perhaps a global option to use either universal or local time?
200
372
    ## Or perhaps just let people set $TZ?
201
 
    import time
202
 
    
203
373
    assert isinstance(t, float)
204
374
    
205
375
    if timezone == 'utc':
213
383
        tt = time.localtime(t)
214
384
        offset = local_time_offset(t)
215
385
    else:
216
 
        bailout("unsupported timezone format %r",
217
 
                ['options are "utc", "original", "local"'])
 
386
        raise BzrError("unsupported timezone format %r" % timezone,
 
387
                       ['options are "utc", "original", "local"'])
218
388
 
219
389
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
220
390
            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
229
399
    """Return size of given open file."""
230
400
    return os.fstat(f.fileno())[ST_SIZE]
231
401
 
232
 
 
233
 
if hasattr(os, 'urandom'): # python 2.4 and later
 
402
# Define rand_bytes based on platform.
 
403
try:
 
404
    # Python 2.4 and later have os.urandom,
 
405
    # but it doesn't work on some arches
 
406
    os.urandom(1)
234
407
    rand_bytes = os.urandom
235
 
else:
236
 
    # FIXME: No good on non-Linux
237
 
    _rand_file = file('/dev/urandom', 'rb')
238
 
    rand_bytes = _rand_file.read
239
 
 
 
408
except (NotImplementedError, AttributeError):
 
409
    # If python doesn't have os.urandom, or it doesn't work,
 
410
    # then try to first pull random data from /dev/urandom
 
411
    if os.path.exists("/dev/urandom"):
 
412
        rand_bytes = file('/dev/urandom', 'rb').read
 
413
    # Otherwise, use this hack as a last resort
 
414
    else:
 
415
        # not well seeded, but better than nothing
 
416
        def rand_bytes(n):
 
417
            import random
 
418
            s = ''
 
419
            while n:
 
420
                s += chr(random.randint(0, 255))
 
421
                n -= 1
 
422
            return s
240
423
 
241
424
## TODO: We could later have path objects that remember their list
242
425
## decomposition (might be too tricksy though.)
255
438
    >>> splitpath('a/../b')
256
439
    Traceback (most recent call last):
257
440
    ...
258
 
    BzrError: ("sorry, '..' not allowed in path", [])
 
441
    BzrError: sorry, '..' not allowed in path
259
442
    """
260
443
    assert isinstance(p, types.StringTypes)
261
 
    ps = [f for f in p.split('/') if f != '.']
 
444
 
 
445
    # split on either delimiter because people might use either on
 
446
    # Windows
 
447
    ps = re.split(r'[\\/]', p)
 
448
 
 
449
    rps = []
262
450
    for f in ps:
263
451
        if f == '..':
264
 
            bailout("sorry, %r not allowed in path" % f)
265
 
    return ps
 
452
            raise BzrError("sorry, %r not allowed in path" % f)
 
453
        elif (f == '.') or (f == ''):
 
454
            pass
 
455
        else:
 
456
            rps.append(f)
 
457
    return rps
266
458
 
267
459
def joinpath(p):
268
460
    assert isinstance(p, list)
269
461
    for f in p:
270
 
        if (f == '..') or (f is None) or (f == ''):
271
 
            bailout("sorry, %r not allowed in path" % f)
272
 
    return '/'.join(p)
 
462
        if (f == '..') or (f == None) or (f == ''):
 
463
            raise BzrError("sorry, %r not allowed in path" % f)
 
464
    return os.path.join(*p)
273
465
 
274
466
 
275
467
def appendpath(p1, p2):
276
468
    if p1 == '':
277
469
        return p2
278
470
    else:
279
 
        return p1 + '/' + p2
 
471
        return os.path.join(p1, p2)
280
472
    
281
473
 
282
474
def extern_command(cmd, ignore_errors = False):
283
475
    mutter('external command: %s' % `cmd`)
284
476
    if os.system(cmd):
285
477
        if not ignore_errors:
286
 
            bailout('command failed')
287
 
 
 
478
            raise BzrError('command failed')
 
479
 
 
480
 
 
481
def _read_config_value(name):
 
482
    """Read a config value from the file ~/.bzr.conf/<name>
 
483
    Return None if the file does not exist"""
 
484
    try:
 
485
        f = file(os.path.join(config_dir(), name), "r")
 
486
        return f.read().decode(bzrlib.user_encoding).rstrip("\r\n")
 
487
    except IOError, e:
 
488
        if e.errno == errno.ENOENT:
 
489
            return None
 
490
        raise