/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/osutils.py

  • Committer: Robert Collins
  • Date: 2005-09-27 07:24:40 UTC
  • mfrom: (1185.1.41)
  • Revision ID: robertc@robertcollins.net-20050927072440-1bf4d99c3e1db5b3
pair programming worx... merge integration and weave

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
from stat import (S_ISREG, S_ISDIR, S_ISLNK, ST_MODE, ST_SIZE,
 
20
                  S_ISCHR, S_ISBLK, S_ISFIFO, S_ISSOCK)
 
21
from cStringIO import StringIO
 
22
import errno
 
23
import os
 
24
import re
 
25
import sha
 
26
import sys
 
27
import time
 
28
import types
 
29
 
 
30
import bzrlib
 
31
from bzrlib.errors import BzrError
 
32
from bzrlib.trace import mutter
 
33
 
 
34
 
 
35
def make_readonly(filename):
 
36
    """Make a filename read-only."""
 
37
    mod = os.stat(filename).st_mode
 
38
    mod = mod & 0777555
 
39
    os.chmod(filename, mod)
 
40
 
 
41
 
 
42
def make_writable(filename):
 
43
    mod = os.stat(filename).st_mode
 
44
    mod = mod | 0200
 
45
    os.chmod(filename, mod)
 
46
 
 
47
 
 
48
_QUOTE_RE = None
 
49
 
 
50
 
 
51
def quotefn(f):
 
52
    """Return a quoted filename filename
 
53
 
 
54
    This previously used backslash quoting, but that works poorly on
 
55
    Windows."""
 
56
    # TODO: I'm not really sure this is the best format either.x
 
57
    global _QUOTE_RE
 
58
    if _QUOTE_RE == None:
 
59
        _QUOTE_RE = re.compile(r'([^a-zA-Z0-9.,:/\\_~-])')
 
60
        
 
61
    if _QUOTE_RE.search(f):
 
62
        return '"' + f + '"'
 
63
    else:
 
64
        return f
 
65
 
 
66
 
 
67
def file_kind(f):
 
68
    mode = os.lstat(f)[ST_MODE]
 
69
    if S_ISREG(mode):
 
70
        return 'file'
 
71
    elif S_ISDIR(mode):
 
72
        return 'directory'
 
73
    elif S_ISLNK(mode):
 
74
        return 'symlink'
 
75
    elif S_ISCHR(mode):
 
76
        return 'chardev'
 
77
    elif S_ISBLK(mode):
 
78
        return 'block'
 
79
    elif S_ISFIFO(mode):
 
80
        return 'fifo'
 
81
    elif S_ISSOCK(mode):
 
82
        return 'socket'
 
83
    else:
 
84
        return 'unknown'
 
85
 
 
86
 
 
87
def kind_marker(kind):
 
88
    if kind == 'file':
 
89
        return ''
 
90
    elif kind == 'directory':
 
91
        return '/'
 
92
    elif kind == 'symlink':
 
93
        return '@'
 
94
    else:
 
95
        raise BzrError('invalid file kind %r' % kind)
 
96
 
 
97
 
 
98
def backup_file(fn):
 
99
    """Copy a file to a backup.
 
100
 
 
101
    Backups are named in GNU-style, with a ~ suffix.
 
102
 
 
103
    If the file is already a backup, it's not copied.
 
104
    """
 
105
    if fn[-1] == '~':
 
106
        return
 
107
    bfn = fn + '~'
 
108
 
 
109
    inf = file(fn, 'rb')
 
110
    try:
 
111
        content = inf.read()
 
112
    finally:
 
113
        inf.close()
 
114
    
 
115
    outf = file(bfn, 'wb')
 
116
    try:
 
117
        outf.write(content)
 
118
    finally:
 
119
        outf.close()
 
120
 
 
121
if os.name == 'nt':
 
122
    import shutil
 
123
    rename = shutil.move
 
124
else:
 
125
    rename = os.rename
 
126
 
 
127
 
 
128
def isdir(f):
 
129
    """True if f is an accessible directory."""
 
130
    try:
 
131
        return S_ISDIR(os.lstat(f)[ST_MODE])
 
132
    except OSError:
 
133
        return False
 
134
 
 
135
 
 
136
def isfile(f):
 
137
    """True if f is a regular file."""
 
138
    try:
 
139
        return S_ISREG(os.lstat(f)[ST_MODE])
 
140
    except OSError:
 
141
        return False
 
142
 
 
143
 
 
144
def is_inside(dir, fname):
 
