/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

Heikki Paajanen's status -r patch

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