~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Martin Pool
  • Date: 2005-03-15 05:19:54 UTC
  • Revision ID: mbp@sourcefrog.net-20050315051954-e4bdd6dfd26f8ecf
witty comment

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Bazaar-NG -- distributed version control
2
 
#
 
2
 
3
3
# Copyright (C) 2005 by Canonical Ltd
4
 
#
 
4
 
5
5
# This program is free software; you can redistribute it and/or modify
6
6
# it under the terms of the GNU General Public License as published by
7
7
# the Free Software Foundation; either version 2 of the License, or
8
8
# (at your option) any later version.
9
 
#
 
9
 
10
10
# This program is distributed in the hope that it will be useful,
11
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
13
# GNU General Public License for more details.
14
 
#
 
14
 
15
15
# You should have received a copy of the GNU General Public License
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
 
from shutil import copyfile
20
 
from stat import (S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE,
21
 
                  S_ISCHR, S_ISBLK, S_ISFIFO, S_ISSOCK)
22
 
from cStringIO import StringIO
23
 
import errno
24
 
import os
25
 
import re
26
 
import sha
27
 
import sys
28
 
import time
29
 
import types
30
 
 
31
 
import bzrlib
32
 
from bzrlib.errors import BzrError
33
 
from bzrlib.trace import mutter
34
 
 
 
19
import os, types, re, time, types
 
20
from stat import S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE
 
21
 
 
22
from errors import bailout
35
23
 
36
24
def make_readonly(filename):
37
25
    """Make a filename read-only."""
 
26
    # TODO: probably needs to be fixed for windows
38
27
    mod = os.stat(filename).st_mode
39
28
    mod = mod & 0777555
40
29
    os.chmod(filename, mod)
46
35
    os.chmod(filename, mod)
47
36
 
48
37
 
49
 
_QUOTE_RE = None
50
 
 
51
 
 
 
38
_QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])')
52
39
def quotefn(f):
53
 
    """Return a quoted filename filename
54
 
 
55
 
    This previously used backslash quoting, but that works poorly on
56
 
    Windows."""
57
 
    # TODO: I'm not really sure this is the best format either.x
58
 
    global _QUOTE_RE
59
 
    if _QUOTE_RE == None:
60
 
        _QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/\\_~-])')
61
 
        
62
 
    if _QUOTE_RE.search(f):
63
 
        return '"' + f + '"'
64
 
    else:
65
 
        return 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
66
46
 
67
47
 
68
48
def file_kind(f):
73
53
        return 'directory'
74
54
    elif S_ISLNK(mode):
75
55
        return 'symlink'
76
 
    elif S_ISCHR(mode):
77
 
        return 'chardev'
78
 
    elif S_ISBLK(mode):
79
 
        return 'block'
80
 
    elif S_ISFIFO(mode):
81
 
        return 'fifo'
82
 
    elif S_ISSOCK(mode):
83
 
        return 'socket'
84
 
    else:
85
 
        return 'unknown'
86
 
 
87
 
 
88
 
def kind_marker(kind):
89
 
    if kind == 'file':
90
 
        return ''
91
 
    elif kind == 'directory':
92
 
        return '/'
93
 
    elif kind == 'symlink':
94
 
        return '@'
95
 
    else:
96
 
        raise BzrError('invalid file kind %r' % kind)
97
 
 
98
 
def lexists(f):
99
 
    try:
100
 
        if hasattr(os, 'lstat'):
101
 
            os.lstat(f)
102
 
        else:
103
 
            os.stat(f)
104
 
        return True
105
 
    except OSError,e:
106
 
        if e.errno == errno.ENOENT:
107
 
            return False;
108
 
        else:
109
 
            raise BzrError("lstat/stat of (%r): %r" % (f, e))
110
 
 
111
 
def normalizepath(f):
112
 
    if hasattr(os.path, 'realpath'):
113
 
        F = os.path.realpath
114
 
    else:
115
 
        F = os.path.abspath
116
 
    [p,e] = os.path.split(f)
117
 
    if e == "" or e == "." or e == "..":
