/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-23 09:25:16 UTC
  • mto: (1092.3.4)
  • mto: This revision was merged to the branch mainline in revision 1390.
  • Revision ID: robertc@robertcollins.net-20050923092516-e2c3c0f31288669d
Merge what applied of Alexander Belchenko's win32 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
if os.name == 'nt':
 
117
    import shutil
 
118
    rename = shutil.move
 
119
else:
 
120
    rename = os.rename
 
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
def isfile(f):
 
132
    """True if f is a regular file."""
 
133
    try:
 
134
        return S_ISREG(os.lstat(f)[ST_MODE])
 
135
    except OSError:
 
136
        return False
 
137
 
 
138
 
 
139
def is_inside(dir, fname):
 
140
    """True if fname is inside dir.
 
141
    
 
142
    The parameters should typically be passed to os.path.normpath first, so
 
143
    that . and .. and repeated slashes are eliminated, and the separators
 
144
    are canonical for the platform.
 
145
    
 
146
    The empty string as a dir name is taken as top-of-tree and matches 
 
147
    everything.
 
148
    
 
149
    >>> is_inside('src', os.path.join('src', 'foo.c'))
 
150
    True
 
151
    >>> is_inside('src', 'srccontrol')
 
152
    False
 
153
    >>> is_inside('src', os.path.join('src', 'a', 'a', 'a', 'foo.c'))
 
154
    True
 
155
    >>> is_inside('foo.c', 'foo.c')
 
156
    True
 
157
    >>> is_inside('foo.c', '')
 
158
    False
 
159
    >>> is_inside('', 'foo.c')
 
160
    True
 
161
    """
 
162
    # XXX: Most callers of this can actually do something smarter by 
 
163
    # looking at the inventory
 
164
    if dir == fname:
 
165
        return True
 
166
    
 
167
    if dir == '':
 
168
        return True
 
169
    
 
170
    if dir[-1] != os.sep:
 
171
        dir += os.sep
 
172
    
 
173
    return fname.startswith(dir)
 
174
 
 
175
 
 
176
def is_inside_any(dir_list, fname):
 
177
    """True if fname is inside any of given dirs."""
 
178
    for dirname in dir_list:
 
179
        if is_inside(dirname, fname):
 
180
            return True
 
181
    else:
 
182
        return False
 
183
 
 
184
 
 
185
def pumpfile(fromfile, tofile):
 
186
    """Copy contents of one file to another."""
 
187
    tofile.write(fromfile.read())
 
188
 
 
189
 
 
190
def uuid():
 
191
    """Return a new UUID"""
 
192
    try:
 
193
        return file('/proc/sys/kernel/random/uuid').readline().rstrip('\n')
 
194
    except IOError:
 
195
        return chomp(os.popen('uuidgen').readline())
 
196
 
 
197
 
 
198
def sha_file(f):
 
199
    import sha
 
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
def sha_string(f):
 
213
    import sha
 
214
    s = sha.new()
 
215
    s.update(f)
 
216
    return s.hexdigest()
 
217
 
 
218
 
 
219
 
 
220
def fingerprint_file(f):
 
221
    import sha
 
222
    s = sha.new()
 
223
    b = f.read()
 
224
    s.update(b)
 
225
    size = len(b)
 
226
    return {'size': size,
 
227
            'sha1': s.hexdigest()}
 
228
 
 
229
 
 
230
def config_dir():
 
231
    """Return per-user configuration directory.
 
232
 
 
233
    By default this is ~/.bzr.conf/
 
234
    
 
235
    TODO: Global option --config-dir to override this.
 
236
    """
 
237
    return os.path.expanduser("~/.bzr.conf")
 
238
 
 
239
 
 
240
def _auto_user_id():
 
241
    """Calculate automatic user identification.
 
242
 
 
243
    Returns (realname, email).
 
244
 
 
245
    Only used when none is set in the environment or the id file.
 
246
 
 
247
    This previously used the FQDN as the default domain, but that can
 
248
    be very slow on machines where DNS is broken.  So now we simply
 
249
    use the hostname.
 
250
    """
 
