~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: mbp at sourcefrog
  • Date: 2005-03-23 06:25:55 UTC
  • Revision ID: mbp@sourcefrog.net-20050323062555-5489339018d0c043
- import a subset of elementtree for easier installation

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
    try:
 
90
        return chomp(file('/proc/sys/kernel/random/uuid').readline())
 
91
    except IOError:
 
92
        return chomp(os.popen('uuidgen').readline())
 
93
 
 
94
 
 
95
def chomp(s):
 
96
    if s and (s[-1] == '\n'):
 
97
        return s[:-1]
 
98
    else:
 
99
        return s
 
100
 
 
101
 
226
102
def sha_file(f):
 
103
    import sha
 
104
    ## TODO: Maybe read in chunks to handle big files
227
105
    if hasattr(f, 'tell'):
228
106
        assert f.tell() == 0
229
107
    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)
 
108
    s.update(f.read())
244
109
    return s.hexdigest()
245
110
 
246
111
 
247
112
def sha_string(f):
 
113
    import sha
248
114
    s = sha.new()
249
115
    s.update(f)
250
116
    return s.hexdigest()
251
117
 
252
118
 
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
 
    """
 
119
 
 
120
def username():
 
121
    """Return email-style username.
 
122
 
 
123
    Something similar to 'Martin Pool <mbp@sourcefrog.net>'
 
124
 
 
125
    :todo: Check it's reasonably well-formed.
 
126
 
 
127
    :todo: Allow taking it from a dotfile to help people on windows
 
128
           who can't easily set variables.
 
129
 
 
130
    :todo: Cope without pwd module, which is only on unix. 
 
131
    """
 
132
    e = os.environ.get('BZREMAIL') or os.environ.get('EMAIL')
 
133
    if e: return e
 
134
 
283
135
    import socket
284
 
 
285
 
    # XXX: Any good way to get real user name on win32?
286
 
 
 
136
    
287
137
    try:
288
138
        import pwd
289
139
        uid = os.getuid()
290
140
        w = pwd.getpwuid(uid)
291
 
        gecos = w.pw_gecos.decode(bzrlib.user_encoding)
292
 
        username = w.pw_name.decode(bzrlib.user_encoding)
 
141
        gecos = w.pw_gecos
293
142
        comma = gecos.find(',')
294
143
        if comma == -1:
295
144
            realname = gecos
296
145
        else:
297
146
            realname = gecos[:comma]
298
 
        if not realname:
299
 
            realname = username
300
 
 
 
147
        return '%s <%s@%s>' % (realname, w.pw_name, socket.getfqdn())
301
148
    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):
 
149
        pass
 
150
 
 
151
    import getpass, socket
 
152
    return '<%s@%s>' % (getpass.getuser(), socket.getfqdn())
 
153
 
 
154
 
 
155
def user_email():
374
156
    """Return just the email component of a username."""
375
 
    e = _get_user_id(branch)
 
157
    e = os.environ.get('BZREMAIL') or os.environ.get('EMAIL')
376
158
    if e:
 
159
        import re
377
160
        m = re.search(r'[\w+.-]+@[\w+.-]+', e)
378
161
        if not m:
379
 
            raise BzrError("%r doesn't seem to contain "
380
 
                           "a reasonable email address" % e)
 
162
            bailout('%r is not a reasonable email address' % e)
381
163
        return m.group(0)
382
164
 
383
 
    return _auto_user_id()[1]
 
165
 
 
166
    import getpass, socket
 
167
    return '%s@%s' % (getpass.getuser(), socket.getfqdn())
 
168
 
 
169
    
384
170
 
385
171
 
386
172
def compare_files(a, b):
387
173
    """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
 
174
    # TODO: don't read the whole thing in one go.
 
175
    result = a.read() == b.read()
 
176
    return result
 
177
 
396
178
 
397
179
 
398
180
def local_time_offset(t=None):
399
181
    """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
182
    if time.localtime(t).tm_isdst and time.daylight:
405
183
        return -time.altzone
406
184
    else:
410
188
def format_date(t, offset=0, timezone='original'):
411
189
    ## TODO: Perhaps a global option to use either universal or local time?
412
190
    ## Or perhaps just let people set $TZ?
 
191
    import time
 
192
    
413
193
    assert isinstance(t, float)
414
194
    
415
195
    if timezone == 'utc':
423
203
        tt = time.localtime(t)
424
204
        offset = local_time_offset(t)
425
205
    else:
426
 
        raise BzrError("unsupported timezone format %r" % timezone,
427
 
                       ['options are "utc", "original", "local"'])
 
206
        bailout("unsupported timezone format %r",
 
207
                ['options are "utc", "original", "local"'])
428
208
 
429
209
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
430
210
            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
439
219
    """Return size of given open file."""
440
220
    return os.fstat(f.fileno())[ST_SIZE]
441
221
 
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)
 
222
 
 
223
if hasattr(os, 'urandom'): # python 2.4 and later
447
224
    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
 
225
else:
 
226
    # FIXME: No good on non-Linux
 
227
    _rand_file = file('/dev/urandom', 'rb')
 
228
    rand_bytes = _rand_file.read
 
229
 
463
230
 
464
231
## TODO: We could later have path objects that remember their list
465
232
## decomposition (might be too tricksy though.)
478
245
    >>> splitpath('a/../b')
479
246
    Traceback (most recent call last):
480
247
    ...
481
 
    BzrError: sorry, '..' not allowed in path
 
248
    BzrError: ("sorry, '..' not allowed in path", [])
482
249
    """
483
250
    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 = []
 
251
    ps = [f for f in p.split('/') if f != '.']
490
252
    for f in ps:
491
253
        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
 
254
            bailout("sorry, %r not allowed in path" % f)
 
255
    return ps
498
256
 
499
257
def joinpath(p):
500
258
    assert isinstance(p, list)
501
259
    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)
 
260
        if (f == '..') or (f is None) or (f == ''):
 
261
            bailout("sorry, %r not allowed in path" % f)
 
262
    return '/'.join(p)
505
263
 
506
264
 
507
265
def appendpath(p1, p2):
508
266
    if p1 == '':
509
267
        return p2
510
268
    else:
511
 
        return os.path.join(p1, p2)
 
269
        return p1 + '/' + p2
512
270
    
513
271
 
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
 
272
def extern_command(cmd, ignore_errors = False):
 
273
    mutter('external command: %s' % `cmd`)
 
274
    if os.system(cmd):
 
275
        if not ignore_errors:
 
276
            bailout('command failed')
 
277