~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Aaron Bentley
  • Date: 2005-09-21 15:33:23 UTC
  • mto: (1185.1.37)
  • mto: This revision was merged to the branch mainline in revision 1390.
  • Revision ID: abentley@panoramicfeedback.com-20050921153323-5db674d572d7649d
Fixed bug in distance-from-root graph operation

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
20
 
from stat import S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE
 
19
import os, types, re, time, errno, sys
 
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'
58
 
    else:
59
 
        raise BzrError("can't handle file kind with mode %o of %r" % (mode, f)) 
 
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'
 
76
    else:
 
77
        return 'unknown'
 
78
 
 
79
 
 
80
def kind_marker(kind):
 
81
    if kind == 'file':
 
82
        return ''
 
83
    elif kind == 'directory':
 
84
        return '/'
 
85
    elif kind == 'symlink':
 
86
        return '@'
 
87
    else:
 
88
        raise BzrError('invalid file kind %r' % kind)
 
89
 
 
90
 
 
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
def rename(path_from, path_to):
 
117
    """Basically the same as os.rename() just special for win32"""
 
118
    if sys.platform == 'win32':
 
119
        try:
 
120
            os.remove(path_to)
 
121
        except OSError, e:
 
122
            if e.errno != e.ENOENT:
 
123
                raise
 
124
    os.rename(path_from, path_to)
 
125
 
 
126
 
60
127
 
61
128
 
62
129
 
77
144
        return False
78
145
 
79
146
 
 
147
def is_inside(dir, fname):
 
148
    """True if fname is inside dir.
 
149
    
 
150
    The parameters should typically be passed to os.path.normpath first, so
 
151
    that . and .. and repeated slashes are eliminated, and the separators
 
152
    are canonical for the platform.
 
153
    
 
154
    The empty string as a dir name is taken as top-of-tree and matches 
 
155
    everything.
 
156
    
 
157
    >>> is_inside('src', 'src/foo.c')
 
158
    True
 
159
    >>> is_inside('src', 'srccontrol')
 
160
    False
 
161
    >>> is_inside('src', 'src/a/a/a/foo.c')
 
162
    True
 
163
    >>> is_inside('foo.c', 'foo.c')
 
164
    True
 
165
    >>> is_inside('foo.c', '')
 
166
    False
 
167
    >>> is_inside('', 'foo.c')
 
168
    True
 
169
    """
 
170
    # XXX: Most callers of this can actually do something smarter by 
 
171
    # looking at the inventory
 
172
    if dir == fname:
 
173
        return True
 
174
    
 
175
    if dir == '':
 
176
        return True
 
177
    
 
178
    if dir[-1] != os.sep:
 
179
        dir += os.sep
 
180
    
 
181
    return fname.startswith(dir)
 
182
 
 
183
 
 
184
def is_inside_any(dir_list, fname):
 
185
    """True if fname is inside any of given dirs."""
 
186
    for dirname in dir_list:
 
187
        if is_inside(dirname, fname):
 
188
            return True
 
189
    else:
 
190
        return False
 
191
 
 
192
 
80
193
def pumpfile(fromfile, tofile):
81
194
    """Copy contents of one file to another."""
82
195
    tofile.write(fromfile.read())
84
197
 
85
198
def uuid():
86
199
    """Return a new UUID"""
87
 
    
88
 
    ## XXX: Could alternatively read /proc/sys/kernel/random/uuid on
89
 
    ## Linux, but we need something portable for other systems;
90
 
    ## preferably an implementation in Python.
91
200
    try:
92
 
        return chomp(file('/proc/sys/kernel/random/uuid').readline())
 
201
        return file('/proc/sys/kernel/random/uuid').readline().rstrip('\n')
93
202
    except IOError:
94
203
        return chomp(os.popen('uuidgen').readline())
95
204
 
96
205
 
97
 
def chomp(s):
98
 
    if s and (s[-1] == '\n'):
99
 
        return s[:-1]
100
 
    else:
101
 
        return s
102
 
 
103
 
 
104
206
def sha_file(f):
105
207
    import sha
106
 
    ## TODO: Maybe read in chunks to handle big files
107
208
    if hasattr(f, 'tell'):
108
209
        assert f.tell() == 0
109
210
    s = sha.new()
110
 
    s.update(f.read())
 
211
    BUFSIZE = 128<<10
 
212
    while True:
 
213
        b = f.read(BUFSIZE)
 
214
        if not b:
 
215
            break
 
216
        s.update(b)
111
217
    return s.hexdigest()
112
218
 
113
219
 
175
281
    return realname, (username + '@' + socket.gethostname())
176
282
 
177
283
 
178
 
def _get_user_id():
 
284
def _get_user_id(branch):
179
285
    """Return the full user id from a file or environment variable.
180
286
 
181
 
    TODO: Allow taking this from a file in the branch directory too
182
 
    for per-branch ids."""
 
287
    e.g. "John Hacker <jhacker@foo.org>"
 
288
 
 
289
    branch
 
290
        A branch to use for a per-branch configuration, or None.
 
291
 
 
292
    The following are searched in order:
 
293
 
 
294
    1. $BZREMAIL
 
295
    2. .bzr/email for this branch.
 
296
    3. ~/.bzr.conf/email
 
297
    4. $EMAIL
 