118
 
        return F(f)
119
 
    else:
120
 
        return os.path.join(F(p), e)
121
 
    
122
 
 
123
 
def backup_file(fn):
124
 
    """Copy a file to a backup.
125
 
 
126
 
    Backups are named in GNU-style, with a ~ suffix.
127
 
 
128
 
    If the file is already a backup, it's not copied.
129
 
    """
130
 
    if fn[-1] == '~':
131
 
        return
132
 
    bfn = fn + '~'
133
 
 
134
 
    inf = file(fn, 'rb')
135
 
    try:
136
 
        content = inf.read()
137
 
    finally:
138
 
        inf.close()
139
 
    
140
 
    outf = file(bfn, 'wb')
141
 
    try:
142
 
        outf.write(content)
143
 
    finally:
144
 
        outf.close()
145
 
 
146
 
if os.name == 'nt':
147
 
    import shutil
148
 
    rename = shutil.move
149
 
else:
150
 
    rename = os.rename
 
56
    else:
 
57
        bailout("can't handle file kind with mode %o of %r" % (mode, f)) 
 
58
 
151
59
 
152
60
 
153
61
def isdir(f):
158
66
        return False
159
67
 
160
68
 
 
69
 
161
70
def isfile(f):
162
71
    """True if f is a regular file."""
163
72
    try:
165
74
    except OSError:
166
75
        return False
167
76
 
168
 
def islink(f):
169
 
    """True if f is a symlink."""
170
 
    try:
171
 
        return S_ISLNK(os.lstat(f)[ST_MODE])
172
 
    except OSError:
173
 
        return False
174
 
 
175
 
def is_inside(dir, fname):
176
 
    """True if fname is inside dir.
177
 
    
178
 
    The parameters should typically be passed to os.path.normpath first, so
179
 
    that . and .. and repeated slashes are eliminated, and the separators
180
 
    are canonical for the platform.
181
 
    
182
 
    The empty string as a dir name is taken as top-of-tree and matches 
183
 
    everything.
184
 
    
185
 
    >>> is_inside('src', os.path.join('src', 'foo.c'))
186
 
    True
187
 
    >>> is_inside('src', 'srccontrol')
188
 
    False
189
 
    >>> is_inside('src', os.path.join('src', 'a', 'a', 'a', 'foo.c'))
190
 
    True
191
 
    >>> is_inside('foo.c', 'foo.c')
192
 
    True
193
 
    >>> is_inside('foo.c', '')
194
 
    False
195
 
    >>> is_inside('', 'foo.c')
196
 
    True
197
 
    """
198
 
    # XXX: Most callers of this can actually do something smarter by 
199
 
    # looking at the inventory
200
 
    if dir == fname:
201
 
        return True
202
 
    
203
 
    if dir == '':
204
 
        return True
205
 
 
206
 
    if dir[-1] != os.sep:
207
 
        dir += os.sep
208
 
 
209
 
    return fname.startswith(dir)
210
 
 
211
 
 
212
 
def is_inside_any(dir_list, fname):
213
 
    """True if fname is inside any of given dirs."""
214
 
    for dirname in dir_list:
215
 
        if is_inside(dirname, fname):
216
 
            return True
217
 
    else:
218
 
        return False
219
 
 
220
77
 
221
78
def pumpfile(fromfile, tofile):
222
79
    """Copy contents of one file to another."""
223
80
    tofile.write(fromfile.read())
224
81
 
225
82
 
 
83
def uuid():
 
