~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Martin Pool
  • Date: 2005-06-30 10:03:13 UTC
  • mto: This revision was merged to the branch mainline in revision 852.
  • Revision ID: mbp@sourcefrog.net-20050630100313-b032ce6b905ce4ee
Remove dead code

Show diffs side-by-side

added added

removed removed

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