145
    """True if fname is inside dir.
 
146
    
 
147
    The parameters should typically be passed to os.path.normpath first, so
 
148
    that . and .. and repeated slashes are eliminated, and the separators
 
149
    are canonical for the platform.
 
150
    
 
151
    The empty string as a dir name is taken as top-of-tree and matches 
 
152
    everything.
 
153
    
 
154
    >>> is_inside('src', os.path.join('src', 'foo.c'))
 
155
    True
 
156
    >>> is_inside('src', 'srccontrol')
 
157
    False
 
158
    >>> is_inside('src', os.path.join('src', 'a', 'a', 'a', 'foo.c'))
 
159
    True
 
160
    >>> is_inside('foo.c', 'foo.c')
 
161
    True
 
162
    >>> is_inside('foo.c', '')
 
163
    False
 
164
    >>> is_inside('', 'foo.c')
 
165
    True
 
166
    """
 
167
    # XXX: Most callers of this can actually do something smarter by 
 
168
    # looking at the inventory
 
169
    if dir == fname:
 
170
        return True
 
171
    
 
172
    if dir == '':
 
173
        return True
 
174
 
 
175
    if dir[-1] != os.sep:
 
176
        dir += os.sep
 
177
 
 
178
    return fname.startswith(dir)
 
179
 
 
180
 
 
181
def is_inside_any(dir_list, fname):
 
182
    """True if fname is inside any of given dirs."""
 
183
    for dirname in dir_list:
 
184
        if is_inside(dirname, fname):
 
185
            return True
 
186
    else:
 
187
        return False
 
188
 
 
189
 
 
190
def pumpfile(fromfile, tofile):
 
191
    """Copy contents of one file to another."""
 
192
    tofile.write(fromfile.read())
 
193
 
 
194
 
 
195
def sha_file(f):
 
196
    if hasattr(f, 'tell'):
 
197
        assert f.tell() == 0
 
198
    s = sha.new()
 
199
    BUFSIZE = 128<<10
 
200
    while True:
 
201
        b = f.read(BUFSIZE)
 
202
        if not b:
 
203
            break
 
204
        s.update(b)
 
205
    return s.hexdigest()
 
206
 
 
207
 
 
208
 
 
209
def sha_strings(strings):
 
210
    """Return the sha-1 of concatenation of strings"""
 
211
    s = sha.new()
 
212
    map(s.update, strings)
 
213
    return s.hexdigest()
 
214
 
 
215
 
 
216
def sha_string(f):
 
217
    s = sha.new()
 
218
    s.update(f)
 
219
    return s.hexdigest()
 
220
 
 
221
 
 
222
def fingerprint_file(f):
 
223
    s = sha.new()
 
224
    b = f.read()
 
225
    s.update(b)
 
226
    size = len(b)
 
227
    return {'size': size,
 
228
            'sha1': s.hexdigest()}
 
229
 
 
230
 
 
231
def config_dir():
 
232
    """Return per-user configuration directory.
 
233
 
 
234
    By default this is ~/.bzr.conf/
 
235
    
 
236
    TODO: Global option --config-dir to override this.
 
237
    """
 
238
    return os.path.join(os.path.expanduser("~"), ".bzr.conf")
 
239
 
 
240
 
 
241
def _auto_user_id():
 
242
    """Calculate automatic user identification.
 
243
 
 
244
    Returns (realname, email).
 
245
 
 
246
    Only used when none is set in the environment or the id file.
 
247
 
 
248
    This previously used the FQDN as the default domain, but that can
 
249
    be very slow on machines where DNS is broken.  So now we simply
 
250
    use the hostname.
 
251
    """
 
252
    import socket
 
253
 
 
254
    # XXX: Any good way to get real user name on win32?
 
255
 
 
256
    try:
 
257
        import pwd
 
258
        uid = os.getuid()
 
259
        w = pwd.getpwuid(uid)
 
260
        gecos = w.pw_gecos.decode(bzrlib.user_encoding)
 
261
        username = w.pw_name.decode(bzrlib.user_encoding)
 
262
        comma = gecos.find(',')
 
263
        if comma == -1:
 
264
            realname = gecos
 
265
        else:
 
266
            realname = gecos[:comma]
 
267
        if not realname:
 
268
            realname = username
 
269
 
 
270
    except ImportError:
 
271
        import getpass
 
272
        realname = username = getpass.getuser().decode(bzrlib.user_encoding)
 
273
 
 
274
    return realname, (username + '@' + socket.gethostname())
 
275
 
 
276
 
 
277
def _get_user_id(branch):
 
278
    """Return the full user id from a file or environment variable.
 
279
 
 
280
    e.g. "John Hacker <jhacker@foo.org>"
 
281
 
 
282
    branch
 
283
        A branch to use for a per-branch configuration, or None.
 
284
 
 
285
    The following are searched in order:
 
286
 
 
287
    1. $BZREMAIL
 
288
    2. .bzr/email for this branch.
 
289
    3. ~/.bzr.conf/email
 
290
    4. $EMAIL
 
291
    """
 
292
    v = os.environ.get('BZREMAIL')
 
293
    if v:
 
294
        return v.decode(bzrlib.user_encoding)
 
295
 
 
296
    if branch:
 
297
        try:
 
