~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Robert Collins
  • Date: 2005-09-23 09:25:16 UTC
  • mto: (1092.3.4)
  • mto: This revision was merged to the branch mainline in revision 1390.
  • Revision ID: robertc@robertcollins.net-20050923092516-e2c3c0f31288669d
Merge what applied of Alexander Belchenko's win32 patch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
18
 
19
19
import os, types, re, time, errno, sys
20
 
from stat import S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE
 
20
from stat import (S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE,
 
21
        S_ISCHR, S_ISBLK, S_ISFIFO, S_ISSOCK)
21
22
 
22
 
from errors import bailout, BzrError
23
 
from trace import mutter
 
23
from bzrlib.errors import BzrError
 
24
from bzrlib.trace import mutter
24
25
import bzrlib
25
26
 
26
27
def make_readonly(filename):
37
38
    os.chmod(filename, mod)
38
39
 
39
40
 
40
 
_QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])')
 
41
_QUOTE_RE = None
 
42
 
 
43
 
41
44
def quotefn(f):
42
 
    """Return shell-quoted filename"""
43
 
    ## We could be a bit more terse by using double-quotes etc
44
 
    f = _QUOTE_RE.sub(r'\\\1', f)
45
 
    if f[0] == '~':
46
 
        f[0:1] = r'\~' 
47
 
    return f
 
45
    """Return a quoted filename filename
 
46
 
 
47
    This previously used backslash quoting, but that works poorly on
 
48
    Windows."""
 
49
    # TODO: I'm not really sure this is the best format either.x
 
50
    global _QUOTE_RE
 
51
    if _QUOTE_RE == None:
 
52
        _QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])')
 
53
        
 
54
    if _QUOTE_RE.search(f):
 
55
        return '"' + f + '"'
 
56
    else:
 
57
        return f
48
58
 
49
59
 
50
60
def file_kind(f):
55
65
        return 'directory'
56
66
    elif S_ISLNK(mode):
57
67
        return 'symlink'
 
68
    elif S_ISCHR(mode):
 
69
        return 'chardev'
 
70
    elif S_ISBLK(mode):
 
71
        return 'block'
 
72
    elif S_ISFIFO(mode):
 
73
        return 'fifo'
 
74
    elif S_ISSOCK(mode):
 
75
        return 'socket'
58
76
    else:
59
 
        raise BzrError("can't handle file kind with mode %o of %r" % (mode, f))
 
77
        return 'unknown'
60
78
 
61
79
 
62
80
def kind_marker(kind):
71
89
 
72
90
 
73
91
 
 
92
def backup_file(fn):
 
93
    """Copy a file to a backup.
 
94
 
 
95
    Backups are named in GNU-style, with a ~ suffix.
 
96
 
 
97
    If the file is already a backup, it's not copied.
 
98
    """
 
99
    import os
 
100
    if fn[-1] == '~':
 
101
        return
 
102
    bfn = fn + '~'
 
103
 
 
104
    inf = file(fn, 'rb')
 
105
    try:
 
106
        content = inf.read()
 
107
    finally:
 
108
        inf.close()
 
109
    
 
110
    outf = file(bfn, 'wb')
 
111
    try:
 
112
        outf.write(content)
 
113
    finally:
 
114
        outf.close()
 
115
 
 
116
if os.name == 'nt':
 
117
    import shutil
 
118
    rename = shutil.move
 
119
else:
 
120
    rename = os.rename
 
121
 
 
122
 
74
123
def isdir(f):
75
124
    """True if f is an accessible directory."""
76
125
    try:
79
128
        return False
80
129
 
81
130
 
82
 
 
83
131
def isfile(f):
84
132
    """True if f is a regular file."""
85
133
    try:
90
138
 
91
139
def is_inside(dir, fname):
92
140
    """True if fname is inside dir.
 
141
    
 
142
    The parameters should typically be passed to os.path.normpath first, so
 
143
    that . and .. and repeated slashes are eliminated, and the separators
 
144
    are canonical for the platform.
 
145
    
 
146
    The empty string as a dir name is taken as top-of-tree and matches 
 
147
    everything.
 
148
    
 
149
    >>> is_inside('src', os.path.join('src', 'foo.c'))
 
150
    True
 
151
    >>> is_inside('src', 'srccontrol')
 
152
    False
 
153
    >>> is_inside('src', os.path.join('src', 'a', 'a', 'a', 'foo.c'))
 
154
    True
 
155
    >>> is_inside('foo.c', 'foo.c')
 
156
    True
 
157
    >>> is_inside('foo.c', '')
 
158
    False
 
159
    >>> is_inside('', 'foo.c')
 
160
    True
93
161
    """
94
 
    return os.path.commonprefix([dir, fname]) == dir
 
162
    # XXX: Most callers of this can actually do something smarter by 
 
163
    # looking at the inventory
 
164
    if dir == fname:
 
165
        return True
 
166
    
 
167
    if dir == '':
 
168
        return True
 
169
    
 
170
    if dir[-1] != os.sep:
 
171
        dir += os.sep
 
172
    
 
173
    return fname.startswith(dir)
95
174
 
96
175
 
97
176
def is_inside_any(dir_list, fname):
98
177
    """True if fname is inside any of given dirs."""
99
 
    # quick scan for perfect match
100
 
    if fname in dir_list:
101
 
        return True
102
 
    
103
178
    for dirname in dir_list:
104
179
        if is_inside(dirname, fname):
105
180
            return True
198
273
    return realname, (username + '@' + socket.gethostname())
199
274
 
200
275
 
201
 
def _get_user_id():
 
