/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-07 10:47:36 UTC
  • mto: (1092.3.1)
  • mto: This revision was merged to the branch mainline in revision 1397.
  • Revision ID: robertc@robertcollins.net-20050907104736-8e592b72108c577d
symlink support updated to work

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