84
    """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
 
97
 
 
98
 
226
99
def sha_file(f):
 
100
    import sha
 
101
    ## TODO: Maybe read in chunks to handle big files
227
102
    if hasattr(f, 'tell'):
228
103
        assert f.tell() == 0
229
104
    s = sha.new()
230
 
    BUFSIZE = 128<<10
231
 
    while True:
232
 
        b = f.read(BUFSIZE)
233
 
        if not b:
234
 
            break
235
 
        s.update(b)
236
 
    return s.hexdigest()
237
 
 
238
 
 
239
 
 
240
 
def sha_strings(strings):
241
 
    """Return the sha-1 of concatenation of strings"""
242
 
    s = sha.new()
243
 
    map(s.update, strings)
 
105
    s.update(f.read())
244
106
    return s.hexdigest()
245
107
 
246
108
 
247
109
def sha_string(f):
 
110
    import sha
248
111
    s = sha.new()
249
112
    s.update(f)
250
113
    return s.hexdigest()
251
114
 
252
115
 
253
 
def fingerprint_file(f):
254
 
    s = sha.new()
255
 
    b = f.read()
256
 
    s.update(b)
257
 
    size = len(b)
258
 
    return {'size': size,
259
 
            'sha1': s.hexdigest()}
260
 
 
261
 
 
262
 
def config_dir():
263
 
    """Return per-user configuration directory.
264
 
 
265
 
    By default this is ~/.bzr.conf/
266
 
    
267
 
    TODO: Global option --config-dir to override this.
268
 
    """
269
 
    return os.path.join(os.path.expanduser("~"), ".bzr.conf")
270
 
 
271
 
 
272
 
def _auto_user_id():
273
 
    """Calculate automatic user identification.
274
 
 
275
 
    Returns (realname, email).
276
 
 
277
 
    Only used when none is set in the environment or the id file.
278
 
 
279
 
    This previously used the FQDN as the default domain, but that can
280
 
    be very slow on machines where DNS is broken.  So now we simply
281
 
    use the hostname.
282
 
    """
 
116
 
 
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
 
283
132
    import socket
284
 
 
285
 
    # XXX: Any good way to get real user name on win32?
286
 
 
 
133
    
287
134
    try:
288
135
        import pwd
289
136
        uid = os.getuid()
290
137
        w = pwd.getpwuid(uid)
291
 
        gecos = w.pw_gecos.decode(bzrlib.user_encoding)
292
 
        username = w.pw_name.decode(bzrlib.user_encoding)
 
138
        gecos = w.pw_gecos
293
139
        comma = gecos.find(',')
294
140
        if comma == -1:
295
141
            realname = gecos
296
142
        else:
297
143
            realname = gecos[:comma]
298
 
        if not realname:
299
 
            realname = username
300
 
 
 
144
        return '%s <%s@%s>' % (realname, w.pw_name, socket.getfqdn())
301
145
    except ImportError:
302
 
        import getpass
303
 
        realname = username = getpass.getuser().decode(bzrlib.user_encoding)
304
 
 
305
 
    return realname, (username + '@' + socket.gethostname())
306
 
 
307
 
 
308
 
def _get_user_id(branch):
309
 
    """Return the full user id from a file or environment variable.
310
 
 
311
 
    e.g. "John Hacker <jhacker@foo.org>"
312
 
 
313
 
    branch
314
 
        A branch to use for a per-branch configuration, or None.
315
 
 
316
 
    The following are searched in order:
317
 
 
318
 
    1. $BZREMAIL
319
 
    2. .bzr/email for this branch.
320
 
    3. ~/.bzr.conf/email
321
 
    4. $EMAIL
322
 
    """
323
 
    v = os.environ.get('BZREMAIL')
324
 
    if v:
325
 
        return v.decode(bzrlib.user_encoding)
326
 
 
327
 
    if branch:
328
 
        try:
329
 
            return (branch.controlfile("email", "r") 
330
 
                    .read()
331
 
                    .decode(bzrlib.user_encoding)
332
 
                    .rstrip("\r\n"))
333
 
        except IOError, e:
334
 
            if e.errno != errno.ENOENT:
335
 
                raise
336
 
        except BzrError, e:
337
 
            pass
338
 
    
339
 
    try:
340
 
        return (open(os.path.join(config_dir(), "email"))
341
 
                .read()
342
 
                .decode(bzrlib.user_encoding)
343
 
                .rstrip("\r\n"))
344
 
    except IOError, e:
345
 
        if e.errno != errno.ENOENT:
346
 
            raise e
347
 
 
348
 
    v = os.environ.get('EMAIL')
349
 
    if v:
350
 
        return v.decode(bzrlib.user_encoding)
351
 
    else:    
352
 
        return None
353
 
 
354
 
 
355
 
def username(branch):
356
 
    """Return email-style username.
