~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

merge merge tweaks from aaron, which includes latest .dev

Show diffs side-by-side

added added

removed removed

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