~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Martin Pool
  • Date: 2005-05-10 06:00:59 UTC
  • Revision ID: mbp@sourcefrog.net-20050510060059-bae67a465325f650
- Use AtomicFile to update statcache.
- New closed property on AtomicFile

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
 
19
import os, types, re, time, errno
 
20
from stat import S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE
30
21
 
 
22
from errors import bailout, BzrError
 
23
from trace import mutter
31
24
import bzrlib
32
 
from bzrlib.errors import BzrError
33
 
from bzrlib.trace import mutter
34
 
 
35
25
 
36
26
def make_readonly(filename):
37
27
    """Make a filename read-only."""
 
28
    # TODO: probably needs to be fixed for windows
38
29
    mod = os.stat(filename).st_mode
39
30
    mod = mod & 0777555
40
31
    os.chmod(filename, mod)
46
37
    os.chmod(filename, mod)
47
38
 
48
39
 
49
 
_QUOTE_RE = None
50
 
 
51
 
 
 
40
_QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])')
52
41
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
 
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
66
48
 
67
49
 
68
50
def file_kind(f):
73
55
        return 'directory'
74
56
    elif S_ISLNK(mode):
75
57
        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
 
58
    else:
 
59
        raise BzrError("can't handle file kind with mode %o of %r" % (mode, f)) 
 
60
 
151
61
 
152
62
 
153
63
def isdir(f):
158
68
        return False
159
69
 
160
70
 
 
71
 
161
72
def isfile(f):
162
73
    """True if f is a regular file."""
163
74
    try:
165
76
    except OSError:
166
77
        return False
167
78
 
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
79
 
221
80
def pumpfile(fromfile, tofile):
222
81
    """Copy contents of one file to another."""
223
82
    tofile.write(fromfile.read())
224
83
 
225
84
 
 
85
def uuid():
 
86
    """Return a new UUID"""
 
87
    try:
 
88
        return file('/proc/sys/kernel/random/uuid').readline().rstrip('\n')
 
89
    except IOError:
 
90
        return chomp(os.popen('uuidgen').readline())
 
91
 
 
92
 
226
93
def sha_file(f):
 
94
    import sha
227
95
    if hasattr(f, 'tell'):
228
96
        assert f.tell() == 0
229
97
    s = sha.new()
236
104
    return s.hexdigest()
237
105
 
238
106
 
239
 
 
240
 
def sha_strings(strings):
241
 
    """Return the sha-1 of concatenation of strings"""
242
 
    s = sha.new()
243
 
    map(s.update, strings)
244
 
    return s.hexdigest()
245
 
 
246
 
 
247
107
def sha_string(f):
 
108
    import sha
248
109
    s = sha.new()
249
110
    s.update(f)
250
111
    return s.hexdigest()
251
112
 
252
113
 
 
114
 
253
115
def fingerprint_file(f):
 
116
    import sha
254
117
    s = sha.new()
255
118
    b = f.read()
256
119
    s.update(b)
266
129
    
267
130
    TODO: Global option --config-dir to override this.
268
131
    """
269
 
    return os.path.join(os.path.expanduser("~"), ".bzr.conf")
 
132
    return os.path.expanduser("~/.bzr.conf")
270
133
 
271
134
 
272
135
def _auto_user_id():
305
168
    return realname, (username + '@' + socket.gethostname())
306
169
 
307
170
 
308
 
def _get_user_id(branch):
 
171
def _get_user_id():
309
172
    """Return the full user id from a file or environment variable.
310
173
 
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
 
    """
 
174
    TODO: Allow taking this from a file in the branch directory too
 
175
    for per-branch ids."""
323
176
    v = os.environ.get('BZREMAIL')
324
177
    if v:
325
178
        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
179
    
339
180
    try:
340
181
        return (open(os.path.join(config_dir(), "email"))
352
193
        return None
353
194
 
354
195
 
355
 
def username(branch):
 
196
def username():
356
197
    """Return email-style username.
357
198
 
358
199
    Something similar to 'Martin Pool <mbp@sourcefrog.net>'
359
200
 
360
201
    TODO: Check it's reasonably well-formed.
361
202
    """
362
 
    v = _get_user_id(branch)
 
203
    v = _get_user_id()
363
204
    if v:
364
205
        return v
365
206
    
370
211
        return email
371
212
 
372
213
 
373
 
def user_email(branch):
 
214
_EMAIL_RE = re.compile(r'[\w+.-]+@[\w+.-]+')
 
215
def user_email():
374
216
    """Return just the email component of a username."""
375
 
    e = _get_user_id(branch)
 
217
    e = _get_user_id()
376
218
    if e:
377
 
        m = re.search(r'[\w+.-]+@[\w+.-]+', e)
 
219
        m = _EMAIL_RE.search(e)
378
220
        if not m:
379
 
            raise BzrError("%r doesn't seem to contain "
380
 
                           "a reasonable email address" % e)
 
221
            bailout("%r doesn't seem to contain a reasonable email address" % e)
381
222
        return m.group(0)
382
223
 
383
224
    return _auto_user_id()[1]
 
225
    
384
226
 
385
227
 
386
228
def compare_files(a, b):
395
237
            return True
396
238
 
397
239
 
 
240
 
398
241
def local_time_offset(t=None):
399
242
    """Return offset of local zone from GMT, either at present or at time t."""
400
243
    # python2.3 localtime() can't take None
423
266
        tt = time.localtime(t)
424
267
        offset = local_time_offset(t)
425
268
    else:
426
 
        raise BzrError("unsupported timezone format %r" % timezone,
427
 
                       ['options are "utc", "original", "local"'])
 
269
        bailout("unsupported timezone format %r",
 
270
                ['options are "utc", "original", "local"'])
428
271
 
429
272
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
430
273
            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
439
282
    """Return size of given open file."""
440
283
    return os.fstat(f.fileno())[ST_SIZE]
441
284
 
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)
 
285
 
 
286
if hasattr(os, 'urandom'): # python 2.4 and later
447
287
    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
 
288
else:
 
289
    # FIXME: No good on non-Linux
 
290
    _rand_file = file('/dev/urandom', 'rb')
 
291
    rand_bytes = _rand_file.read
 
292
 
463
293
 
464
294
## TODO: We could later have path objects that remember their list
465
295
## decomposition (might be too tricksy though.)
478
308
    >>> splitpath('a/../b')
479
309
    Traceback (most recent call last):
480
310
    ...
481
 
    BzrError: sorry, '..' not allowed in path
 
311
    BzrError: ("sorry, '..' not allowed in path", [])
482
312
    """
483
313
    assert isinstance(p, types.StringTypes)
484
314
 
489
319
    rps = []
490
320
    for f in ps:
491
321
        if f == '..':
492
 
            raise BzrError("sorry, %r not allowed in path" % f)
 
322
            bailout("sorry, %r not allowed in path" % f)
493
323
        elif (f == '.') or (f == ''):
494
324
            pass
495
325
        else:
500
330
    assert isinstance(p, list)
501
331
    for f in p:
502
332
        if (f == '..') or (f == None) or (f == ''):
503
 
            raise BzrError("sorry, %r not allowed in path" % f)
 
333
            bailout("sorry, %r not allowed in path" % f)
504
334
    return os.path.join(*p)
505
335
 
506
336
 
511
341
        return os.path.join(p1, p2)
512
342
    
513
343
 
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
 
344
def extern_command(cmd, ignore_errors = False):
 
345
    mutter('external command: %s' % `cmd`)
 
346
    if os.system(cmd):
 
347
        if not ignore_errors:
 
348
            bailout('command failed')
 
349