298
            return (branch.controlfile("email", "r") 
 
299
                    .read()
 
300
                    .decode(bzrlib.user_encoding)
 
301
                    .rstrip("\r\n"))
 
302
        except IOError, e:
 
303
            if e.errno != errno.ENOENT:
 
304
                raise
 
305
        except BzrError, e:
 
306
            pass
 
307
    
 
308
    try:
 
309
        return (open(os.path.join(config_dir(), "email"))
 
310
                .read()
 
311
                .decode(bzrlib.user_encoding)
 
312
                .rstrip("\r\n"))
 
313
    except IOError, e:
 
314
        if e.errno != errno.ENOENT:
 
315
            raise e
 
316
 
 
317
    v = os.environ.get('EMAIL')
 
318
    if v:
 
319
        return v.decode(bzrlib.user_encoding)
 
320
    else:    
 
321
        return None
 
322
 
 
323
 
 
324
def username(branch):
 
325
    """Return email-style username.
 
326
 
 
327
    Something similar to 'Martin Pool <mbp@sourcefrog.net>'
 
328
 
 
329
    TODO: Check it's reasonably well-formed.
 
330
    """
 
331
    v = _get_user_id(branch)
 
332
    if v:
 
333
        return v
 
334
    
 
335
    name, email = _auto_user_id()
 
336
    if name:
 
337
        return '%s <%s>' % (name, email)
 
338
    else:
 
339
        return email
 
340
 
 
341
 
 
342
def user_email(branch):
 
343
    """Return just the email component of a username."""
 
344
    e = _get_user_id(branch)
 
345
    if e:
 
346
        m = re.search(r'[\w+.-]+@[\w+.-]+', e)
 
347
        if not m:
 
348
            raise BzrError("%r doesn't seem to contain "
 
349
                           "a reasonable email address" % e)
 
350
        return m.group(0)
 
351
 
 
352
    return _auto_user_id()[1]
 
353
 
 
354
 
 
355
def compare_files(a, b):
 
356
    """Returns true if equal in contents"""
 
357
    BUFSIZE = 4096
 
358
    while True:
 
359
        ai = a.read(BUFSIZE)
 
360
        bi = b.read(BUFSIZE)
 
361
        if ai != bi:
 
362
            return False
 
363
        if ai == '':
 
364
            return True
 
365
 
 
366
 
 
367
def local_time_offset(t=None):
 
368
    """Return offset of local zone from GMT, either at present or at time t."""
 
369
    # python2.3 localtime() can't take None
 
370
    if t == None:
 
371
        t = time.time()
 
372
        
 
373
    if time.localtime(t).tm_isdst and time.daylight:
 
374
        return -time.altzone
 
375
    else:
 
376
        return -time.timezone
 
377
 
 
378
    
 
379
def format_date(t, offset=0, timezone='original'):
 
380
    ## TODO: Perhaps a global option to use either universal or local time?
 
381
    ## Or perhaps just let people set $TZ?
 
382
    assert isinstance(t, float)
 
383
    
 
384
    if timezone == 'utc':
 
385
        tt = time.gmtime(t)
 
386
        offset = 0
 
387
    elif timezone == 'original':
 
388
        if offset == None:
 
389
            offset = 0
 
390
        tt = time.gmtime(t + offset)
 
391
    elif timezone == 'local':
 
392
        tt = time.localtime(t)
 
393
        offset = local_time_offset(t)
 
394
    else:
 
395
        raise BzrError("unsupported timezone format %r" % timezone,
 
396
                       ['options are "utc", "original", "local"'])
 
397
 
 
398
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
 
399
            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
 
400
 
 
401
 
 
402
def compact_date(when):
 
403
    return time.strftime('%Y%m%d%H%M%S', time.gmtime(when))
 
404
    
 
405
 
 
406
 
 
407
def filesize(f):
 
408
    """Return size of given open file."""
 
409
    return os.fstat(f.fileno())[ST_SIZE]
 
410
 
 
411
# Define rand_bytes based on platform.
 
412
try:
 
413
    # Python 2.4 and later have os.urandom,
 
414
    # but it doesn't work on some arches
 
415
    os.urandom(1)
 
416
    rand_bytes = os.urandom
 
417
except (NotImplementedError, AttributeError):
 
418
    # If python doesn't have os.urandom, or it doesn't work,
 
419
    # then try to first pull random data from /dev/urandom
 
420
    if os.path.exists("/dev/urandom"):
 
421
        rand_bytes = file('/dev/urandom', 'rb').read
 
422
    # Otherwise, use this hack as a last resort
 
423
    else:
 
424
        # not well seeded, but better than nothing
 
425
        def rand_bytes(n):
 
426
            import random
 
427
            s = ''
 
428
            while n:
 
429
                s += chr(random.randint(0, 255))
 
430
                n -= 1
 
431
            return s
 
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
def split_lines(s):
 
503
    """Split s into lines, but without removing the newline characters."""
 
504
    return StringIO(s).readlines()