~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Harald Meland
  • Date: 2005-09-23 12:49:00 UTC
  • mto: (1185.1.45)
  • mto: This revision was merged to the branch mainline in revision 1391.
  • Revision ID: hmeland@twoflower.uio.no-20050923124900-fa7d79d29f1e537d
Use fcntl.lockf() rather than fcntl.flock() to support NFS file systems.

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, types
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
 
23
from bzrlib.errors import BzrError
 
24
from bzrlib.trace import mutter
 
25
import bzrlib
23
26
 
24
27
def make_readonly(filename):
25
28
    """Make a filename read-only."""
35
38
    os.chmod(filename, mod)
36
39
 
37
40
 
38
 
_QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])')
 
41
_QUOTE_RE = None
 
42
 
 
43
 
39
44
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
 
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
46
58
 
47
59
 
48
60
def file_kind(f):
53
65
        return 'directory'
54
66
    elif S_ISLNK(mode):
55
67
        return 'symlink'
56
 
    else:
57
 
        bailout("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
 
58
127
 
59
128
 
60
129
 
75
144
        return False
76
145
 
77
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
 
78
193
def pumpfile(fromfile, tofile):
79
194
    """Copy contents of one file to another."""
80
195
    tofile.write(fromfile.read())
82
197
 
83
198
def uuid():
84
199
    """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
 
    bailout('uuids not allowed!')
90
 
    return chomp(os.popen('uuidgen').readline())
91
 
 
92
 
def chomp(s):
93
 
    if s and (s[-1] == '\n'):
94
 
        return s[:-1]
95
 
    else:
96
 
        return s
 
200
    try:
 
201
        return file('/proc/sys/kernel/random/uuid').readline().rstrip('\n')
 
202
    except IOError:
 
203
        return chomp(os.popen('uuidgen').readline())
97
204
 
98
205
 
99
206
def sha_file(f):
100
207
    import sha
101
 
    ## TODO: Maybe read in chunks to handle big files
102
208
    if hasattr(f, 'tell'):
103
209
        assert f.tell() == 0
104
210
    s = sha.new()
105
 
    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)
106
217
    return s.hexdigest()
107
218
 
108
219
 
114
225
 
115
226
 
116
227
 
117
 
def username():
118
 
    """Return email-style username.
119
 
 
120
 
    Something similar to 'Martin Pool <mbp@sourcefrog.net>'
121
 
 
122
 
    :todo: Check it's reasonably well-formed.
123
 
 
124
 
    :todo: Allow taking it from a dotfile to help people on windows
125
 
           who can't easily set variables.
126
 
 
127
 
    :todo: Cope without pwd module, which is only on unix. 
128
 
    """
129
 
    e = os.environ.get('BZREMAIL') or os.environ.get('EMAIL')
130
 
    if e: return e
131
 
 
 
228
def fingerprint_file(f):
 
229
    import sha
 
230
    s = sha.new()
 
231
    b = f.read()
 
232
    s.update(b)
 
233
    size = len(b)
 
234
    return {'size': size,
 
235
            'sha1': s.hexdigest()}
 
236
 
 
237
 
 
238
def config_dir():
 
239
    """Return per-user configuration directory.
 
240
 
 
241
    By default this is ~/.bzr.conf/
 
242
    
 
243
    TODO: Global option --config-dir to override this.
 
244
    """
 
245
    return os.path.expanduser("~/.bzr.conf")
 
246
 
 
247
 
 
248
def _auto_user_id():
 
249
    """Calculate automatic user identification.
 
250
 
 
251
    Returns (realname, email).
 
252
 
 
253
    Only used when none is set in the environment or the id file.
 
254
 
 
255
    This previously used the FQDN as the default domain, but that can
 
256
    be very slow on machines where DNS is broken.  So now we simply
 
257
    use the hostname.
 
258
    """
132
259
    import socket
133
 
    
 
260
 
 
261
    # XXX: Any good way to get real user name on win32?
 
262
 
134
263
    try:
135
264
        import pwd
136
265
        uid = os.getuid()
137
266
        w = pwd.getpwuid(uid)
138
 
        gecos = w.pw_gecos
 
267
        gecos = w.pw_gecos.decode(bzrlib.user_encoding)
 
268
        username = w.pw_name.decode(bzrlib.user_encoding)
139
269
        comma = gecos.find(',')
140
270
        if comma == -1:
141
271
            realname = gecos
142
272
        else:
143
273
            realname = gecos[:comma]
144
 
        return '%s <%s@%s>' % (realname, w.pw_name, socket.getfqdn())
 
274
        if not realname:
 
275
            realname = username
 
276
 
145
277
    except ImportError:
146
 
        pass
147
 
 
148
 
    import getpass, socket
149
 
    return '<%s@%s>' % (getpass.getuser(), socket.getfqdn())
150
 
 
151
 
 
152
 
def user_email():
 
278
        import getpass
 
279
        realname = username = getpass.getuser().decode(bzrlib.user_encoding)
 
280
 
 
281
    return realname, (username + '@' + socket.gethostname())
 
282
 
 
283
 
 
284
def _get_user_id(branch):
 
285
    """Return the full user id from a file or environment variable.
 
286
 
 
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
    """
 
299
    v = os.environ.get('BZREMAIL')
 
300
    if v:
 
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
 
314
    
 
315
    try:
 
316
        return (open(os.path.join(config_dir(), "email"))
 
317
                .read()
 
318
                .decode(bzrlib.user_encoding)
 
319
                .rstrip("\r\n"))
 
320
    except IOError, e:
 
321
        if e.errno != errno.ENOENT:
 
322
            raise e
 
323
 
 
324
    v = os.environ.get('EMAIL')
 
325
    if v:
 
326
        return v.decode(bzrlib.user_encoding)
 
327
    else:    
 
328
        return None
 
329
 
 
330
 
 
331
def username(branch):
 
332
    """Return email-style username.
 
333
 
 
334
    Something similar to 'Martin Pool <mbp@sourcefrog.net>'
 
335
 
 
336
    TODO: Check it's reasonably well-formed.
 
337
    """
 
338
    v = _get_user_id(branch)
 
339
    if v:
 
340
        return v
 
341
    
 
342
    name, email = _auto_user_id()
 
343
    if name:
 
344
        return '%s <%s>' % (name, email)
 
345
    else:
 
346
        return email
 
347
 
 
348
 
 
349
def user_email(branch):
153
350
    """Return just the email component of a username."""
154
 
    e = os.environ.get('BZREMAIL') or os.environ.get('EMAIL')
 
351
    e = _get_user_id(branch)
155
352
    if e:
156
 
        import re
157
353
        m = re.search(r'[\w+.-]+@[\w+.-]+', e)
158
354
        if not m:
159
 
            bailout('%r is not a reasonable email address' % e)
 
355
            raise BzrError("%r doesn't seem to contain a reasonable email address" % e)
160
356
        return m.group(0)
161
357
 
162
 
 
163
 
    import getpass, socket
164
 
    return '%s@%s' % (getpass.getuser(), socket.getfqdn())
165
 
 
 
358
    return _auto_user_id()[1]
166
359
    
167
360
 
168
361
 
169
362
def compare_files(a, b):
170
363
    """Returns true if equal in contents"""
171
 
    # TODO: don't read the whole thing in one go.
172
 
    result = a.read() == b.read()
173
 
    return result
174
 
 
175
 
 
176
 
 
177
 
def local_time_offset():
178
 
    if time.daylight:
 
364
    BUFSIZE = 4096
 
365
    while True:
 
366
        ai = a.read(BUFSIZE)
 
367
        bi = b.read(BUFSIZE)
 
368
        if ai != bi:
 
369
            return False
 
370
        if ai == '':
 
371
            return True
 
372
 
 
373
 
 
374
 
 
375
def local_time_offset(t=None):
 
376
    """Return offset of local zone from GMT, either at present or at time t."""
 
377
    # python2.3 localtime() can't take None
 
378
    if t == None:
 
379
        t = time.time()
 
380
        
 
381
    if time.localtime(t).tm_isdst and time.daylight:
179
382
        return -time.altzone
180
383
    else:
181
384
        return -time.timezone
184
387
def format_date(t, offset=0, timezone='original'):
185
388
    ## TODO: Perhaps a global option to use either universal or local time?
186
389
    ## Or perhaps just let people set $TZ?
187
 
    import time
188
 
    
189
390
    assert isinstance(t, float)
190
391
    
191
392
    if timezone == 'utc':
197
398
        tt = time.gmtime(t + offset)
198
399
    elif timezone == 'local':
199
400
        tt = time.localtime(t)
200
 
        offset = local_time_offset()
 
401
        offset = local_time_offset(t)
201
402
    else:
202
 
        bailout("unsupported timezone format %r",
203
 
                ['options are "utc", "original", "local"'])
 
403
        raise BzrError("unsupported timezone format %r" % timezone,
 
404
                       ['options are "utc", "original", "local"'])
204
405
 
205
406
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
206
407
            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
215
416
    """Return size of given open file."""
216
417
    return os.fstat(f.fileno())[ST_SIZE]
217
418
 
218
 
 
219
 
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)
220
424
    rand_bytes = os.urandom
221
 
else:
222
 
    # FIXME: No good on non-Linux
223
 
    _rand_file = file('/dev/urandom', 'rb')
224
 
    rand_bytes = _rand_file.read
225
 
 
 
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
226
440
 
227
441
## TODO: We could later have path objects that remember their list
228
442
## decomposition (might be too tricksy though.)
241
455
    >>> splitpath('a/../b')
242
456
    Traceback (most recent call last):
243
457
    ...
244
 
    BzrError: ("sorry, '..' not allowed in path", [])
 
458
    BzrError: sorry, '..' not allowed in path
245
459
    """
246
460
    assert isinstance(p, types.StringTypes)
247
 
    ps = [f for f in p.split('/') if f != '.']
 
461
 
 
462
    # split on either delimiter because people might use either on
 
463
    # Windows
 
464
    ps = re.split(r'[\\/]', p)
 
465
 
 
466
    rps = []
248
467
    for f in ps:
249
468
        if f == '..':
250
 
            bailout("sorry, %r not allowed in path" % f)
251
 
    return ps
 
469
            raise BzrError("sorry, %r not allowed in path" % f)
 
470
        elif (f == '.') or (f == ''):
 
471
            pass
 
472
        else:
 
473
            rps.append(f)
 
474
    return rps
252
475
 
253
476
def joinpath(p):
254
477
    assert isinstance(p, list)
255
478
    for f in p:
256
 
        if (f == '..') or (f is None) or (f == ''):
257
 
            bailout("sorry, %r not allowed in path" % f)
258
 
    return '/'.join(p)
 
479
        if (f == '..') or (f == None) or (f == ''):
 
480
            raise BzrError("sorry, %r not allowed in path" % f)
 
481
    return os.path.join(*p)
259
482
 
260
483
 
261
484
def appendpath(p1, p2):
262
485
    if p1 == '':
263
486
        return p2
264
487
    else:
265
 
        return p1 + '/' + p2
 
488
        return os.path.join(p1, p2)
266
489
    
267
490
 
268
491
def extern_command(cmd, ignore_errors = False):
269
492
    mutter('external command: %s' % `cmd`)
270
493
    if os.system(cmd):
271
494
        if not ignore_errors:
272
 
            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
 
273
509