357
 
 
358
 
    Something similar to 'Martin Pool <mbp@sourcefrog.net>'
359
 
 
360
 
    TODO: Check it's reasonably well-formed.
361
 
    """
362
 
    v = _get_user_id(branch)
363
 
    if v:
364
 
        return v
365
 
    
366
 
    name, email = _auto_user_id()
367
 
    if name:
368
 
        return '%s <%s>' % (name, email)
369
 
    else:
370
 
        return email
371
 
 
372
 
 
373
 
def user_email(branch):
 
146
        pass
 
147
 
 
148
    import getpass, socket
 
149
    return '<%s@%s>' % (getpass.getuser(), socket.getfqdn())
 
150
 
 
151
 
 
152
def user_email():
374
153
    """Return just the email component of a username."""
375
 
    e = _get_user_id(branch)
 
154
    e = os.environ.get('BZREMAIL') or os.environ.get('EMAIL')
376
155
    if e:
 
156
        import re
377
157
        m = re.search(r'[\w+.-]+@[\w+.-]+', e)
378
158
        if not m:
379
 
            raise BzrError("%r doesn't seem to contain "
380
 
                           "a reasonable email address" % e)
 
159
            bailout('%r is not a reasonable email address' % e)
381
160
        return m.group(0)
382
161
 
383
 
    return _auto_user_id()[1]
 
162
 
 
163
    import getpass, socket
 
164
    return '%s@%s' % (getpass.getuser(), socket.getfqdn())
 
165
 
 
166
    
384
167
 
385
168
 
386
169
def compare_files(a, b):
387
170
    """Returns true if equal in contents"""
388
 
    BUFSIZE = 4096
389
 
    while True:
390
 
        ai = a.read(BUFSIZE)
391
 
        bi = b.read(BUFSIZE)
392
 
        if ai != bi:
393
 
            return False
394
 
        if ai == '':
395
 
            return True
396
 
 
397
 
 
398
 
def local_time_offset(t=None):
399
 
    """Return offset of local zone from GMT, either at present or at time t."""
400
 
    # python2.3 localtime() can't take None
401
 
    if t == None:
402
 
        t = time.time()
403
 
        
404
 
    if time.localtime(t).tm_isdst and time.daylight:
 
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:
405
179
        return -time.altzone
406
180
    else:
407
181
        return -time.timezone
410
184
def format_date(t, offset=0, timezone='original'):
411
185
    ## TODO: Perhaps a global option to use either universal or local time?
412
186
    ## Or perhaps just let people set $TZ?
 
187
    import time
 
188
    
413
189
    assert isinstance(t, float)
414
190
    
415
191
    if timezone == 'utc':
421
197
        tt = time.gmtime(t + offset)
422
198
    elif timezone == 'local':
423
199
        tt = time.localtime(t)
424
 
        offset = local_time_offset(t)
 
200
        offset = local_time_offset()
425
201
    else:
426
 
        raise BzrError("unsupported timezone format %r" % timezone,
427
 
                       ['options are "utc", "original", "local"'])
 
202
        bailout("unsupported timezone format %r",
 
203
                ['options are "utc", "original", "local"'])
428
204
 
429
205
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
430
206
            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
439
215
    """Return size of given open file."""
440
216
    return os.fstat(f.fileno())[ST_SIZE]
441
217
 
442
 
# Define rand_bytes based on platform.
443
 
try:
444
 
    # Python 2.4 and later have os.urandom,
445
 
    # but it doesn't work on some arches
446
 
    os.urandom(1)
 
218
 
 
219
if hasattr(os, 'urandom'): # python 2.4 and later
447
220
    rand_bytes = os.urandom
448
 
except (NotImplementedError, AttributeError):
449
 
    # If python doesn't have os.urandom, or it doesn't work,
450
 
    # then try to first pull random data from /dev/urandom
451
 
    if os.path.exists("/dev/urandom"):
452
 
        rand_bytes = file('/dev/urandom', 'rb').read
453
 
    # Otherwise, use this hack as a last resort
454
 
    else:
455
 
        # not well seeded, but better than nothing
456
 
        def rand_bytes(n):
457
 
            import random
458
 
            s = ''
459
 
            while n:
460
 
                s += chr(random.randint(0, 255))
461
 
                n -= 1
462
 
            return s
 
221
else:
 
222
    # FIXME: No good on non-Linux
 
223
    _rand_file = file('/dev/urandom', 'rb')
 
224
    rand_bytes = _rand_file.read
 
225
 
463
226
 
464
227
## TODO: We could later have path objects that remember their list
465
228
## decomposition (might be too tricksy though.)
478
241
    >>> splitpath('a/../b')
479
242
    Traceback (most recent call last):
480
243
    ...
481
 
    BzrError: sorry, '..' not allowed in path
 
244
    BzrError: ("sorry, '..' not allowed in path", [])
482
245
    """