251
    import socket
 
252
 
 
253
    # XXX: Any good way to get real user name on win32?
 
254
 
 
255
    try:
 
256
        import pwd
 
257
        uid = os.getuid()
 
258
        w = pwd.getpwuid(uid)
 
259
        gecos = w.pw_gecos.decode(bzrlib.user_encoding)
 
260
        username = w.pw_name.decode(bzrlib.user_encoding)
 
261
        comma = gecos.find(',')
 
262
        if comma == -1:
 
263
            realname = gecos
 
264
        else:
 
265
            realname = gecos[:comma]
 
266
        if not realname:
 
267
            realname = username
 
268
 
 
269
    except ImportError:
 
270
        import getpass
 
271
        realname = username = getpass.getuser().decode(bzrlib.user_encoding)
 
272
 
 
273
    return realname, (username + '@' + socket.gethostname())
 
274
 
 
275
 
 
276
def _get_user_id(branch):
 
277
    """Return the full user id from a file or environment variable.
 
278
 
 
279
    e.g. "John Hacker <jhacker@foo.org>"
 
280
 
 
281
    branch
 
282
        A branch to use for a per-branch configuration, or None.
 
283
 
 
284
    The following are searched in order:
 
285
 
 
286
    1. $BZREMAIL
 
287
    2. .bzr/email for this branch.
 
288
    3. ~/.bzr.conf/email
 
289
    4. $EMAIL
 
290
    """
 
291
    v = os.environ.get('BZREMAIL')
 
292
    if v:
 
293
        return v.decode(bzrlib.user_encoding)
 
294
 
 
295
    if branch:
 
296
        try:
 
297
            return (branch.controlfile("email", "r") 
 
298
                    .read()
 
299
                    .decode(bzrlib.user_encoding)
 
300
                    .rstrip("\r\n"))
 
301
        except IOError, e:
 
302
            if e.errno != errno.ENOENT:
 
303
                raise
 
304
        except BzrError, e:
 
305
            pass
 
306
    
 
307
    try:
 
308
        return (open(os.path.join(config_dir(), "email"))
 
309
                .read()
 
310
                .decode(bzrlib.user_encoding)
 
311
                .rstrip("\r\n"))
 
312
    except IOError, e:
 
313
        if e.errno != errno.ENOENT:
 
314
            raise e
 
315
 
 
316
    v = os.environ.get('EMAIL')
 
317
    if v:
 
318
        return v.decode(bzrlib.user_encoding)
 
319
    else:    
 
320
        return None
 
321
 
 
322
 
 
323
def username(branch):
 
324
    """Return email-style username.
 
325
 
 
326
    Something similar to 'Martin Pool <mbp@sourcefrog.net>'
 
327
 
 
328
    TODO: Check it's reasonably well-formed.
 
329
    """
 
330
    v = _get_user_id(branch)
 
331
    if v:
 
332
        return v
 
333
    
 
334
    name, email = _auto_user_id()
 
335
    if name:
 
336
        return '%s <%s>' % (name, email)
 
337
    else:
 
338
        return email
 
339
 
 
340
 
 
341
def user_email(branch):
 
342
    """Return just the email component of a username."""
 
343
    e = _get_user_id(branch)
 
344
    if e:
 
345
        m = re.search(r'[\w+.-]+@[\w+.-]+', e)
 
346
        if not m:
 
347
            raise BzrError("%r doesn't seem to contain a reasonable email address" % e)
 
348
        return m.group(0)
 
349
 
 
350
    return _auto_user_id()[1]
 
351
    
 
352
 
 
353
 
 
354
def compare_files(a, b):
 
355
    """Returns true if equal in contents"""
 
356
    BUFSIZE = 4096
 
357
    while True:
 
358
        ai = a.read(BUFSIZE)
 
359
        bi = b.read(BUFSIZE)
 
360
        if ai != bi:
 
361
            return False
 
362
        if ai == '':
 
363
            return True
 
364
 
 
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