~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

merge David Clymer's patch for TestCaseInTestDir.runcmd

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
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):
56
65
    elif S_ISLNK(mode):
57
66
        return 'symlink'
58
67
    else:
59
 
        raise BzrError("can't handle file kind with mode %o of %r" % (mode, f)) 
 
68
        raise BzrError("can't handle file kind with mode %o of %r" % (mode, f))
 
69
 
 
70
 
 
71
def kind_marker(kind):
 
72
    if kind == 'file':
 
73
        return ''
 
74
    elif kind == 'directory':
 
75
        return '/'
 
76
    elif kind == 'symlink':
 
77
        return '@'
 
78
    else:
 
79
        raise BzrError('invalid file kind %r' % kind)
 
80
 
 
81
 
 
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
 
60
118
 
61
119
 
62
120
 
77
135
        return False
78
136
 
79
137
 
 
138
def is_inside(dir, fname):
 
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
 
160
    """
 
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)
 
173
 
 
174
 
 
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):
 
179
            return True
 
180
    else:
 
181
        return False
 
182
 
 
183
 
80
184
def pumpfile(fromfile, tofile):
81
185
    """Copy contents of one file to another."""
82
186
    tofile.write(fromfile.read())
168
272
    return realname, (username + '@' + socket.gethostname())
169
273
 
170
274
 
171
 
def _get_user_id():
 
275
def _get_user_id(branch):
172
276
    """Return the full user id from a file or environment variable.
173
277
 
174
 
    TODO: Allow taking this from a file in the branch directory too
175
 
    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
    """
176
290
    v = os.environ.get('BZREMAIL')
177
291
    if v:
178
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
179
305
    
180
306
    try:
181
307
        return (open(os.path.join(config_dir(), "email"))
193
319
        return None
194
320
 
195
321
 
196
 
def username():
 
322
def username(branch):
197
323
    """Return email-style username.
198
324
 
199
325
    Something similar to 'Martin Pool <mbp@sourcefrog.net>'
200
326
 
201
327
    TODO: Check it's reasonably well-formed.
202
328
    """
203
 
    v = _get_user_id()
 
329
    v = _get_user_id(branch)
204
330
    if v:
205
331
        return v
206
332
    
211
337
        return email
212
338
 
213
339
 
214
 
_EMAIL_RE = re.compile(r'[\w+.-]+@[\w+.-]+')
215
 
def user_email():
 
340
def user_email(branch):
216
341
    """Return just the email component of a username."""
217
 
    e = _get_user_id()
 
342
    e = _get_user_id(branch)
218
343
    if e:
219
 
        m = _EMAIL_RE.search(e)
 
344
        m = re.search(r'[\w+.-]+@[\w+.-]+', e)
220
345
        if not m:
221
 
            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)
222
347
        return m.group(0)
223
348
 
224
349
    return _auto_user_id()[1]
266
391
        tt = time.localtime(t)
267
392
        offset = local_time_offset(t)
268
393
    else:
269
 
        bailout("unsupported timezone format %r",
270
 
                ['options are "utc", "original", "local"'])
 
394
        raise BzrError("unsupported timezone format %r" % timezone,
 
395
                       ['options are "utc", "original", "local"'])
271
396
 
272
397
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
273
398
            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
282
407
    """Return size of given open file."""
283
408
    return os.fstat(f.fileno())[ST_SIZE]
284
409
 
285
 
 
286
 
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)
287
415
    rand_bytes = os.urandom
288
 
else:
289
 
    # FIXME: No good on non-Linux
290
 
    _rand_file = file('/dev/urandom', 'rb')
291
 
    rand_bytes = _rand_file.read
292
 
 
 
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
293
431
 
294
432
## TODO: We could later have path objects that remember their list
295
433
## decomposition (might be too tricksy though.)
308
446
    >>> splitpath('a/../b')
309
447
    Traceback (most recent call last):
310
448
    ...
311
 
    BzrError: ("sorry, '..' not allowed in path", [])
 
449
    BzrError: sorry, '..' not allowed in path
312
450
    """
313
451
    assert isinstance(p, types.StringTypes)
314
452
 
319
457
    rps = []
320
458
    for f in ps:
321
459
        if f == '..':
322
 
            bailout("sorry, %r not allowed in path" % f)
 
460
            raise BzrError("sorry, %r not allowed in path" % f)
323
461
        elif (f == '.') or (f == ''):
324
462
            pass
325
463
        else:
330
468
    assert isinstance(p, list)
331
469
    for f in p:
332
470
        if (f == '..') or (f == None) or (f == ''):
333
 
            bailout("sorry, %r not allowed in path" % f)
 
471
            raise BzrError("sorry, %r not allowed in path" % f)
334
472
    return os.path.join(*p)
335
473
 
336
474
 
345
483
    mutter('external command: %s' % `cmd`)
346
484
    if os.system(cmd):
347
485
        if not ignore_errors:
348
 
            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
 
349
500