~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

merge merge tweaks from aaron, which includes latest .dev

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
 
19
import os, types, re, time, errno, sys
20
20
from stat import S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE
21
21
 
22
 
from errors import bailout
 
22
from bzrlib.errors import BzrError
 
23
from bzrlib.trace import mutter
 
24
import bzrlib
23
25
 
24
26
def make_readonly(filename):
25
27
    """Make a filename read-only."""
35
37
    os.chmod(filename, mod)
36
38
 
37
39
 
38
 
_QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])')
 
40
_QUOTE_RE = None
 
41
 
 
42
 
39
43
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
 
44
    """Return a quoted filename filename
 
45
 
 
46
    This previously used backslash quoting, but that works poorly on
 
47
    Windows."""
 
48
    # TODO: I'm not really sure this is the best format either.x
 
49
    global _QUOTE_RE
 
50
    if _QUOTE_RE == None:
 
51
        _QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/_~-])')
 
52
        
 
53
    if _QUOTE_RE.search(f):
 
54
        return '"' + f + '"'
 
55
    else:
 
56
        return f
46
57
 
47
58
 
48
59
def file_kind(f):
54
65
    elif S_ISLNK(mode):
55
66
        return 'symlink'
56
67
    else:
57
 
        bailout("can't handle file kind with mode %o of %r" % (mode, f)) 
 
68
        raise BzrError("can't handle file kind with mode %o of %r" % (mode, f))
 
69
 
 
70
 
 
71
def kind_marker(kind):
 
72
    if kind == 'file':
 
73
        return ''
 
74
    elif kind == 'directory':
 
75
        return '/'
 
76
    elif kind == 'symlink':
 
77
        return '@'
 
78
    else:
 
79
        raise BzrError('invalid file kind %r' % kind)
 
80
 
 
81
 
 
82
 
 
83
def backup_file(fn):
 
84
    """Copy a file to a backup.
 
85
 
 
86
    Backups are named in GNU-style, with a ~ suffix.
 
87
 
 
88
    If the file is already a backup, it's not copied.
 
89
    """
 
90
    import os
 
91
    if fn[-1] == '~':
 
92
        return
 
93
    bfn = fn + '~'
 
94
 
 
95
    inf = file(fn, 'rb')
 
96
    try:
 
97
        content = inf.read()
 
98
    finally:
 
99
        inf.close()
 
100
    
 
101
    outf = file(bfn, 'wb')
 
102
    try:
 
103
        outf.write(content)
 
104
    finally:
 
105
        outf.close()
 
106
 
 
107
def rename(path_from, path_to):
 
108
    """Basically the same as os.rename() just special for win32"""
 
109
    if sys.platform == 'win32':
 
110
        try:
 
111
            os.remove(path_to)
 
112
        except OSError, e:
 
113
            if e.errno != e.ENOENT:
 
114
                raise
 
115
    os.rename(path_from, path_to)
 
116
 
 
117
 
58
118
 
59
119
 
60
120
 
75
135
        return False
76
136
 
77
137
 
 
138
def is_inside(dir, fname):
 
139
    """True if fname is inside dir.
 
140
    
 
141
    The parameters should typically be passed to os.path.normpath first, so
 
142
    that . and .. and repeated slashes are eliminated, and the separators
 
143
    are canonical for the platform.
 
144
    
 
145
    The empty string as a dir name is taken as top-of-tree and matches 
 
146
    everything.
 
147
    
 
148
    >>> is_inside('src', 'src/foo.c')
 
149
    True
 
150
    >>> is_inside('src', 'srccontrol')
 
151
    False
 
152
    >>> is_inside('src', 'src/a/a/a/foo.c')
 
153
    True
 
154
    >>> is_inside('foo.c', 'foo.c')
 
155
    True
 
156
    >>> is_inside('foo.c', '')
 
157
    False
 
158
    >>> is_inside('', 'foo.c')
 
159
    True
 
160
    """
 
161
    # XXX: Most callers of this can actually do something smarter by 
 
162
    # looking at the inventory
 
163
    if dir == fname:
 
164
        return True
 