298
    """
183
299
    v = os.environ.get('BZREMAIL')
184
300
    if v:
185
301
        return v.decode(bzrlib.user_encoding)
 
302
 
 
303
    if branch:
 
304
        try:
 
305
            return (branch.controlfile("email", "r") 
 
306
                    .read()
 
307
                    .decode(bzrlib.user_encoding)
 
308
                    .rstrip("\r\n"))
 
309
        except IOError, e:
 
310
            if e.errno != errno.ENOENT:
 
311
                raise
 
312
        except BzrError, e:
 
313
            pass
186
314
    
187
315
    try:
188
316
        return (open(os.path.join(config_dir(), "email"))
200
328
        return None
201
329
 
202
330
 
203
 
def username():
 
331
def username(branch):
204
332
    """Return email-style username.
205
333
 
206
334
    Something similar to 'Martin Pool <mbp@sourcefrog.net>'
207
335
 
208
336
    TODO: Check it's reasonably well-formed.
209
337
    """
210
 
    v = _get_user_id()
 
338
    v = _get_user_id(branch)
211
339
    if v:
212
340
        return v
213
341
    
218
346
        return email
219
347
 
220
348
 
221
 
_EMAIL_RE = re.compile(r'[\w+.-]+@[\w+.-]+')
222
 
def user_email():
 
349
def user_email(branch):
223
350
    """Return just the email component of a username."""
224
 
    e = _get_user_id()
 
351
    e = _get_user_id(branch)
225
352
    if e:
226
 
        m = _EMAIL_RE.search(e)
 
353
        m = re.search(r'[\w+.-]+@[\w+.-]+', e)
227
354
        if not m:
228
 
            bailout("%r doesn't seem to contain a reasonable email address" % e)
 
355
            raise BzrError("%r doesn't seem to contain a reasonable email address" % e)
229
356
        return m.group(0)
230
357
 
231
358
    return _auto_user_id()[1]
234
361
 
235
362
def compare_files(a, b):
236
363
    """Returns true if equal in contents"""
237
 
    # TODO: don't read the whole thing in one go.
238
364
    BUFSIZE = 4096
239
365
    while True:
240
366
        ai = a.read(BUFSIZE)
274
400
        tt = time.localtime(t)
275
401
        offset = local_time_offset(t)
276
402
    else:
277
 
        bailout("unsupported timezone format %r",
278
 
                ['options are "utc", "original", "local"'])
 
403
        raise BzrError("unsupported timezone format %r" % timezone,
 
404
                       ['options are "utc", "original", "local"'])
279
405
 
280
406
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
281
407
            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
290
416
    """Return size of given open file."""
291
417
    return os.fstat(f.fileno())[ST_SIZE]
292
418
 
293
 
 
294
 
if hasattr(os, 'urandom'): # python 2.4 and later
 
419
# Define rand_bytes based on platform.
 
420
try:
 
421
    # Python 2.4 and later have os.urandom,
 
422
    # but it doesn't work on some arches
 
423
    os.urandom(1)
295
424
    rand_bytes = os.urandom
296
 
else:
297
 
    # FIXME: No good on non-Linux
298
 
    _rand_file = file('/dev/urandom', 'rb')
299
 
    rand_bytes = _rand_file.read
300
 
 
 
425
except (NotImplementedError, AttributeError):
 
426
    # If python doesn't have os.urandom, or it doesn't work,
 
427
    # then try to first pull random data from /dev/urandom
 
428
    if os.path.exists("/dev/urandom"):
 
429
        rand_bytes = file('/dev/urandom', 'rb').read
 
430
    # Otherwise, use this hack as a last resort
 
431
    else:
 
432
        # not well seeded, but better than nothing
 
433
        def rand_bytes(n):
 
434
            import random
 
435
            s = ''
 
436
            while n:
 
437
                s += chr(random.randint(0, 255))
 
438
                n -= 1
 
439
            return s
301
440
 
302
441
## TODO: We could later have path objects that remember their list
303
442
## decomposition (might be too tricksy though.)
316
455
    >>> splitpath('a/../b')
317
456
    Traceback (most recent call last):
318
457
    ...
319
 
    BzrError: ("sorry, '..' not allowed in path", [])
 
458
    BzrError: sorry, '..' not allowed in path
320
459
    """
321
460
    assert isinstance(p, types.StringTypes)
322
461
 
327
466
    rps = []
328
467
    for f in ps:
329
468
        if f == '..':
330
 
            bailout("sorry, %r not allowed in path" % f)
 
469
            raise BzrError("sorry, %r not allowed in path" % f)
331
470
        elif (f == '.') or (f == ''):
332
471
            pass
333
472
        else:
338
477
    assert isinstance(p, list)
339
478
    for f in p:
340
479
        if (f == '..') or (f == None) or (f == ''):
341
 
            bailout("sorry, %r not allowed in path" % f)
 
480
            raise BzrError("sorry, %r not allowed in path" % f)
342
481
    return os.path.join(*p)
343
482
 
344
483
 
353
492
    mutter('external command: %s' % `cmd`)
354
493
    if os.system(cmd):
355
494
        if not ignore_errors:
356
 
            bailout('command failed')
 
495
            raise BzrError('command failed')
 
496
 
 
497
 
 
498
def _read_config_value(name):
 
499
    """Read a config value from the file ~/.bzr.conf/<name>
 
500
    Return None if the file does not exist"""
 
501
    try:
 
502
        f = file(os.path.join(config_dir(), name), "r")
 
503
        return f.read().decode(bzrlib.user_encoding).rstrip("\r\n")
 
504
    except IOError, e:
 
505
        if e.errno == errno.ENOENT:
 
506
            return None
 
507
        raise
 
508
 
357
509