483
246
    assert isinstance(p, types.StringTypes)
484
 
 
485
 
    # split on either delimiter because people might use either on
486
 
    # Windows
487
 
    ps = re.split(r'[\\/]', p)
488
 
 
489
 
    rps = []
 
247
    ps = [f for f in p.split('/') if f != '.']
490
248
    for f in ps:
491
249
        if f == '..':
492
 
            raise BzrError("sorry, %r not allowed in path" % f)
493
 
        elif (f == '.') or (f == ''):
494
 
            pass
495
 
        else:
496
 
            rps.append(f)
497
 
    return rps
 
250
            bailout("sorry, %r not allowed in path" % f)
 
251
    return ps
498
252
 
499
253
def joinpath(p):
500
254
    assert isinstance(p, list)
501
255
    for f in p:
502
 
        if (f == '..') or (f == None) or (f == ''):
503
 
            raise BzrError("sorry, %r not allowed in path" % f)
504
 
    return os.path.join(*p)
 
256
        if (f == '..') or (f is None) or (f == ''):
 
257
            bailout("sorry, %r not allowed in path" % f)
 
258
    return '/'.join(p)
505
259
 
506
260
 
507
261
def appendpath(p1, p2):
508
262
    if p1 == '':
509
263
        return p2
510
264
    else:
511
 
        return os.path.join(p1, p2)
 
265
        return p1 + '/' + p2
512
266
    
513
267
 
514
 
def _read_config_value(name):
515
 
    """Read a config value from the file ~/.bzr.conf/<name>
516
 
    Return None if the file does not exist"""
517
 
    try:
518
 
        f = file(os.path.join(config_dir(), name), "r")
519
 
        return f.read().decode(bzrlib.user_encoding).rstrip("\r\n")
520
 
    except IOError, e:
521
 
        if e.errno == errno.ENOENT:
522
 
            return None
523
 
        raise
524
 
 
525
 
 
526
 
def split_lines(s):
527
 
    """Split s into lines, but without removing the newline characters."""
528
 
    return StringIO(s).readlines()
529
 
 
530
 
 
531
 
def hardlinks_good():
532
 
    return sys.platform not in ('win32', 'cygwin', 'darwin')
533
 
 
534
 
 
535
 
def link_or_copy(src, dest):
536
 
    """Hardlink a file, or copy it if it can't be hardlinked."""
537
 
    if not hardlinks_good():
538
 
        copyfile(src, dest)
539
 
        return
540
 
    try:
541
 
        os.link(src, dest)
542
 
    except (OSError, IOError), e:
543
 
        if e.errno != errno.EXDEV:
544
 
            raise
545
 
        copyfile(src, dest)
546
 
 
547
 
 
548
 
def has_symlinks():
549
 
    if hasattr(os, 'symlink'):
550
 
        return True
551
 
    else:
552
 
        return False
 
268
def extern_command(cmd, ignore_errors = False):
 
269
    mutter('external command: %s' % `cmd`)
 
270
    if os.system(cmd):
 
271
        if not ignore_errors:
 
272
            bailout('command failed')
 
273