165
    
 
166
    if dir == '':
 
167
        return True
 
168
    
 
169
    if dir[-1] != os.sep:
 
170
        dir += os.sep
 
171
    
 
172
    return fname.startswith(dir)
 
173
 
 
174
 
 
175
def is_inside_any(dir_list, fname):
 
176
    """True if fname is inside any of given dirs."""
 
177
    for dirname in dir_list:
 
178
        if is_inside(dirname, fname):
 
179
            return True
 
180
    else:
 
181
        return False
 
182
 
 
183
 
78
184
def pumpfile(fromfile, tofile):
79
185
    """Copy contents of one file to another."""
80
186
    tofile.write(fromfile.read())
82
188
 
83
189
def uuid():
84
190
    """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
191
    try:
90
 
        return chomp(file('/proc/sys/kernel/random/uuid').readline())
 
192
        return file('/proc/sys/kernel/random/uuid').readline().rstrip('\n')
91
193
    except IOError:
92
194
        return chomp(os.popen('uuidgen').readline())
93
195
 
94
196
 
95
 
def chomp(s):
96
 
    if s and (s[-1] == '\n'):
97
 
        return s[:-1]
98
 
    else:
99
 
        return s
100
 
 
101
 
 
102
197
def sha_file(f):
103
198
    import sha
104
 
    ## TODO: Maybe read in chunks to handle big files
105
199
    if hasattr(f, 'tell'):
106
200
        assert f.tell() == 0
107
201
    s = sha.new()
108
 
    s.update(f.read())
 
202
    BUFSIZE = 128<<10
 
203
    while True:
 
204
        b = f.read(BUFSIZE)
 
205
        if not b:
 
206
            break
 
207
        s.update(b)
109
208
    return s.hexdigest()
110
209
 
111
210
 
117
216
 
118
217
 
119
218
 
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
 
 
 
219
def fingerprint_file(f):
 
220
    import sha
 
221
    s = sha.new()
 
222
    b = f.read()
 
223
    s.update(b)
 
224
    size = len(b)
 
225
    return {'size': size,
 
226
            'sha1': s.hexdigest()}
 
227
 
 
228
 
 
229
def config_dir():
 
230
    """Return per-user configuration directory.
 
231
 
 
232
    By default this is ~/.bzr.conf/
 
233
    
 
234
    TODO: Global option --config-dir to override this.
 
235
    """
 
236
    return os.path.expanduser("~/.bzr.conf")
 
237
 
 
238
 
 
239
def _auto_user_id():
 
240
    """Calculate automatic user identification.
 
241
 
 
242
    Returns (realname, email).
 
243
 
 
244
    Only used when none is set in the environment or the id file.
 
245
 
 
246
    This previously used the FQDN as the default domain, but that can
 
247
    be very slow on machines where DNS is broken.  So now we simply
 
248
    use the hostname.
 
249
    """
135
250
    import socket
136
 
    
 
251
 
 
252
    # XXX: Any good way to get real user name on win32?
 
253
 
137
254
    try:
138
255
        import pwd
139
256
        uid = os.getuid()
140
257
        w = pwd.getpwuid(uid)
141
 
        gecos = w.pw_gecos
 
258
        gecos = w.pw_gecos.decode(bzrlib.user_encoding)
 
259
        username = w.pw_name.decode(bzrlib.user_encoding)
142
260
        comma = gecos.find(',')
143
261
        if comma == -1:
144
262
            realname = gecos
145
263
        else:
146
264
            realname = gecos[:comma]
147
 
        return '%s <%s@%s>' % (realname, w.pw_name, socket.getfqdn())
 
265
        if not realname:
 
266
            realname = username
 
267
 
148
268
    except ImportError:
149
 
        pass
150
 
 
151
 
    import getpass, socket
152
 
    return '<%s@%s>' % (getpass.getuser(), socket.getfqdn())
153
 
 
154
 
 
155
 
def user_email():
 
269
        import getpass
 
270
        realname = username = getpass.getuser().decode(bzrlib.user_encoding)
 
271
 
 
272
    return realname, (username + '@' + socket.gethostname())
 
273
 
 
274
 
 
275
def _get_user_id(branch):
 
276
    """Return the full user id from a file or environment variable.
 