276
def _get_user_id(branch):
202
277
    """Return the full user id from a file or environment variable.
203
278
 
204
 
    TODO: Allow taking this from a file in the branch directory too
205
 
    for per-branch ids."""
 
279
    e.g. "John Hacker <jhacker@foo.org>"
 
280
 
 
281
    branch
 
282
        A branch to use for a per-branch configuration, or None.
 
283
 
 
284
    The following are searched in order:
 
285
 
 
286
    1. $BZREMAIL
 
287
    2. .bzr/email for this branch.
 
288
    3. ~/.bzr.conf/email
 
289
    4. $EMAIL
 
290
    """
206
291
    v = os.environ.get('BZREMAIL')
207
292
    if v:
208
293
        return v.decode(bzrlib.user_encoding)
 
294
 
 
295
    if branch:
 
296
        try:
 
297
            return (branch.controlfile("email", "r") 
 
298
                    .read()
 
299
                    .decode(bzrlib.user_encoding)
 
300
                    .rstrip("\r\n"))
 
301
        except IOError, e:
 
302
            if e.errno != errno.ENOENT:
 
303
                raise
 
304
        except BzrError, e:
 
305
            pass
209
306
    
210
307
    try:
211
308
        return (open(os.path.join(config_dir(), "email"))
223
320
        return None
224
321
 
225
322
 
226
 
def username():
 
323
def username(branch):
227
324
    """Return email-style username.
228
325
 
229
326
    Something similar to 'Martin Pool <mbp@sourcefrog.net>'
230
327
 
231
328
    TODO: Check it's reasonably well-formed.
232
329
    """
233
 
    v = _get_user_id()
 
330
    v = _get_user_id(branch)
234
331
    if v:
235
332
        return v
236
333
    
241
338
        return email
242
339
 
243
340
 
244
 
_EMAIL_RE = re.compile(r'[\w+.-]+@[\w+.-]+')
245
 
def user_email():
 
341
def user_email(branch):
246
342
    """Return just the email component of a username."""
247
 
    e = _get_user_id()
 
343
    e = _get_user_id(branch)
248
344
    if e:
249
 
        m = _EMAIL_RE.search(e)
 
345
        m = re.search(r'[\w+.-]+@[\w+.-]+', e)
250
346
        if not m:
251
 
            bailout("%r doesn't seem to contain a reasonable email address" % e)
 
347
            raise BzrError("%r doesn't seem to contain a reasonable email address" % e)
252
348
        return m.group(0)
253
349
 
254
350
    return _auto_user_id()[1]
296
392
        tt = time.localtime(t)
297
393
        offset = local_time_offset(t)
298
394
    else:
299
 
        bailout("unsupported timezone format %r",
300
 
                ['options are "utc", "original", "local"'])
 
395
        raise BzrError("unsupported timezone format %r" % timezone,
 
396
                       ['options are "utc", "original", "local"'])
301
397
 
302
398
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
303
399
            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
312
408
    """Return size of given open file."""
313
409
    return os.fstat(f.fileno())[ST_SIZE]
314
410
 
315
 
 
316
 
if hasattr(os, 'urandom'): # python 2.4 and later
 
411
# Define rand_bytes based on platform.
 
412
try:
 
413
    # Python 2.4 and later have os.urandom,
 
414
    # but it doesn't work on some arches
 
415
    os.urandom(1)
317
416
    rand_bytes = os.urandom
318
 
elif sys.platform == 'linux2':
319
 
    rand_bytes = file('/dev/urandom', 'rb').read
320
 
else:
321
 
    # not well seeded, but better than nothing
322
 
    def rand_bytes(n):
323
 
        import random
324
 
        s = ''
325
 
        while n:
326
 
            s += chr(random.randint(0, 255))
327
 
            n -= 1
328
 
        return s
329
 
 
 
417
except (NotImplementedError, AttributeError):
 
418
    # If python doesn't have os.urandom, or it doesn't work,
 
419
    # then try to first pull random data from /dev/urandom
 
420
    if os.path.exists("/dev/urandom"):
 
421
        rand_bytes = file('/dev/urandom', 'rb').read
 
422
    # Otherwise, use this hack as a last resort
 
423
    else:
 
424
        # not well seeded, but better than nothing
 
425
        def rand_bytes(n):
 
426
            import random
 
427
            s = ''
 
428
            while n:
 
429
                s += chr(random.randint(0, 255))
 
430
                n -= 1
 
431
            return s
330
432
 
331
433
## TODO: We could later have path objects that remember their list
332
434
## decomposition (might be too tricksy though.)
345
447
    >>> splitpath('a/../b')
346
448
    Traceback (most recent call last):
347
449
    ...
348
 
    BzrError: ("sorry, '..' not allowed in path", [])
 
450
    BzrError: sorry, '..' not allowed in path
349
451
    """
350
452
    assert isinstance(p, types.StringTypes)
351
453
 
356
458
    rps = []
357
459
    for f in ps:
358
460
        if f == '..':
359
 
            bailout("sorry, %r not allowed in path" % f)
 
461
            raise BzrError("sorry, %r not allowed in path" % f)
360
462
        elif (f == '.') or (f == ''):
361
463
            pass
362
464
        else:
367
469
    assert isinstance(p, list)
368
470
    for f in p:
369
471
        if (f == '..') or (f == None) or (f == ''):
370
 
            bailout("sorry, %r not allowed in path" % f)
 
472
            raise BzrError("sorry, %r not allowed in path" % f)
371
473
    return os.path.join(*p)
372
474
 
373
475
 
382
484
    mutter('external command: %s' % `cmd`)
383
485
    if os.system(cmd):
384
486
        if not ignore_errors:
385
 
            bailout('command failed')
 
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
 
386
501