277
 
 
278
    e.g. "John Hacker <jhacker@foo.org>"
 
279
 
 
280
    branch
 
281
        A branch to use for a per-branch configuration, or None.
 
282
 
 
283
    The following are searched in order:
 
284
 
 
285
    1. $BZREMAIL
 
286
    2. .bzr/email for this branch.
 
287
    3. ~/.bzr.conf/email
 
288
    4. $EMAIL
 
289
    """
 
290
    v = os.environ.get('BZREMAIL')
 
291
    if v:
 
292
        return v.decode(bzrlib.user_encoding)
 
293
 
 
294
    if branch:
 
295
        try:
 
296
            return (branch.controlfile("email", "r") 
 
297
                    .read()
 
298
                    .decode(bzrlib.user_encoding)
 
299
                    .rstrip("\r\n"))
 
300
        except IOError, e:
 
301
            if e.errno != errno.ENOENT:
 
302
                raise
 
303
        except BzrError, e:
 
304
            pass
 
305
    
 
306
    try:
 
307
        return (open(os.path.join(config_dir(), "email"))
 
308
                .read()
 
309
                .decode(bzrlib.user_encoding)
 
310
                .rstrip("\r\n"))
 
311
    except IOError, e:
 
312
        if e.errno != errno.ENOENT:
 
313
            raise e
 
314
 
 
315
    v = os.environ.get('EMAIL')
 
316
    if v:
 
317
        return v.decode(bzrlib.user_encoding)
 
318
    else:    
 
319
        return None
 
320
 
 
321
 
 
322
def username(branch):
 
323
    """Return email-style username.
 
324
 
 
325
    Something similar to 'Martin Pool <mbp@sourcefrog.net>'
 
326
 
 
327
    TODO: Check it's reasonably well-formed.
 
328
    """
 
329
    v = _get_user_id(branch)
 
330
    if v:
 
331
        return v
 
332
    
 
333
    name, email = _auto_user_id()
 
334
    if name:
 
335
        return '%s <%s>' % (name, email)
 
336
    else:
 
337
        return email
 
338
 
 
339
 
 
340
def user_email(branch):
156
341
    """Return just the email component of a username."""
157
 
    e = os.environ.get('BZREMAIL') or os.environ.get('EMAIL')
 
342
    e = _get_user_id(branch)
158
343
    if e:
159
 
        import re
160
344
        m = re.search(r'[\w+.-]+@[\w+.-]+', e)
161
345
        if not m:
162
 
            bailout('%r is not a reasonable email address' % e)
 
346
            raise BzrError("%r doesn't seem to contain a reasonable email address" % e)
163
347
        return m.group(0)
164
348
 
165
 
 
166
 
    import getpass, socket
167
 
    return '%s@%s' % (getpass.getuser(), socket.getfqdn())
168
 
 
 
349
    return _auto_user_id()[1]
169
350
    
170
351
 
171
352
 
172
353
def compare_files(a, b):
173
354
    """Returns true if equal in contents"""
174
 
    # TODO: don't read the whole thing in one go.
175
355
    BUFSIZE = 4096
176
356
    while True:
177
357
        ai = a.read(BUFSIZE)
186
366
def local_time_offset(t=None):
187
367
    """Return offset of local zone from GMT, either at present or at time t."""
188
368
    # python2.3 localtime() can't take None
189
 
    if t is None:
 
369
    if t == None:
190
370
        t = time.time()
191
371
        
192
372
    if time.localtime(t).tm_isdst and time.daylight:
198
378
def format_date(t, offset=0, timezone='original'):
199
379
    ## TODO: Perhaps a global option to use either universal or local time?
200
380
    ## Or perhaps just let people set $TZ?
201
 
    import time
202
 
    
203
381
    assert isinstance(t, float)
204
382
    
205
383
    if timezone == 'utc':
213
391
        tt = time.localtime(t)
214
392
        offset = local_time_offset(t)
215
393
    else:
216
 
        bailout("unsupported timezone format %r",
217
 
                ['options are "utc", "original", "local"'])
 
394
        raise BzrError("unsupported timezone format %r" % timezone,
 
395
                       ['options are "utc", "original", "local"'])
218
396
 
219
397
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
220
398
            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
229
407
    """Return size of given open file."""
230
408
    return os.fstat(f.fileno())[ST_SIZE]
231
409
 
232
 
 
233
 
if hasattr(os, 'urandom'): # python 2.4 and later
 
410
# Define rand_bytes based on platform.
 
411
try:
 
412
    # Python 2.4 and later have os.urandom,
 
413
    # but it doesn't work on some arches
 
414
    os.urandom(1)
234
415
    rand_bytes = os.urandom
235
 
else:
236
 
    # FIXME: No good on non-Linux
237
 
    _rand_file = file('/dev/urandom', 'rb')
238
 
    rand_bytes = _rand_file.read
239
 
 
 
416
except (NotImplementedError, AttributeError):
 
417
    # If python doesn't have os.urandom, or it doesn't work,
 
418
    # then try to first pull random data from /dev/urandom
 
419
    if os.path.exists("/dev/urandom"):
 
420
        rand_bytes = file('/dev/urandom', 'rb').read
 
421
    # Otherwise, use this hack as a last resort
 
422
    else:
 
423
        # not well seeded, but better than nothing
 
424
        def rand_bytes(n):
 
425
            import random
 
426
            s = ''
 
427
            while n:
 
428
                s += chr(random.randint(0, 255))
 
429
                n -= 1
 
430
            return s
240
431
 
241
432
## TODO: We could later have path objects that remember their list
242
433
## decomposition (might be too tricksy though.)
255
446
    >>> splitpath('a/../b')
256
447
    Traceback (most recent call last):
257
448
    ...
258
 
    BzrError: ("sorry, '..' not allowed in path", [])
 
449
    BzrError: sorry, '..' not allowed in path
259
450
    """
260
451
    assert isinstance(p, types.StringTypes)
261
 
    ps = [f for f in p.split('/') if f != '.']
 
452
 
 
453
    # split on either delimiter because people might use either on
 
454
    # Windows
 
455
    ps = re.split(r'[\\/]', p)
 
456
 
 
457
    rps = []
262
458
    for f in ps:
263
459
        if f == '..':
264
 
            bailout("sorry, %r not allowed in path" % f)
265
 
    return ps
 
460
            raise BzrError("sorry, %r not allowed in path" % f)
 
461
        elif (f == '.') or (f == ''):
 
462
            pass
 
463
        else:
 
464
            rps.append(f)
 
465
    return rps
266
466
 
267
467
def joinpath(p):
268
468
    assert isinstance(p, list)
269
469
    for f in p:
270
 
        if (f == '..') or (f is None) or (f == ''):
271
 
            bailout("sorry, %r not allowed in path" % f)
272
 
    return '/'.join(p)
 
470
        if (f == '..') or (f == None) or (f == ''):
 
471
            raise BzrError("sorry, %r not allowed in path" % f)
 
472
    return os.path.join(*p)
273
473
 
274
474
 
275
475
def appendpath(p1, p2):
276
476
    if p1 == '':
277
477
        return p2
278
478
    else:
279
 
        return p1 + '/' + p2
 
479
        return os.path.join(p1, p2)
280
480
    
281
481
 
282
482
def extern_command(cmd, ignore_errors = False):
283
483
    mutter('external command: %s' % `cmd`)
284
484
    if os.system(cmd):
285
485
        if not ignore_errors:
286
 
            bailout('command failed')
 
486
            raise BzrError('command failed')
 
487
 
 
488
 
 
489
def _read_config_value(name):
 
490
    """Read a config value from the file ~/.bzr.conf/<name>
 
491
    Return None if the file does not exist"""
 
492
    try:
 
493
        f = file(os.path.join(config_dir(), name), "r")
 
494
        return f.read().decode(bzrlib.user_encoding).rstrip("\r\n")
 
495
    except IOError, e:
 
496
        if e.errno == errno.ENOENT:
 
497
            return None
 
498
        raise
 
499
 
287
500